Как стать автором
Обновить

Комментарии 44

А сколько всего было просмотрено пулл реквестов? Если каждый просмотренный содержал ошибки, которые находил анализатор, то это хорошо. Если же пришлось просматривать множество фиксов, то совершенно другое. Сложно говорить о полезности инструмента для фикса критичных багов без такой информации.

Много. Но прежде чем говорить «фи, тогда не интересно», попробуйте сами поискать :). Крайне неблагодарное занятие. Во-первых, при поиске по комментарию, не работает фильтр по языкам и лезут всякие правки в html и т.д. А во-вторых, много багов лежит не в плоскости анализа кода (не тот цвет кнопки, поправили логику работы меню, использовали не ту формулу и т.д.).
PVS — штука определённо полезная, как и данная статья.
Но для совсем честного понимания выгоды от использования PVS случаи
не тот цвет кнопки, поправили логику работы меню, использовали не ту формулу
тоже в общем-то надо учитывать.
Совершенно верно. Мы всегда говорим, что если хочется получить качественное ПО, следует использовать одновременно несколько методологий (придерживаться принятого стандарта кодирования, юнит-тесты, ручное тестирование, регрессионные тесты, динамический анализ, статический анализ и т.д.)
Спасибо, за очередное интересное исследование. А можно как то получить временно на месяц, сравнить с PC Lint. PC Lint, я так понял полноценный компилятор почти… т. е он проверяет исходный код и не нуждается в наличии стороннего компилятора. Это будет имееть какое то преимущество перед PVS-Studio? Просто, напрмер, я сравнивал PC Lint и IAR CSTAT. Ну это небо и земля. CSTAT половину ошибок дает, на, то чего в коде даже нет, но видимо есть в обьектных файлах есть после компилятора.
Плюсом, Lint делает еще замечания, типа бест практис, что так лучше не делать, но ничего страшного…
А можно как то получить временно на месяц, сравнить с PC Lint.
Можно. Напишите нам и мы выдадим Вам ключ на месяц.
PC Lint, я так понял полноценный компилятор почти… т. е он проверяет исходный код и не нуждается в наличии стороннего компилятора.
Так это… PVS-Studio тоже почти полноценный компилятор (по разбору кода). Внешние компиляторы используются только для препроцессирования. Препроцессировали внешним компилятором, сами построили дерево разбора и давай применять разные технологии анализа :).
P.S. Почему мы сами не пишем о сравнении PVS-Studio с другими статическими анализаторами кода: www.viva64.com/ru/b/0637

Всегда с интересом читаю ваши статьи.
Но в данном случае мне непонятно одно… Почему стандарт языка, а за ним все следом говорят о том, что разыменование указателя с адресом 0 является UB? Ноль — это всего лишь адрес памяти к которому так же можно "достучаться" (я даже доказал это на одном известном форуме).
С таким же успехом можно сказать про любой указатель в любой программе.
Дело в том, что разработчики стандарта языка просто приняли за должное считать nullptr невалидным адресом. Но почему-то они не учли, что на языке C++ можно писать без конкретной ОС. Вообще без ОС, где по нулевому адресу (например на архитектуре x86) находится таблица векторов прерываний.
Хотелось бы услышать ваше мнение по этому поводу.

По факту в большинстве взрослых архитектур и ос (16 битный дос не берем) первые 64к адресного пространства процессов огорожены или вообще не выделены, т.е. любое обращение к ним даст segfault.
Глубинный смысл именно в защите памяти от ошибок работы с указателями. Ну как в этой статье зевнули *. Лучше упасть так, чем запороть данные и дальше неизбежно пойти в разнос.
По факту в большинстве взрослых архитектур и ос (16 битный дос не берем) первые 64к адресного пространства процессов огорожены или вообще не выделены, т.е. любое обращение к ним даст segfault.
По какому-такому факту? Кто вам сказал этот бред? Что значит «не выделены»? Кто их должен «выделять»?
Я легко могу обратиться к адресу ниже 64к на x86 без ОС.
И да, разделяйте понятия «архитектура» и «ОС».
Кстати, процессор архитектуры x86 изначально запускается в 16-битном режиме, а уж после (по требованию загрузчика или ОС) переходит в защещенный.
Разыменование нулевого указателя не является undefined behavior. Лишь доступ к этому значению. Самый тупой пример &*(p = (void*)0) вполне определено. Кстати, обратите внимание на void. Само по себе разыменовывание 0 теоретически означает разыменовывание 0 адреса, что опять-таки без проблем, хотя тут тоже стандарт как-то не очень об этом говорит. А и segfault — это ядро ругается, и это не часть стадарта. Поверьте, если бы любое разыменование нулевого указателя приводило к Segfault, то и уязвимостей бы не было. А тут, сами посмотрите github.com/FFmpeg/FFmpeg/commits/master от 12 июля Null dereference, сюрприз!

