Как получить предупреждение об указателях на локальные переменные вне области видимости
Рассмотрим следующий код:
#include <stdio.h>
void badidea(int**);
int main(void) {
int* p;
badidea(&p);
printf("%d\n", *p); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
return 0;
}
void badidea(int** p) {
int x = 5;
*p = &x;
}
Намерение, кажется, что это напечатает 5
, но фактически вызывает неопределенное поведение из-за разыменования указателя на локальную переменную вне области видимости в main
, Как я могу найти экземпляры этой проблемы в базе кода? Вот что я пробовал до сих пор:
- Компилирование с
gcc -Wall -Wextra -pedantic
- Компилирование с
clang -Weverything
- Бег, скомпилированный с
clang -fsanitize=undefined
- Работает под
valgrind
Ничто из вышеперечисленного не выдало никаких предупреждений.
3 ответа
Компиляция сначала с GCC 7.2 и без -fsanitize=address
и затем запуск под Valgrind производит следующее:
==25751== Conditional jump or move depends on uninitialised value(s)
==25751== at 0x4E988DA: vfprintf (vfprintf.c:1642)
==25751== by 0x4EA0F25: printf (printf.c:33)
==25751== by 0x1086E5: main (in ./a.out)
следуют другие предупреждения.
Наш инструмент CheckPointer может обнаружить это с помощью динамического анализа, наряду с множеством других ошибок доступа к памяти. Инструмент отслеживает все выделения, доступы и назначения, связанные с явными или неявными указателями, и жалуется в самый ранний момент, когда такой доступ является незаконным или неопределенным.
Сохранение примера кода OP как "buggy.c" и запуск CheckPointer приводит к следующему выводу (некоторые строки удалены из педагогических соображений):
C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential Powered by DMS (R) Software Reengineering Toolkit
Parsing source file "E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
Grouping top level declarations ...
Creating object meta data initializers ...
Normalizing syntax tree ...
Instrumenting syntax tree ...
Ungrouping top level declarations ...
Writing target file "E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code gcc.exe -I"e:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c Target\check-pointer-data-initializers.c "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer.c" "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-splay-tree.c" "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-wrappers.c"
*** Executing instrumented application
*** Error: CWE-465 Pointer Issue (subcategory CWE-476, CWE-587, CWE-824, or CWE-825)
Dereference of dangling pointer.
in function: main, line: 8, file E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
Конкретный тип ошибки сообщается с использованием кодов, определенных стандартом перечисления общей слабости.
NIST предлагает "пытки" теста для ошибок Java и C под названием Джульетта. CheckPointer обнаружил 13257 ожидаемых ошибок доступа к памяти из 14 195 тестовых примеров Juliet, относящихся к языку C. 908 тестовых случаев не были диагностированы, но они включают в себя те, которые содержат неопределенное поведение, не связанное с ошибками использования указателя (которые CheckPointer не предназначен для обнаружения), или ошибками использования указателя, которые не были обнаружены при фактическом выполнении (например, неинициализированная переменная содержала 0 в фактическое исполнение). [Мы изменили некоторые из этих примеров, чтобы фактическое выполнение не содержало 0 для таких переменных, а затем CheckPointer выдал сообщение об ошибке, как и ожидалось.]
CheckPointer работает с GCC и MSVisualStudio.
=======================================
@nm сделал несколько комментариев к различным ответам в этой теме. Он выпустил своего рода проблему, в которой продемонстрировал, что valgrind не может найти ошибку в следующем коде, похожую на OP, но более глубоко вложенную:
#include <stdio.h>
void badidea(int**);
void worseidea(int**);
int main(void) {
int* p;
badidea(&p);
// printf("%d\n", *p); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
worseidea(&p);
return 0;
}
void worseidea(int **p) {
int x = 42;
printf("%d %d\n", **p, x); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
}
void badidea(int** p) {
int x = 5;
*p = &x;
}
Вот прогон Checkpointer, который диагностирует проблему указателя в коде nm:
C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential
...
Parsing source file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
...
Writing target file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code
gcc.exe -I"c:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c Target\check-pointer-data-initializers.c "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-
pointer.c" "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-splay-tree.c" "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-wrappers.c"
*** Executing instrumented application
*** Error: CWE-465 Pointer Issue (subcategory CWE-476, CWE-587, CWE-824, or CWE-825)
Dereference of dangling pointer.
in function: worseidea, line: 16, file C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
called in function: main, line: 10, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
Я не думаю, что такой механизм существует в языке Си, в конце указатели - это просто переменные, содержащие адреса. Когда вы задаете им тип, он просто сообщает компилятору, какая переменная находится в адресном пространстве, указанном указателем. Таким образом, в теории указатель может содержать любые значения адресов, если они находятся в определенном адресном пространстве.
И на самом деле это то, что делает язык Си действительно мощным. Особенно в механизмах передачи данных, потому что вы можете отправлять данные любого типа, даже определяемые пользователем структуры и т. Д., В любом порядке и легко получать / преобразовывать данные на другом конце, не беспокоясь о endiannes и тому подобном.
Хотя в вашем случае, надеюсь, если вы знаете размер стека и начальный адрес вашей программы, вы можете проверить, находится ли содержимое адреса, на которое указывает указатель, в области, зарезервированной для стека, или нет. Таким образом, зная, указываете ли вы на локальную переменную или нет.
+ Если вам нужно указать на локальную переменную, вы можете определить ее как статическую, которая помещает переменную вне стека в ОЗУ. (Вы, вероятно, знаете это, но знаете, что некоторые не могут.)