Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 23.01.23 20:04
Оценка: :)
Я с незапамятных времен привык компилировать весь свой код со всеми включенными предупреждениями (/W4 /WX /Wall для VC++), и он у меня набит Assume/Assert, так что статическим анализом никогда систематически не занимался — когда-то попробовал анализатор из WDK, но он мне не понравился. В кои веки решил попробовать последние компиляторы VC++ с ключом /analyze. Сразу же пошли (от компилятора 19.29.30147) предупреждения такого вида:

warning C6011: Dereferencing NULL pointer 'p

на таком примерно коде:

void f (int * p) {

  if (p == nullptr) Break ();

  *p = 0;

}


На самом деле на месте таких if у меня везде стоят Assert'ы, которые примерно так и раскрываются.

Хохма в том, что при удалении if'а предупреждение анализатора пропадает.

Чтобы оно пропало при наличии if'а, нужно использовать какую-то особую форму обработки условия? Или это просто глюк анализатора, и надо писать багрепорт?
Re: Предупреждение анализатора "Dereferencing NULL pointer"
От: vsb Казахстан  
Дата: 23.01.23 20:14
Оценка: 20 (2) +2
Ну очевидно, что анализатор не знает, что такое Break() в твоём коде.

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

В твоём же коде явно есть проверка на null. Из чего анализатор делает вывод, что null тут точно может быть. Дальше он видит, что ты вызываешь функцию Break(), после чего разыменовываешь этот самый нулевой указатель.

Я предполагаю, что твоя функция Break() на самом деле не возвращает управление в вызывающую её функцию. Но анализатор про это не знает. Собственно в этом и суть проблемы. Тебе надо как-то пометить для анализатора эту функцию, чтобы он знал, что эта функция не возвращает результат.

Ну или написать "холостые" return везде в таких местах, если предыдущий пункт невозможно сделать.

Я бы попробовал noreturn: https://en.cppreference.com/w/cpp/language/attributes/noreturn
Re: Предупреждение анализатора "Dereferencing NULL pointer"
От: wander  
Дата: 23.01.23 20:16
Оценка: 18 (1) +1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Чтобы оно пропало при наличии if'а, нужно использовать какую-то особую форму обработки условия? Или это просто глюк анализатора, и надо писать багрепорт?


А если аттрибут [[ noreturn ]] к функции Break добавить?
Re: Предупреждение анализатора "Dereferencing NULL pointer"
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.01.23 20:37
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Чтобы оно пропало при наличии if'а, нужно использовать какую-то особую форму обработки условия? Или это просто глюк анализатора, и надо писать багрепорт?


В C ведь на указателе не написано, может он быть NULL-ом или нет, так ведь? Т.а., анализатору надо как-то хитро догадаться.

Так вот, анализатор думает, что если ты в какой-то ветке проверяешь указатель на NULL, значит, он в принципе может быть NULL. А это значит, что если в какой-то другой ветке тот же самый указатель используется без предварительной проверки на NULL, может быть ай-ай-ай.
Re[2]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 23.01.23 20:54
Оценка: -1
Здравствуйте, vsb, Вы писали:

vsb>Ну очевидно, что анализатор не знает, что такое Break() в твоём коде.


Ему достаточно знать то, что Break никак не может повлиять на значение указателя, кроме как через побочные эффекты. Но наличия подобных эффектов он как раз не предполагает, иначе предупреждения валились бы на половину всего кода.

vsb>Если у тебя нет if-а, он предполагает, что ты где-то выше проверил указатель на null.


На каком основании он это предполагает? Это где-то описано?

vsb>Я предполагаю, что твоя функция Break() на самом деле не возвращает управление в вызывающую её функцию.


Возвращает. Она вызывает отладочное прерывание, затем возвращается.

vsb>Тебе надо как-то пометить для анализатора эту функцию, чтобы он знал, что эта функция не возвращает результат.