Кстати, тут недавно все спорили насчет &p->element. Дак вот, я считаю, что стандарт об это ничего не говорит вообще. Это такое крупное пятно.
Но давайте всё-таки определим несколько вещей. Вы понимаете, что по факту нет никакого оператора ->? Это просто shorthand для (*p).element (скобки обязательны, поэтому и shorthand). Очевидно, что это туфта какая-то для NULL. Теперь добавим &. Оно раскрывается в &((*p).element). Обратите внимание на еще одни скобки. Понимаете, вы берете & от element! Но структуры то нет… В общем это как-то не очень само по себе. На самом деле все еще сложнее…

Теперь про offsetof. Wikipedia уже исправили.
en.wikipedia.org/wiki/Offsetof
«It has generated some debate if this is undefined behavior according to the C standard, since it appears to involve a dereference of a null pointer (although, according to the standard, section 6.6 Constant Expressions, Paragraph 9, the value of the object is not accessed by the operation).

И кстати, когда вы искали на stackechange, слона то вы и не заметили, там такое в одном месте есть…
stackoverflow.com/questions/28482809/c-access-static-members-using-null-pointer/28483477
Дак вот, я считаю, что стандарт об это ничего не говорит вообще.

Разве в C99 нет специального параграфа именно по поводу суперпозиции & и *?


C99, §6.5.3.2
The unary & operator yields the address of its operand.

If the operand is the result of a unary * operator,
neither that operator nor the & operator is evaluated and the result is as if both were
omitted, except that the constraints on the operators still apply and the result is not an
lvalue.

Даже больше, есть специальная сноска, явно определяющая &*NULL как корректную операцию:


C99, footnote 87
Thus, &*E is equivalent to E (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)).
Дак вот, я считаю, что стандарт об это ничего не говорит вообще.
Разве в C99 нет специального параграфа именно по поводу суперпозиции & и *?

Так тут, насколько я понимаю, явной суперпозиции нет (в случае &(p->element)). Порядок операций-то такой — dereference, обращение к полю, получение адреса, т.е. & и * идут не подряд, и "свернуть" их так, как указано в цитате, нельзя.

Разыменование нулевого указателя не является undefined behavior.
Согласно стандарту — является. В последнем черновике не удалось беглым взглядом найти это утверждение. Приведу другой источник.

Само по себе разыменовывание 0 теоретически означает разыменовывание 0 адреса...
Так я об этом и писал.

А и segfault — это ядро ругается, и это не часть стадарта.
Про segfault я ничего и не говорил. Это к товарищу vanxant.
Месье каждый день пишет или хотя бы запускает софт, работающий в реальном режиме х86? Кроме самых первых стадий инициализации биоса мне даже в голову ничего не приходит.
В ембеде да, такое встречается. Но там у них вообще, главный кукбук — еррата на конкретный чип.
Может пишет, а может и не пишет. Зависит от настроения. Вы вообще к чему это спросили? Какое это имеет отношение к данной теме?

Вы можете ответить хотя бы на один из моих вопросов?
Спросил я это к тому, что в реальной жизни, даже если как-то обхитрить компилятор, за обращение по адресу ноль подавляющее число ОС грохнут ваш процесс. Поэтому даже не очень важно, что там написано в стандарте языка.
Отвечать на поток вопросов в стиле «кто вам сказал этот бред» я не намерен.
Интересный вы человек…
Отвечать на поток вопросов в стиле «кто вам сказал этот бред» я не намерен.
Ну ответьте хотя-бы на не бредовые вопросы.
Я Вас чем-то обидел?
Задайте вопрос по теме — постараюсь ответить. Я пока просто не вижу, о чём вы хотите узнать.
Почему разыменование nullptr это UB? Я уже ответил: потому что в большинстве ОС это может привести к краху процесса. Но в каких-то случаях (например, системах без MMU) — может и не привести. При этом в начале адресного пространства процесса обычно ОСи хранят всякие системные таблицы — про память, права, дескрипторы и т.д, лазить в которые как минимум опасно и может привести к непредсказуемым эффектам. Так что это именно undefined, а не unspecified behaviour.

