Обход переполнения без знака, обнаруженного CBMC
CBMC обнаруживает возможное переполнение без знака в следующих строках:
l = (t + *b)&(0xffffffffL);
c += (l < t);
Я согласен, что существует вероятность переполнения в первой строке, но я забочусь о переносе в следующей строке, который CBMC не в состоянии рассмотреть. Если в случае переполнения я устанавливаю перенос 1. Таким образом, поскольку я знаю об этом, об этом и о том, как я хочу, чтобы мой код работал, я хотел бы перейти к процессу проверки. Итак, как же я говорю CBMC пропустить эту ошибку и двигаться дальше?
1 ответ
TL;DR Это зависит от того, каковы действительные типы переменных. Во всех случаях CBMC обнаруживает фактическую ошибку, которая может привести к неопределенному поведению. Это означает, что вы должны исправить свой код, а не отключить сообщение в CBMC.
Полный ответ:
В целом: насколько мне известно, CBMC не позволяет исключать определенные свойства (с другой стороны, вы можете проверить только одно отдельное свойство, используя --property
флаг). Если вы хотите получить официальный ответ / мнение или сделать запрос на добавление новых функций, я бы рекомендовал размещать их в группе поддержки CProver.
(Конечно, можно использовать __CPROVER_assume
чтобы CBMC исключил следы, приводящие к ошибке, но это было бы очень, очень, очень плохой идеей, поскольку это может сделать другие проблемы недоступными.)
Вариант 1: я предполагаю, что ваш код выглядит примерно так (в связи с этим: пожалуйста, опубликуйте отдельные примеры и объясните точно, в чем проблема, эти вещи трудно угадать)
long nondet_long(void);
void main(void) {
long l = 0;
int c = 0;
long t = nondet_long();
long s = nondet_long();
long *b = &s;
l = (t + *b) & (0xffffffffL);
c += (l < t);
}
и ты бежишь
cbmc --signed-overflow-check test.c
дать вывод, похожий на следующий?
CBMC версия 5.1 64-битные макросы x86_64 Parsing test.c Преобразование теста проверки типа Генерация программы GOTO Добавление библиотеки CPROVER Функция Удаление указателя Частичное встраивание Общие инструменты Инструментарий Запуск ограниченной модели Проверка размера выражения программы: 41 шаг, простое срезание удалено 3 назначения Сгенерировано 2 VCC(s), 2 осталось после упрощения Передача задачи на пропозициональное редукцию преобразование SSA Выполнение пропозиционального редукции Постобработка Решение с помощью MiniSAT 2.2.0 с упрощающими переменными 792, 2302 предложения Проверка SAT: отрицательное утверждение является SATISFIABLE, т. е. не содержит процедуру принятия решения во время выполнения: 0.006s Трассировка ошибки построения Контрпример: Состояние 17 файла test.c строка 4 функции основной поток 0 ---------------------------------------------------- l=0 (0000000000000000000000000000000000000000000000000000000000000000000000000000) Файл состояния 18 test.c строка 4, функция основной поток 0 ---------------------------------------------------- l=0 (0000000000000000000000000000000000000000000000000000000000000000) Состояние 19 файла test.c строка 5 функции main thread 0 ---------------------------------------------------- c=0 (00000000000000000000000000000000) Файл состояния 20 test.c строка 5, функция основной поток 0 ---------------- ------------------------------------ c = 0 (00000000000000000000000000000000) Состояние 21 file test.c строка 6 функция основного потока 0 ---------------------------------------------------- t=0 (0000000000000000000000000000000000000000000000000000000000000000000000000000) Файл состояния 22 test.c строка 6, функция основной поток 0 ---------------------------------------------------- t=-9223372036854775808 (100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) Состояние 23 файл test.c строка 7, функция основной поток 0 ---------------------------------------------------- s=0 (000000000000000000000000000000000000000000000000000000000000000000000000000000) Файл состояния 24 test.c строка 7, функция основной поток 0 ---------------------------------------------------- s=-9223372036854775807 (1000000000000000000000000000000000000000000000000000000000000001) Состояние 25 файл test.c строка 8 функция основной поток 0 ---------------------------------------------------- b=((long int *)NULL) (000000000000000000000000000000000000000000000000000000000000000000000000000000) Файл состояния 26 test.c строка 8 функция основной поток 0 ---------------------------------------------------- b=&s!0@1 (0000001000000000000000000000000000000000000000000000000000000000) Нарушенное свойство: файл test.c Строка 10 Функция основного арифметического переполнения при знаменном + в t + *b! Переполнении ("+", знаковый длинный int, t, *b) СБОЙ ПРОВЕРКИ
Я не думаю, что вы должны отключить эту проверку свойства, даже если вы могли бы. Причиной этого, как вы говорите, является то, что это дополнение может переполниться, и целочисленное переполнение является неопределенным поведением в C, или, как этот ответ на вопрос, как проверить целочисленное переполнение в C? красиво говорит:
[O] После того, как вы выполнили x + y, если он переполнен, вы уже в рукаве. Слишком поздно делать какие-либо проверки - ваша программа могла уже потерпеть крах. Думайте об этом, как о проверке деления на ноль - если вы ждете, пока разделение будет выполнено, проверить, уже слишком поздно.
См. Также Целочисленное переполнение и неопределенное поведение и Насколько губительным является целочисленное переполнение в C++?,
Таким образом, это реальная ошибка, и у CBMC есть веская причина рассказать вам об этом. Что вы на самом деле должны сделать, так это адаптировать свой код, чтобы избежать возможных переполнений! Ответ, упомянутый выше, предполагает что-то вроде (не забудьте включить limits.h
):
if ((*b > 0 && t > LONG_MAX - *b)
|| (*b < 0 && LONG_MIN < *b && t < LONG_MIN - *b)
|| (*b==LONG_MIN && t < 0))
{
/* Overflow will occur, need to do maths in a more elaborate, but safe way! */
/* ... */
}
else
{
/* No overflow, addition is safe! */
l = (t + *b) & (0xffffffffL);
/* ... */
}
Вариант 2: Здесь, я предполагаю, ваш код выглядит примерно так:
unsigned int nondet_uint(void);
void main(void) {
unsigned int l = 0;
unsigned int c = 0;
unsigned int t = nondet_uint();
unsigned int s = nondet_uint();
unsigned int *b = &s;
l = (t + *b) & (0xffffffffL);
c += (l < t);
}
и ты бежишь
cbmc --unsigned-overflow-check test.c
дать вывод, похожий на следующий?
CBMC версия 5.1 64-битные макросы x86_64 Разбор test.c преобразование Тип проверки Генерация программы GOTO Добавление библиотеки CPROVER Удаление указателя функции Частичное встраивание Универсальный приборостроение Начало проверки ограниченной модели размер выражения программы: 42 шага простая нарезка убрано 3 задания Создано 3 VCC, 3 осталось после упрощения Передача задачи в пропозициональную редукцию преобразование SSA Запуск пропозиционального сокращения Постобработка Решение с помощью MiniSAT 2.2.0 с упрощением 519 переменных, 1306 предложений SAT checker: отрицаемая заявка является УДОВЛЕТВОРЕННОЙ, т. Е. Не имеет места Процедура принятия решения во время выполнения: 0,01 с Трассировка ошибок здания контрпример: Состояние 17 файла test.c строка 4 функции основного потока 0 --------------------------------------------------- l=0 (00000000000000000000000000000000) Состояние 18 файла test.c строка 4 функции основной поток 0 --------------------------------------------------- l=0 (00000000000000000000000000000000) Состояние 19 файла test.c строка 5 функции основного потока 0 -------------------------------------------------- - с = 0 (00000000000000000000000000000000) Состояние 20 файла test.c Строка 5 Функция основного потока 0 -------------------------------------------------- - с = 0 (00000000000000000000000000000000) Состояние 21 файла test.c Строка 6 Функция основного потока 0 --------------------------------------------------- t=0 (00000000000000000000000000000000) Состояние 22 файла test.c строка 6 функция основной поток 0 --------------------------------------------------- t=4187126263 (11111001100100100111100111110111) Состояние 23 файла test.c строка 7 функция основной поток 0 --------------------------------------------------- s=0 (00000000000000000000000000000000) Состояние 24 файла test.c строка 7 функция основной поток 0 --------------------------------------------------- s=3329066504 (11000110011011011000011000001000) Состояние 25 файла test.c строка 8 функция основной поток 0 --------------------------------------------------- b=((unsigned int *)NULL) (000000000000000000000000000000000000000000000000000000000000000000000000000000) Состояние 26 файла test.c строка 8 функции основного потока 0 -------------------------------------------------- - b = & s! 0 @ 1 (0000001000000000000000000000000000000000000000000000000000000000000000000000) Нарушенное свойство: файл test.c строка 10 функция main арифметическое переполнение на unsigned + в t + *b! переполнение ("+", без знака int, t, *b) ПРОВЕРКА НЕ ПРОВЕРЕНА
Опять же, это реальная ошибка, и у CBMC есть веская причина рассказать вам об этом. Этот может быть исправлен
l = ((unsigned long)t + (unsigned long)*b) & (0xffffffffL);
c += (l < t);
который дает
CBMC версия 5.1 64-битные макросы x86_64 Разбор test.c преобразование Тип проверки Генерация программы GOTO Добавление библиотеки CPROVER Удаление указателя функции Частичное встраивание Универсальный приборостроение Начало проверки ограниченной модели размер выражения программы: 42 шага простая нарезка убрано 3 задания Создано 3 VCC, 3 осталось после упрощения Передача задачи в пропозициональную редукцию преобразование SSA Запуск пропозиционального сокращения Постобработка Решение с помощью MiniSAT 2.2.0 с упрощением 542 переменных, 1561 предложение Проверка SAT несовместима: отклоненное требование НЕ УДОВЛЕТВОРЕНО, т.е. Процедура принятия решения во время выполнения: 0,002 с ПРОВЕРКА УСПЕШНАЯ
Вариант 3: Если все как и в предыдущем случае, но у вас есть signed int
вместо unsigned int
все становится немного сложнее. Здесь, если вы используете (написано несколько более сложным способом, чтобы лучше увидеть, что происходит)
int nondet_int(void);
void main(void) {
int l = 0;
int c = 0;
int t = nondet_int();
int s = nondet_int();
long longt = (long)t;
long longs = (long)s;
long temp1 = longt + longs;
long temp2 = temp1 & (0xffffffffL);
l = temp2;
c += (l < t);
}
и беги
cbmc --signed-overflow-check test.c
ты получишь
CBMC версия 5.1 64-битные макросы x86_64 Разбор test.c преобразование Тип проверки Генерация программы GOTO Добавление библиотеки CPROVER Удаление указателя функции Частичное встраивание Универсальный приборостроение Начало проверки ограниченной модели размер выражения программы: 48 шагов простая нарезка убрано 3 задания Создано 3 VCC, 3 осталось после упрощения Передача задачи в пропозициональную редукцию преобразование SSA Запуск пропозиционального сокращения Постобработка Решение с помощью MiniSAT 2.2.0 с упрощением 872 переменных, 2430 предложений SAT checker: отрицаемая заявка является УДОВЛЕТВОРЕННОЙ, т. Е. Не имеет места Процедура принятия решения во время выполнения: 0,008 с Трассировка ошибок здания контрпример: Состояние 17 файла test.c строка 4 функции основного потока 0 --------------------------------------------------- l=0 (00000000000000000000000000000000) Состояние 18 файла test.c строка 4 функции основной поток 0 --------------------------------------------------- l=0 (00000000000000000000000000000000) Состояние 19 файла test.c строка 5 функции основного потока 0 -------------------------------------------------- - с = 0 (00000000000000000000000000000000) Состояние 20 файла test.c Строка 5 Функция основного потока 0 -------------------------------------------------- - с = 0 (00000000000000000000000000000000) Состояние 21 файла test.c Строка 6 Функция основного потока 0 --------------------------------------------------- t=0 (00000000000000000000000000000000) Состояние 22 файла test.c строка 6 функция основной поток 0 --------------------------------------------------- t=-2147483648 (10000000000000000000000000000000) Состояние 23 файла test.c строка 7 функция основной поток 0 --------------------------------------------------- s=0 (00000000000000000000000000000000) Состояние 24 файла test.c строка 7 функция основной поток 0 --------------------------------------------------- s=1 (00000000000000000000000000000001) Состояние 25 файла test.c строка 9 функции основного потока 0 --------------------------------------------------- longt=0 (0000000000000000000000000000000000000000000000000000000000000000000000000000) Состояние 26 файла test.c строка 9 функции основного потока 0 --------------------------------------------------- longt=-2147483648 (1111111111111111111111111111111110000000000000000000000000000000) Состояние 27 файла test.c строка 10 функции основного потока 0 --------------------------------------------------- longs=0 (0000000000000000000000000000000000000000000000000000000000000000000000000000) Состояние 28 файла test.c строка 10 функции основного потока 0 --------------------------------------------------- longs=1 (0000000000000000000000000000000000000000000000000000000000000000000000000001) Состояние 29 файла test.c строка 11 функции основного потока 0 --------------------------------------------------- temp1=0 (0000000000000000000000000000000000000000000000000000000000000000000000000000) Состояние 31 файла test.c строка 11 функции основного потока 0 --------------------------------------------------- temp1=-2147483647 (1111111111111111111111111111111110000000000000000000000000000001) Состояние 32 файла test.c строка 12 функция основной поток 0 --------------------------------------------------- temp2=0 (0000000000000000000000000000000000000000000000000000000000000000000000000000) Состояние 33 файла test.c строка 12 функции основного потока 0 --------------------------------------------------- temp2=2147483649 (0000000000000000000000000000000010000000000000000000000000000001) Нарушенное свойство: файл test.c строка 14 функция main арифметическое переполнение при преобразовании со знаком в типе (подписано int)temp2 temp2 = -2147483648l ПРОВЕРКА НЕ ПРОВЕРЕНА
Или, написано более кратко, если у вас есть
t == -2147483648 (0b10000000000000000000000000000000)
s == 1 (0b00000000000000000000000000000001)
затем
temp2 == 2147483649 (0b0000000000000000000000000000000010000000000000000000000000000001)
и попытка превратить это в signed int
это проблема, потому что она выходит за пределы допустимого диапазона (см. также. Поддерживает ли приведение между подписанным и беззнаковым int сохранение точного битового шаблона переменной в памяти?).
Как видите, этот контрпример также является реальной ошибкой, и опять же, CBMC прав, когда рассказывает вам об этом. В частности, это означает, что ваша маскировка / математика не работает должным образом (ваша маскировка превращает отрицательное число в положительное число, которое выходит за пределы), и вам нужно исправить свой код, чтобы результат находился в необходимом диапазоне. (Вероятно, стоит тщательно подумать о том, что вы действительно хотите сделать, чтобы убедиться, что вы получите правильные результаты.)