Как получить предупреждение об указателях на локальные переменные вне области видимости

Рассмотрим следующий код:

#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 и тому подобном.

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

+ Если вам нужно указать на локальную переменную, вы можете определить ее как статическую, которая помещает переменную вне стека в ОЗУ. (Вы, вероятно, знаете это, но знаете, что некоторые не могут.)

Другие вопросы по тегам