Это не "наезд", не обижайтесь.

Всё это уже обсуждалось: 1, 2. С практической точки зрения, лично для нас, тема не очень интересна. Для тех систем, где применяется PVS-Studio, разыменование нулевого указателя — это беда. Если PVS-Studio столкнётся с системой, где надо писать по нулевому указателю, это будет особый случай и всё равно там будет куча подпорок для компиляторов и анализаторов, чтобы они «замолчали» на разный странный код :).

Вот только нулевой указатель (nullptr) и указатель на нулевой адрес памяти (reinterpret_cast<T*>((int)0)) — это разные значения, которые могут "случайно" совпасть в рантайме.

Мы сейчас про С++ говорим? nullptr это вообще то не указатель. Он может вернуть указатель, но указателем не является. Поэтому я не могу понять ваше второе предложение… Вы теплое пытаетесь сравнить с мягким

AntonSazonov


Почему стандарт языка, а за ним все следом говорят о том, что разыменование указателя с адресом 0 является UB?

Прежде всего, неопределенным поведением является разыменование именно некорректного указателя, а не указателя с адресом 0:


C99, footnote 87
Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.

При этом под нулевым указателем понимается не указатель с адресом 0, а некоторая константа NULL, или nullptr с внутренним представлением, которое вполне может оказаться не 0. Другими словами, для корректного указателя ptr верно ptr != NULL, даже если (uintptr_t) ptr == 0.


C99, §6.3.2.3
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

P.S. Занимательно, что в таком случае указатели ptr1 и ptr2


uintptr_t zero = 0;
void
  *ptr1 = (void *) 0,  /* == NULL */
  *ptr2 = (void *) zero;  /* != NULL */

могут оказаться не равны, потому что zero для ptr2 не является integer constant expression.

Именно, что на разных архитектурах будет разное поведение.
И поэтому в общем оно не определено. Когда вы программируете embedded-систему, где это легальный адрес, поведение будет тоже вполне определённым. Но тут речь о стандарте всего языка, а не только в применении к микроконтроллерам. Именно поэтому оно и называется прямо, как есть — undefined.


(к слову, не так досаждает попытка доступа по нулевому адресу, как аналогичная по околонулевому. Когда указатель на инстанс класса нулевой, но при этом конкретное поле уже имеет другое (ненулевое) значение. Например, 0x10. Проверку на "не-нуль" оно проходит, но при использовании это всё равно UB).

То, что вы предлагаете называть undefined, в стандарте называется unspecified. Код, содержащий undefined behaviour, не может быть корректным в частном случае, в отличие от кода, содержащего unspecified behaviour. Вы согласны с тем, что следующий код не содержит UB?


void foo(uintptr_t offset) {
  char *ptr = (char *) offset;
  char val = *ptr;
}

foo(0);

От компилятора зависит, как именно offset будет приведен к ptr.
От архитектуры и операционной системы зависит, что именно случится при чтении по адресу ptr.
На мой взгляд, здесь нет неопределенного поведения с точки зрения языковой грамматики.

Если PVS-Studio столкнётся с системой, где надо писать по нулевому указателю...
А есть такая вероятность?)
Ждём статью о том, какие ошибки были допущены при написании PVS-Studio и были выявлены и исправлены после проверки инструментом PVS-Studio.
Для тех, кто в теме.


Мы регулярно проверяем наш статический анализатор самим собой.

Во-первых, у нас используется инкрементальный анализ. То есть анализатор автоматически стартует на файлах, которые только что были перекомпилированы. И ошибки (которые обнаруживаются анализатором) если и появляются в нашем коде, то сразу же исправляются. При этом они не попадают ни в систему контроля версий, ни в баг-трекер.

Во-вторых, у нас настроен автоматический запуск анализатора каждую ночь на свои исходники. Поэтому даже если результаты инкрементального анализа кто-то просмотрел, то разработчикам приходят по почте отчеты об найденных и не устранённых ошибках.

