2013-05-25 3 views
9

Мне было бы интересно узнать, возможно ли его явное заражение переменной в C как неинициализированной.Есть ли способ установить переменную как неинициализированную в GCC/Clang?

Псевдокод ...

{ 
    int *array; 
    array = some_alloc(); 
    b = array[0]; 
    some_free(array); 
    TAINT_MACRO(array); 

    /* the compiler should raise an uninitialized warning here */ 
    b = array[0]; 
} 

Вот один пример одного из способов испортить переменный, но GCC поднимает предупреждение, когда «а» присваиваются неинициализированный вар, а затем второе использование «а».

{ 
    int a = 10; 
    printf("first %d\n", a); 
    do { 
     int b; 
     a = b; 
    } while(0); 
    printf("second %d\n", a); 
} 

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

#define TAINT_MACRO_BEGIN(array) (void)(array); { void **array; (void)array; 
#define TAINT_MACRO_END(array) } (void)(array); 
{ 
    int *array; 
    array = some_alloc(); 
    b = array[0]; 
    some_free(array); 
    TAINT_MACRO_BEGIN(array); 

    /* the compiler should raise an uninitialized warning here */ 
    b = array[0]; 
    TAINT_MACRO_END(array); 
} 

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

Я знаю, что есть статические шашки, и я их использую, но Im ищет что-то такое, что может дать предупреждение во время компиляции и без ложных срабатываний, которые, я считаю, возможны в этом случае и могут избежать определенного класса ошибок ,

+0

Что делать, если указатель был освобожден в одной единицы перевода, а затем использован в другом? Компилятор не может это понять. – Collin

+0

'some_index' не объявлен в вашем первом примере. Я ожидаю, что весь компилятор будет жаловаться на это, а не на инициализацию. – chux

+0

Не знаете, почему вам не нравится предупреждение в вашем втором примере в 'a = b;' Компилятор, который также жаловался на последующие плохие использования ('printf (« second% d \ n », a);')) было бы подробным , Первое предупреждение будет достаточным для большей части отладки. – chux

ответ

1

Я бы пошел наоборот и обернул макросы вокруг распределений и свободных функций. Это то, что я имею в виду:

#ifdef O_TAINT 
volatile int taint_me; 
#define TAINT(x, m) \ 
    if (taint_me) { goto taint_end_##x; } else {} x = m 
#define free(x) free(x); taint_end_##x: (void)0 
#else 
#define TAINT(x, m) x = m 
#endif 

Итак, ваш пример будет выглядеть следующим образом:

int *array; 
int b; 

TAINT(array, malloc(sizeof(int))); 
b = array[0]; 
printf("%d\n", b); 
free(array); 

/* the compiler should raise an uninitialized warning here */ 
b = array[0]; 
printf("%d\n", b); 

Это не является совершенным. Может быть только один вызов free() за tainted переменную, потому что метка goto привязана к имени переменной. Если скачок пропускает другие инициализации, вы можете получить другие ложные срабатывания. Он не работает, если распределение происходит в одной функции, а память освобождается в другой функции.

Но, это обеспечивает поведение, которое вы попросили для своего примера. При компиляции обычно никаких предупреждений не появляется. Если скомпилировано с -DO_TAINT, во втором назначении появится предупреждение b.


я работать довольно общее решение, но оно включает в себя брекет всю функцию с начальными/конечными макросов, и полагается на расширение typeof оператора GCC. Решение заканчивает тем, как это:

void foo (int *array, char *buf) 
{ 
    TAINT_BEGIN2(array, buf); 
    int b; 

    puts(buf); 
    b = array[0]; 
    printf("%d\n", b); 

    free(array); 
    free(buf); 

    /* the compiler should raise an uninitialized warning here */ 
    puts(buf); 
    b = array[0]; 
    printf("%d\n", b); 

    TAINT_END; 
} 

Здесь TAINT_BEGIN2 используется для объявления два функциональных параметров, которые будут получать лечение заразы. К сожалению, макросы являются своего рода беспорядок, но легко расширить:

#ifdef O_TAINT 
volatile int taint_me; 
#define TAINT(x, m) \ 
    if (taint_me) { goto taint_end_##x; } else {} x = m 
#define TAINT1(x) \ 
    if (taint_me) { goto taint_end_##x; } else {} x = x##_taint 
#define TAINT_BEGIN(v1) \ 
    typeof(v1) v1##_taint = v1; do { \ 
    typeof(v1##_taint) v1; TAINT1(v1) 
#define TAINT_BEGIN2(v1, ...) \ 
    typeof(v1) v1##_taint = v1; TAINT_BEGIN(__VA_ARGS__); \ 
    typeof(v1##_taint) v1; TAINT1(v1) 
#define TAINT_BEGIN3(v1, ...) \ 
    typeof(v1) v1##_taint = v1; TAINT_BEGIN2(__VA_ARGS__); \ 
    typeof(v1##_taint) v1; TAINT1(v1) 
#define TAINT_END } while(0) 
#define free(x) free(x); taint_end_##x: (void)0 
#else 
#define TAINT_BEGIN(x) (void)0 
#define TAINT_BEGIN2(...) (void)0 
#define TAINT_BEGIN3(...) (void)0 
#define TAINT_END (void)0 
#define TAINT1(x) (void)0 
#define TAINT(x, m) x = m 
#endif 
+0

относительно вашего решения, похоже, что на самом деле он использует 'taint_me' перед его инициализацией ?, или вы бы определили тень для него в другом месте? – ideasman42

+0

@ ideasman42: 'taint_me' является глобальным, поэтому он инициализируется равным 0. Но поскольку он объявлен volatile, компилятор не знает, все равно 0 или нет во время компиляции. – jxh

+0

Что касается использования макросов начала и конца, я уверен, что это может быть сделано для работы, проблема в том, что это слишком разрушительно, а не что-то, что я мог бы реализовать для своих проектов codebase - изменил бы 100 (возможно, 1000) строк, а IMHO не читать очень приятно. Я думаю, было бы сложно включить повторное назначение внутри блока (возможно, с помощью typeof() и макрома магии, я думаю). Если бы это можно было сделать в одном блоке, его можно было бы обернуть в макрос, который обертывает вызов функции (например, бесплатный), который затем можно использовать без необходимости перебирать все исходные файлы и редактировать их. – ideasman42

3

Я послал ответ на список GCC, но так как я использую SO первый сам ...

В современном C и C++ , Я ожидал бы, что программисты будут использовать ограниченную область видимости переменной для управления этим видом экспозиции.

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

int x = 1; // initialized 
int y;  // uninitialized 

x = y;  // use of uninitialized value 'y' 

y = 2;  // no longer uninitialized 
x = y;  // fine 

y = ((__attr__ uninitialized))0; // tell gcc it's uninitialized again 

x = y; // warn here please. 

Если да, то я хотел бы использовать дополнительные области в С99 (или более поздней версии) или C++ (довольно уверен, что это было «объявить в точке использования», так как, по крайней мере ARM в 1993 году ...):

int x = 1; // initialized 

{ 
    int y; // uninitialized 
    x = y; // warn here 
    y = 2; // ok, now it's initialized 
    x = y; // fine, no warning 
} 

{ 
    int y; // uninitialized again! 
    x = y; // warns here 
} 

Лишние прицелов немного отпугнуть, но я очень привык к ним в C++ (от интенсивного использования методов RAII.)

Поскольку есть ответ на это в большинстве языков, я не Думаю, стоит добавить к компилятор.

Глядя на ваш пример, вы обеспокоены массивом. Это должно быть работать с дополнительными областями, и не должно быть дополнительных затрат , так как весь фрейм стека распределяется по функции записи (по крайней мере, SFAIK).

+0

Я иногда делаю это, когда куски кода локально можно разбить на блоки, и каждый из них имеет свои собственные вары, иногда это имеет смысл. Однако с большими функциями с большим количеством вложенных блоков я бы предпочел не вызывать больше отступов, добавляя блоки только из-за вероятности использования переменной, когда этого не должно быть. В основном отступы больших блоков существующего кода довольно разрушительны (беспорядок с историей фиксации), и в большинстве случаев Id говорит, что компромисс не стоит. С другой стороны, одна строка, чтобы испортить переменную, не вызывает слишком много шума, поэтому ИМХО ее приемлемо. – ideasman42

+0

Я понимаю ваши возражения, но я искренне верю, что вам будет лучше в долгосрочной перспективе, если вы перестроете свой код, чтобы использовать больше областей - будь то добавление дополнительных областей к существующим функциям или разделение ваших функций на более мелкие. Если вы используете ограничения по длине и заканчиваете боковую комнату для отступов, это оба намека на то, что вам нужно учитывать эту функцию в более мелких. Наконец, вы, по сути, просите компилятор обрабатывать переменную «как новый»; фактически сделав его «новым», ИМХО, будет менее запутанным. Удачи! – AnthonyFoiani

+0

Это хороший ответ. На самом деле проблему в вопросе можно лучше избегать, не переустанавливая переменные в пределах одной области, которая является: а) запахом кода и b) * очень плохим * запахом, если переменные могут стать недействительными на полпути через их продолжительность жизни. Если это может произойти, то, что у вас есть *, есть две переменные, так почему бы не сделать это явным? – Leushenko

2

Based on an answer to a different question, вы можете использовать setjmp и longjmp, чтобы изменить локальную переменную с неопределенным значением.

#define TAINT(x)        \ 
     do {         \ 
      static jmp_buf jb;    \ 
      if (setjmp(jb) == 0) {   \ 
       memset(&x, '\0', sizeof(x)); \ 
       longjmp(jb, 1);    \ 
      }        \ 
     } while (0) 

Если x является локальным переменным, этим значение будет неопределенным в строках коды после TAINT применяются к нему. Это происходит из-С.11 § 7.13.2 ¶ 3 (курсив мой):

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

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