Бред. В чем тогда смысл assert'ов, которые всю жизнь использовались для проверки инвариантов, но это никогда не предполагало обязательного аварийного завершения? В стандартной реализации VC++ он выкидывает запрос вида "abort/debug/ignore".
Re[3]: Предупреждение анализатора "Dereferencing NULL pointer"
От: пффф  
Дата: 23.01.23 21:00
Оценка: +4
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Ему достаточно знать то, что Break никак не может повлиять на значение указателя, кроме как через побочные эффекты. Но наличия подобных эффектов он как раз не предполагает, иначе предупреждения валились бы на половину всего кода.


vsb>>Если у тебя нет if-а, он предполагает, что ты где-то выше проверил указатель на null.


ЕМ>На каком основании он это предполагает? Это где-то описано?


Зачем ты тут споришь? Тебе сказали, какова может быть логика анализатора, тебе она не нравится. Ну иди спорь с разработчиками анализатора, там хоть толк какой-то может получится


vsb>>Я предполагаю, что твоя функция Break() на самом деле не возвращает управление в вызывающую её функцию.


ЕМ>Возвращает. Она вызывает отладочное прерывание, затем возвращается.


И затем идёт разыменование нулевого указателя. Не понял, какие претензии к анализатору?


ЕМ>Бред. В чем тогда смысл assert'ов, которые всю жизнь использовались для проверки инвариантов, но это никогда не предполагало обязательного аварийного завершения? В стандартной реализации VC++ он выкидывает запрос вида "abort/debug/ignore".


Ассерты обычно просто завершают работу. MS решила, что можно предложить какие-то варианты. Обычно люди определяют свой ассерт, который в лог пишет, и затем тоже завершает работу. Ассерт всегда предполагает аварийное завершение. Если указатель может быть нулевым штатно, то это без всяких ассертов обрабатывают
Re[2]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 23.01.23 21:06
Оценка: -5
Здравствуйте, wander, Вы писали:

W>А если аттрибут [[ noreturn ]] к функции Break добавить?


Вроде помогает. Но это ж дурдом. Она не обязана обеспечивать завершение программы — только вывалиться в отладчик, где я могу изменить значение указателя.
Re[3]: Предупреждение анализатора "Dereferencing NULL pointer"
От: пффф  
Дата: 23.01.23 21:08
Оценка: +3
Здравствуйте, Евгений Музыченко, Вы писали:

W>>А если аттрибут [[ noreturn ]] к функции Break добавить?


ЕМ>Вроде помогает. Но это ж дурдом. Она не обязана обеспечивать завершение программы — только вывалиться в отладчик, где я могу изменить значение указателя.


Дурдом — это ждать от анализатора, что он будет уметь в телепатию
Re[3]: Предупреждение анализатора "Dereferencing NULL pointer"
От: vsb Казахстан  
Дата: 23.01.23 21:13
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

vsb>>Если у тебя нет if-а, он предполагает, что ты где-то выше проверил указатель на null.


ЕМ>На каком основании он это предполагает? Это где-то описано?


На основании здравого смысла. Никому не нужен статический анализатор, который завалит весь проект предупреждениями. Поэтому у анализатора всегда сложная задача — с одной стороны найти код с багами, с другой стороны не показывать false positive, чтобы в них не утонули полезные сообщения.

vsb>>Я предполагаю, что твоя функция Break() на самом деле не возвращает управление в вызывающую её функцию.


ЕМ>Возвращает. Она вызывает отладочное прерывание, затем возвращается.


Ну тогда noreturn тут возможно не совсем подходит. Это кажется нетипичной ситуацией.

vsb>>Тебе надо как-то пометить для анализатора эту функцию, чтобы он знал, что эта функция не возвращает результат.


ЕМ>Бред. В чем тогда смысл assert'ов, которые всю жизнь использовались для проверки инвариантов, но это никогда не предполагало обязательного аварийного завершения? В стандартной реализации VC++ он выкидывает запрос вида "abort/debug/ignore".


Это не важно, возвращает ли на самом деле функция управление или нет. Важно лишь то, что ты хочешь выразить этой функцией. Статический анализатор это не какой-то инструмент, гарантирующий что-либо. Это просто инструмент, который пытается понять твой код и найти в нём проблемы. Если ты пишешь assert, то ты выражаешь некий инвариант. И статический анализатор этот инвариант будет учитывать. Даже если твой рантайм отключает эту проверку. Ты ведь не просто так assert пишешь, не для красоты.

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