В итоге, хотя мы и регулярно проверяем свой код, мы никогда не сможем написать статью об ошибках, найденных с помощью нашего анализатора в нем самом.

НО! Кое-что есть: Проверяем исходный код плагина PVS-Studio с помощью PVS-Studio.
> В итоге, хотя мы и регулярно проверяем свой код, мы никогда не сможем написать статью об ошибках, найденных с помощью нашего анализатора в нем самом.

Вот этого вывода я не понял.
Если, например, новой эвристикой вы находите что-то, что раньше не находилось, почему бы не написать об этом на примере собственного кода?
Да, сбор данных будет не одномоментный, а растянутый во времени, но всё равно может набраться достаточно интересных примеров.
Очень сложно подвигнуть коллег программистов всё это аккуратно выписывать в какое-то общее место. Поправили и пошли дальше программировать. По себе знаю. Неоднократно было. Поправляешь ошибку, и только потом уже вспоминаешь, что хорошо бы записать. Но кода то с ошибкой уже нет, надо править обратно… В общем лень и тихо мирно делаешь вид, что ничего и не было :).
Глупый вопрос по первому примеру: а разве сам компилятор не выдавал предупреждения при компиляции, что функция не возвращает результат?
Мне казалось, что GCC вполне себе выкидывал предупреждения (хотя, давно не писал на С++, может и ошибаюсь) и исправить можно было и без анализатора. Тогда тут разработчики сами виноваты, что предупреждения не читают (еще, как вариант: у них столько предупреждений при компиляции, что они на них уже не обращают внимания).
вы бы знали как этого не хватает в Питоне))
язык замечательный, но лажа вокруг него…
начиная от помощника IDE который невероятно хуже чем в Java

у вас идей нет сделать кросс -языковой вариант (просто по набору правил, тогда расширить можно для всего) или вариант чтобы юзем мог програмно его вызывать в качестве макро движка было бы круто
> у вас идей нет сделать кросс -языковой вариант
Нет. Если уж делать, то полноценный анализатор.
просто сделайте расширяемым чтобы люди могли сами кидать свои правила общение юзеров вроде форума и возможностью сохранять и загружать набор правил на гитхаб
Это всё фантазии. Разработка статического анализа — это сложная задача, требующая погружения в предметную область. Поэтому, теоретически, да все будут писать правила. А как дело доходит до практики, то Cppcheck объявляет сбор средств, чтобы нормально научиться искать неинициализированные переменные.
вы собираете данные из реальных проэктов поповоду того какие там баги?
например пользователь устанавливает это у себя в IDE и потом по сообщению IDE об ошибке используется эвристика для поиска (по эксепшну) или при нахождении неизвестного бага он заносится в список и потом его можно сохранить у себя или посылается вам в базу.
Нет, мы не воруем исходники у пользователей.

А то, что вы пишите именно так и будет восприниматься.
ну вам же нужно как то базу расширять. Сообщение может посылаться максимально оторванным от кода — только паттерн бага с переменными названными по другому, чтобы ничего воровать. Кстати я тут просмотрел базу, вы некоторые правильные выражения записали как баги, ну что неправильного в:

Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'.

обычный инкремент поинтера

The 'first' argument of 'Foo' function is equal to the 'second' argument.

тоже часто бывает в векторных операциях и т.п, ничего криминального

A suspicious expression 'A[B < C]'. Probably meant 'A[B] < C'.

А это с какой стати вообще?

обычный инкремент поинтера

Только лично мне, например, непонятно, зачем инкрементировать указатель и в той же самой операции его разыменовывать, особенно если результат разыменования ничему не присваивается (как обычно бывает в случае, если задумывалось инкрементировать значение по указателю).


тоже часто бывает в векторных операциях и т.п, ничего криминального

Читайте внимательнее:


Передача одного и того же значения в качестве двух аргументов для многих функций является нормальной ситуацией. Но если речь идет о таких функциях как memmove, memcpy, strstr, strncmp, то это очень подозрительная ситуация.

А это с какой стати вообще?

А Вы умеете индексировать массивы boolean-ами, да ещё и так, что код получается понятным, читабельным и поддерживаемым?

просто

просто???

как_нарисовать_сову.jpg
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.