Реализация С++, которая определяет поведение undefined?

Огромное количество операций на С++ приводит к поведению undefined, где спецификация полностью не соответствует тому, что должно быть поведением программы, и допускает, чтобы что-либо произошло. Из-за этого существуют случаи, когда люди имеют код, который компилируется в режиме отладки, но не в режиме выпуска, или работает до тех пор, пока не будет сделано какое-либо несвязанное изменение, или работает на одном компьютере, но не на другом, и т.д.

Мой вопрос в том, есть ли утилита, которая смотрит на выполнение кода С++ и флаги всех экземпляров, где программа вызывает поведение undefined. Хотя хорошо, что у нас есть инструменты, такие как valgrind и проверенные реализации STL, это не так сильно, как то, о чем я думаю - valgrind может иметь ложные негативы, если вы удаляете память, которую вы все еще выделили, например, и проверили реализации STL не будет удаляться с помощью указателя базового класса.

Имеется ли этот инструмент? Или было бы даже полезно, если бы он вообще лежал?

РЕДАКТИРОВАТЬ. Я знаю, что в общем случае нельзя статически проверять, может ли программа на С++ выполнять что-то, что имеет поведение undefined. Тем не менее, можно определить, выполнялось ли конкретное выполнение С++ поведения undefined. Одним из способов сделать это было бы сделать интерпретатор С++, который будет проходить через код в соответствии с определениями, указанными в спецификации, в каждой точке, определяющей, имеет ли код поведение undefined. Это не будет обнаруживать поведение undefined, которое не происходит при выполнении конкретной программы, но оно найдет поведение undefined, которое действительно проявляется в программе. Это связано с тем, как распознать Тьюринга, чтобы определить, принимает ли ТМ некоторый ввод, даже если он все еще неразрешимый вообще.

Спасибо!

Ответы

Ответ 1

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

Предположительно, такая реализация будет почти интерпретатором С++ или, по крайней мере, компилятором для чего-то большего, чем Lisp или Java. Для каждого указателя необходимо сохранить дополнительные данные, чтобы убедиться, что вы не выполнили арифметику вне массива или не разыменовали то, что уже было освобождено или что-то еще.

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

int *p = new int;
delete p;
int *q = new int;

if (p == q)
    *p = 17;

Является ли поведение *p = 17 undefined? С одной стороны, он разыгрывает p после того, как он был освобожден. С другой стороны, разыменование q прекрасное и p == q...

Но это не совсем так. Дело в том, что независимо от того, оценивает ли значение if значение true, зависит от деталей реализации кучи, которая может варьироваться от реализации к реализации. Поэтому замените *p = 17 на некоторые действительные действия undefined, и у вас есть программа, которая может очень сильно взорваться на обычном компиляторе, но отлично работает на вашем гипотетическом "детекторе UB". (Типичная реализация на С++ будет использовать свободный список LIFO, поэтому у указателей есть хорошие шансы быть равными. Гипотетический "детектор UB" может больше походить на сборник мусора, чтобы обнаружить проблемы без использования.)

Другими словами, существование просто определенного поведения не позволяет писать "детектор UB", который работает для всех программ, я подозреваю.

Тем не менее, очень интересным будет проект создания "компилятора С++". Дайте мне знать, если вы хотите его запустить.: -)

Ответ 2

John Regehr в Поиск Undefined Ошибки поведения путем поиска Dead Code указывает инструмент STACK, и я цитирую с сайта (выделение мое):

Оптимизационно-неустойчивый код (короткий код нестабильного кода) - это новый класс программных ошибок: код, который неожиданно устраняется оптимизацией компилятора из-за поведения Undefined в программе. Нестабильный код присутствует во многих системах, включая ядро ​​Linux и сервер базы данных Postgres. Последствия нестабильного кода варьируются от неправильной функциональности до отсутствующих проверок безопасности.

STACK - статическая проверка , которая обнаруживает нестабильный код в программах на C/С++. Применение STACK для широко используемых систем выявило 160 новых ошибок, которые были подтверждены и исправлены разработчиками.

Также в С++ 11 для случая переменных constexpr и функций Undefined поведение должно быть обнаружено во время компиляции.

У нас также есть gcc ubsan:

Недавно GCC (версия 4.9) получил Undefined Behavior Sanitizer (ubsan), средство проверки времени выполнения для языков C и С++. Чтобы проверить свою программу с помощью ubsan, скомпилировать и связать программу с -fsanitize = undefined. Такие инструментальные двоичные файлы должны быть выполнены; если ubsan обнаруживает какую-либо проблему, он выдает "ошибку времени выполнения": и в большинстве случаев продолжает выполнение программы.

и Clang Static Analyzer, который включает много проверок для поведения Undefined. Например, clangs - fsanitize проверяет, что включает -fsanitize=undefined:

-fsanitize = undefined: Быстрая и совместимая проверка поведения Undefined. Включает проверки поведения Undefined, которые имеют небольшую стоимость исполнения и не влияет на расположение адресного пространства или ABI. Сюда входят все проверки, перечисленные ниже, кроме unsigned-integer-overflow.

а для C мы можем посмотреть на его статью Время для серьезного использования Undefined Поведение, в котором говорится:

[..] Я признаюсь, что лично не хватало необходимости использовать CCC или LLVM с помощью лучших доступных динамических проверок поведения Undefined: KCC и Frama-C. [...]