Я не нашёл, как можно передать информацию только статическому анализатору. Есть SAL Annotations, но они влияют на работу оптимизатора, что может быть не совсем хорошо.
Re[3]: Предупреждение анализатора "Dereferencing NULL pointer"
От: CreatorCray  
Дата: 23.01.23 21:32
Оценка: +5
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Возвращает. Она вызывает отладочное прерывание, затем возвращается.

Тогда всё правильно говорит анализатор, у тебя есть путь по которому *p будет гарантированно дереференсить NULL.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[3]: Предупреждение анализатора "Dereferencing NULL pointer"
От: CreatorCray  
Дата: 23.01.23 21:32
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>где я могу изменить значение указателя.

А можешь и не изменить.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[4]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 23.01.23 22:00
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Ну тогда noreturn тут возможно не совсем подходит.


На практике — подходит, поскольку у VC++ атрибут __declspec (noreturn) не влияет на генерацию кода, а только на анализ. А в каких-то реализациях я видел подобные атрибуты, которые генерировали функцию без кода возврата.

vsb>Это не важно, возвращает ли на самом деле функция управление или нет.


Не важно это до тех пор, пока атрибут не влияет на генерацию кода.

vsb>Статический анализатор это не какой-то инструмент, гарантирующий что-либо.


Дык, это понятно. Просто я привык к "нефатальным" assert'ам, а тут такое.

vsb>Я не нашёл, как можно передать информацию только статическому анализатору. Есть SAL Annotations, но они влияют на работу оптимизатора, что может быть не совсем хорошо.


Вот и я в SAL такого не вижу. Удачно, что __declspec (noreturn) на нее не влияет.
Re[4]: Предупреждение анализатора "Dereferencing NULL pointer"
От: KOLRH Финляндия  
Дата: 24.01.23 07:08
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, Евгений Музыченко, Вы писали:


ЕМ>>Возвращает. Она вызывает отладочное прерывание, затем возвращается.

CC>Тогда всё правильно говорит анализатор, у тебя есть путь по которому *p будет гарантированно дереференсить NULL.

Может надо добавить else?
void f (int * p) {

  if (p == nullptr) Break ();
  else *p = 0;

}
Re[5]: Предупреждение анализатора "Dereferencing NULL pointer"
От: пффф  
Дата: 24.01.23 08:04
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

vsb>>Ну тогда noreturn тут возможно не совсем подходит.


ЕМ>На практике — подходит, поскольку у VC++ атрибут __declspec (noreturn) не влияет на генерацию кода, а только на анализ. А в каких-то реализациях я видел подобные атрибуты, которые генерировали функцию без кода возврата.


Это лютый костылище и пипецома подавлять предупреждения анализатора такими директивами. потому что это а) нестандартно б) в любой момент может начать работать по другому. И это пишет человек, которому не дают спокойно спать побочные эффекты плюсовых шаблонов, которые ещё 30 лет назад стали стандартом де факто. Тут кроме других эмоций нет


vsb>>Это не важно, возвращает ли на самом деле функция управление или нет.


ЕМ>Не важно это до тех пор, пока атрибут не влияет на генерацию кода.


__declspec (noreturn), как я понимаю, лучше?


ЕМ>Дык, это понятно. Просто я привык к "нефатальным" assert'ам, а тут такое.


Странная и вредная привычка. MS-ноё Abort/Retry/Ignore — это пипец какой звоночек, что что-то пошло не так, а не "нефатальный" assert. Хорошо, что ты драйвера пишешь только для своего продукта, кстати, как он называется? Ну, чтобы стороной обходить?
Re[5]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.01.23 09:47
Оценка:
Здравствуйте, KOLRH, Вы писали:

KOL>Может надо добавить else?


Так все равно ж возможный путь отслеживается. По уму, у анализатора должно быть правило для таких случаев, но у MS-овского таких нет.
Re[6]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.01.23 09:55
Оценка:
Здравствуйте, пффф, Вы писали:

П>Это лютый костылище и пипецома подавлять предупреждения анализатора такими директивами.


Так я в курсе, а что есть лучшего? Будь у анализатора подходящее правило — я б применил его, но нету ж.

П>нестандартно


А что здесь стандартно?

П>в любой момент может начать работать по другому.


Как начнет, так и буду разбираться. Ежели очередная версия компилятора и перестанет генерить код возврата по __declspec (noreturn), я это увижу при первом же отладочном запуске. Но вероятность этого чуть меньше, чем нулевая, поскольку атрибут применяется уже очень давно, и поломается слишком многое.

П>__declspec (noreturn), как я понимаю, лучше?


На безрыбье — да.

ЕМ>>я привык к "нефатальным" assert'ам


П>Странная и вредная привычка.


Что в ней вредного? Assert'ы есть только в отладочном коде, который запускается только под отладчиком. Их задача — выявить несоответствия между внутренней логикой, поэтому в релизных сборках их нет. Внешние же несоответствия отсекаются другими методами. Это классический подход, который не менялся с незапамятных времен.
Re[3]: Предупреждение анализатора "Dereferencing NULL pointer"
От: AleksandrN Россия  
Дата: 25.01.23 16:46
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Вроде помогает. Но это ж дурдом. Она не обязана обеспечивать завершение программы — только вывалиться в отладчик, где я могу изменить значение указателя.


Можешь изменить, а можешь и не изменить.
А перед тем, как код пойдёт в продакшн, ты выкинешь ассерт? А разыменовывание указателя тоже выкинешь?

void f (int * p) {

  if (p == nullptr) {
    Break ();
    return;
  }

  *p = 0;

}


Тогда и анализатор ругаться не будет и сам случайно не разыменуешь нулевой указатель.
Re[4]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.01.23 17:59
Оценка:
Здравствуйте, AleksandrN, Вы писали:

AN>перед тем, как код пойдёт в продакшн, ты выкинешь ассерт?


Его выкинет компилятор — в релизных сборках он определяется, как пустой макрос.

AN>
AN>  if (p == nullptr) {
AN>    Break ();
AN>    return;
AN>  }
AN>


Так нежелательно. За такими assert'ами далеко не всегда идет код, неспособный переварить нулевой указатель — просто по логике он должен быть ненулевым. Именно поэтому я и не ставлю принудительного завершения при каждом срабатывании, чтобы в отладчике иметь возможность пустить дальше.

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

Еще он возбуждается на работу с массивами фиксированного размера. Например, определяю массив из 12 символов, в который записываю текстовое представление целого 32-разрядного числа — он утверждает, что обнаружил возможность записи туда 14 символов. Меняю 12 на 16 — начинает утверждать, что теперь видит риск попадания туда 18-ти.

Не знаю даже, зачем я с этим вожусь — пока он не нашел в коде ни одного косяка, одни "необоснованные подозрения" (C).
Re[5]: Предупреждение анализатора "Dereferencing NULL pointer"
От: AleksandrN Россия  
Дата: 25.01.23 20:21
Оценка: +2
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>просто по логике он должен быть ненулевым. Именно поэтому я и не ставлю принудительного завершения при каждом срабатывании, чтобы в отладчике иметь возможность пустить дальше.


Зачем в таком случае пускать его в отладчике дальше, вместо того, что бы посмотреть по стеку вызовов, откуда в функцию прилетает нулевой указатель и почему он там портится?

А если после проверки на nullptr потом разыменовывать указатель вне зависимости от результатов проверки, то есть вероятность, что при тестировании не все возможные случаи были протестированы, тогда это может привести к падению в продакшене.
Re[6]: Предупреждение анализатора "Dereferencing NULL pointer"
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 26.01.23 10:14
Оценка:
Здравствуйте, AleksandrN, Вы писали:

AN>Зачем в таком случае пускать его в отладчике дальше, вместо того, что бы посмотреть по стеку вызовов, откуда в функцию прилетает нулевой указатель и почему он там портится?


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