Вот ссылка на kcc, и я цитирую:

[...] Если вы попытаетесь запустить программу с именем Undefined (или той, для которой нам не хватает семантики), программа застрянет. Сообщение должно сказать вам, где оно застряло, и может дать подсказку о том, почему. Если вы хотите помочь расшифровать вывод или помочь понять, почему программа undefined, отправьте файл .kdump нам. [...]

и здесь ссылка на Frama-C, статья, где описано первое использование Frama-C как интерпретатора C и дополнение к статье.

Ответ 3

Используя g++

-Wall -Werror -pedantic-error

(желательно с соответствующим аргументом -std), выберет довольно много случаев U.B.


Вещи, в которые вы попадаете -Wall, включают:

-pedantic
           Выдать все предупреждения, требуемые строгими ISO C и ISO С++; отклонять            все программы, которые используют запрещенные расширения, и некоторые другие программы            которые не соответствуют ISO C и ISO С++. Для ISO C следует            версия стандарта ISO C, заданная с помощью любой опции -std.

-Winit-self (только C, С++, Objective-C и Objective-C ++)
           Предупреждать о неинициализированных переменных, которые инициализируются            самих себя. Примечание. Эта опция может использоваться только с            -Унициализированный вариант, который, в свою очередь, работает только с -O1 и            выше.

-Wuninitialized
           Предупреждать, если автоматическая переменная используется без            инициализируется или если переменная может быть сбита вызовом "setjmp".

и различные недопустимые вещи, которые вы можете сделать с помощью спецификаторов для printf и scanf семейных функций.

Ответ 4

Clang имеет набор дезинфицирующих средств, которые захватывают различные формы поведения undefined. Их конечная цель - уловить все поведение языка undefined на языке С++, но проверки на несколько сложных форм поведения undefined отсутствуют.

Для достойного набора дезинфицирующих средств попробуйте:

clang++ -fsanitize=undefined,address

-fsanitize=address проверяет использование плохих указателей (не указывая на действительную память), а -fsanitize=undefined включает набор упрощенных проверок UB (переполнение целого числа, неправильные сдвиги, неправильные указатели,...).

-fsanitize=memory (для обнаружения неинициализированных считываний памяти) и -fsanitize=thread (для обнаружения расчётов данных) также полезны, но ни одно из них не может сочетаться с -fsanitize=address и друг с другом, потому что все три имеют инвазивное воздействие на адресное пространство программы.

Ответ 5

Возможно, вы захотите прочитать SAFECode.

Это исследовательский проект Университета штата Иллинойс, цель которого указана на первой странице (см. выше):

Цель проекта SAFECode - обеспечить безопасность программы без сбора мусора и с минимальными проверками времени выполнения, используя статический анализ, когда это возможно, и проверки времени выполнения, когда это необходимо. SAFECode определяет представление кода с минимальными смысловыми ограничениями, предназначенными для обеспечения статического обеспечения безопасности, используя агрессивные методы компилятора, разработанные в этом проекте.

Что действительно интересно для меня - это устранение проверок времени выполнения, когда можно доказать, что программа статична правильно, например:

int array[N];
for (i = 0; i != N; ++i) { array[i] = 0; }

Не следует брать на себя дополнительные накладные расходы, чем обычная версия.

В более легком способе Clang есть некоторые гарантии относительно поведения undefined, насколько я помню, но я не могу понять это..

Ответ 6

Компилятор clang может распознавать поведение undefined и предупреждать о них. Вероятно, это не так полно, как вы хотите, но это определенно хороший старт.

Ответ 7

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

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

Обзор кода также может помочь выявить случаи, на которых полагается UB.

Затем вам нужно полагаться на valgrind, чтобы фиксировать оставшиеся случаи.

Ответ 8

Как и побочное наблюдение, согласно теории вычислимости, у вас не может быть программы, которая обнаруживает поведение все undefined.

У вас могут быть только инструменты, которые используют эвристику и обнаруживают некоторые конкретные случаи, которые следуют определенным шаблонам. Или вы можете в определенных случаях доказать, что программа ведет себя так, как вы хотите. Но вы вообще не можете обнаружить поведение undefined.

Edit

Если программа не заканчивается (зависает, петли навсегда) на заданном входе, то ее вывод undefined.

Если вы согласны с этим определением, то определение того, завершается ли программа, - это известная "проблема с остановкой", которая оказалась неразрешимой, т.е. не существует программы (Turing Machine, C-программа, программа на С++, Pascal программа на любом языке), который может решить эту проблему в целом.

Проще говоря: не существует программы P, которая может принимать в качестве входных данных любую программу Q и входные данные я и печатать как выход TRUE, если Q (I) завершается, или же печатать FALSE, если Q (I) не заканчивается.

Для получения дополнительной информации вы можете посмотреть http://en.wikipedia.org/wiki/Halting_problem.

Ответ 9

Undefined поведение undefined. Лучшее, что вы можете сделать, это соответствовать стандартным педантичным, как и другие, но вы не можете проверить, что такое undefined, потому что вы не знаете, что это такое. Если бы вы знали, что это такое, и стандарты указали его, это не будет undefined.

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

Ответ 10

Взгляните на PCLint, довольно неплохо обнаружив в С++ много плохих вещей.

Здесь подмножество того, что он ловит