Как стать автором
Обновить

Комментарии 40

do {
 ...
 } while (false)

часто использовалось в макросах, что бы после их вызова надо было написать одну ";".
А это требовалось, потому что некоторые кодингстайлы запрещали использовать "{}" если блок состоял из одного оператора, а если этот оператор был макросом, который раскрывался в блок, то лишняя ";" приводила к неожиданностям.
Возможно, эта функция переделана из макроса, или автор просто макросами увлекался…

Вот только код в текущем виде допускает только последовательности вида < id >. Если допустить, что это правильное поведение — зачем тут вообще проверка на запятую?!

Легаси или (устаревшая) надежда на расширение функционала проекта.
НЛО прилетело и опубликовало эту надпись здесь
LLVM (Low Level Virtual Machine)

LLVM уже давно так не расшифровывается: http://lists.llvm.org/pipermail/llvm-dev/2011-December/046445.html
if (X != '=' || X != 'S' || X != 'I')

Переменная X всегда будет чему-то не равна.

А как же Х = true?
Не понял вопрос.
Возможно, Clang-tidy научат распознавать первую проблему.
Пока научат, мы ещё пяток диагностик добавим. Покупайте наших слонов!
Этот код не имеет смысла, так как если память не удастся выделить, должно быть сгенерировано исключение std::bad_alloc.

В кодобазе LLVM же запрещено использовать RTTI и исключения. Так что можно смело в статью добавить «но к LLVM это не относится, ибо тут так заведено.» и ссылку на стандарт кодирования llvm.

А вообще полезная проверка. Почивший в бозе cppcat нашел несколько таких в моих хобби-проектах и мне было даже немного стыдно, но потом прошло.
Неверно, чтобы new не выдавал exception нужно явно указывать std::nothrow
Иначе всегда будет будет вываливаться в случае фейла exception. http://www.cplusplus.com/reference/new/operator%20new/

http://www.cplusplus.com/reference/new/nothrow/

А то что по ссылке — это лишь кодинг стайл и к данной проблеме не относится.
В случае llvm это не просто кодинг стайл, это принципиальное архитектурное решение, выбранное разработчиками проекта. Достаточно нетрудно увидеть где в AddLLVM.cmake появляется -fno-exceptions. Ну и для полноты картины можно заглянуть в ту же libc++ реализацию operator new и увидеть как легко и непринужденно эта самая поддержка исключений отключается с помощью магии макросов.

По ссылке — объяснение почему конкретно llvm не использует исключения. Оную поддержку часто отключают и при разработке встраиваемых решений. Раньше отключали и на консолях, теперь это менее актуально (хотя если мне не изменяет память даже в последнем SCE SDK llvm тулчейн форсирует -fno-exceptions и библиотеки собраны без поддержки исключений).

Что касается вываливаний: нет throw — нет и abort().
Вы наверно вообще не понимаете разницы между код-стайлом и кроссплатформенной реализацией и стандартом С++, никто не может запретить выбрасывать exception на new операторе до тех пор пока его не переопределили на данной платформе. Как по мне llvm это кросс-платформменый инструмент потому запрещать такие вещи просто нереально.
Ответ ниже. Промахнулся на телефоне мимо ссылки.
До того, как мы углубимся в дебри разборок в том, кто и что понимает, давайте рассмотрим две ситуации:
1. llvm собранный без поддержки исключений, и libc++/libstd++, собранные с их поддержкой. В обсуждаемом месте будет abort(). Некрасиво? Пожалуй так, но это ожидаемое и задокументированное поведение для проекта.
2. Оба без поддержки. Ситуация невыделения памяти как-то относительно корректно обработана.

Вы утверждаете, что 1) недопустимо для проекта, не спросив в общем-то их мнения. Я так понимаю, что и Страустроп с его стародавним демаршем в отношении исключений в jsf++ не аргумент, если необходимо гнаться за мифом о кросс-платформенности.

Тем не менее отловить исключение там невозможно, т.к. llvm собран без соответствующей поддержки. Если убрать условие — все равно будет abort() в 1), но теперь ещё и 2) будет обрабатываться некорректно. Предупреждение показывать на данном примере бессмысленно, т.к. архитектурное решение (отказ от использования исключений) не позволяет устранить потенциальный аварийный выход. Более того, фанатичное следование предложенному решению (убрать условие), уничтожает обработку ошибки. То есть программа станет не лучше, а хуже.

Об этом был мой комментарий, который был направлен на улучшение статьи путем добавления одной маленькой ремарки. Статья не о стандартах С++, а о статическом анализаторе и как он помогает находить дефекты. Обсуждаемое — это false positive в силу специфики проекта. То есть по факту показывает слабые стороны статического анализа, что на мой взгляд не входило в задачи статьи.

Ну а теперь давайте продолжим кидаться какашками и гадать на кириллице, кто и как видит разницу между чем.

А еще можнл использовпть nothrow-версию оператора new — и тогда будет работать как 1, так и 2.

Можно, об этом в общем-то написано примечание в помощи к предупреждению (а лучше бы в самом предупреждении было написано, ИМХО).

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

Кроме того, обычно nothrow вариант напрямую вызывает «throw» вариант, то есть фактически является оберткой вокруг кидающего исключения operator new. По сути это добавляет лишний вызов, если стандартная библиотека собрана без поддержки исключений.

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

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


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

Если этот код используется в составе библиотеки — то разработчик библиотеки смотрит на описание метода и видит, что при ошибке метод вернет nullptr. И он обязан этот самый nullptr обработать! А внезапное выпадение bad_alloc из метода, который "не использует исключений" — это нарушение спецификации.

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

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

Если же речь идёт уже безотносительно к обсуждаемому проекту, то бишь в общем и целом о полезности раскрутки стека при исключениях: кто ж спорит с тем, что базово оно полезно за исключением ряда областей применения ПО, а при правильном применении ещё более полезно?

то разработчик библиотеки смотрит на описание метода и видит, что при ошибке метод вернет nullptr.

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

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

В функции LLVMCreateDisasmCPUFeatures есть несколько локальных unique_ptr, которые создаются до вызова последнего оператора new.


Исключение bad_alloc, выкинутое new, может быть поймано внешним кодом — в таком случае оно не приведет к остановке программы.


Если Когда оно будет поймано внешним кодом — ресурсы, удерживаемые локальными unique_ptr, утекут.

> Когда оно будет поймано внешним кодом — ресурсы, удерживаемые локальными unique_ptr, утекут.
Изложите мысль подробнее, пожалуйста. С утра прочитаю, на ночь глядя тяжело понять, почему вы считаете, что деаллокаторы unique_ptr не будут вызваны при раскрутке стека.

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

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

Чтобы исключение было поймано внешним кодом в А, нужно чтобы и B было собрано с соответствующей поддержкой. Но в таком случае, никакой утечки ресурсов, которыми владеют unique_ptr, не будет.

Почему приложение будет закрыто? Как стандартная библиотека вообще увидит что ее вызвал код без поддержки исключений?


Кстати, внезапное закрытие приложения — это, по-вашему, правильное поведение?

Почему приложение будет закрыто?

Потому что такова общепринятая реализация: вызов terminate() и в нем стандартным обработчиком является abort().

Как стандартная библиотека вообще увидит что ее вызвал код без поддержки исключений?

Не верьте мне на слово, просто возьмите наскоро написанный код да и проверьте: http://pastebin.com/FtigcuwJ

Подробности про почему ищите по ключевым словам __cxa_throw и Itanium C++ ABI.
Суть в том, что если происходит ошибка при развертывании стека, то вызовется terminate().

Кстати, внезапное закрытие приложения — это, по-вашему, правильное поведение?

Зависит от приложения и обстоятельств.
В случае с llvm — это ожидаемое поведение, т.к. исключения приходить в него не должны, так задумано, документировано и прочая. Кроме того, нет ничего более действенного для отладки приложений, чем краши, да ещё и с крашдампами. Культура stop to fix сразу начинает развиваться сама собой.

Да с чего закрытие ожидаемое-то — если по документации при ошибке возвращается nullptr?

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

Если то, что про документацию написано, не удовлетворяет, то вот взгляд с другого угла: с точки зрения программы исчерпание памяти — это системная проблема.
Когда приложение крашится из-за проблемы в оборудовании, вас же не волнует почему системный вызов open() не вернул ничего, и приложение скрашилось? Теперь распространите это на случай обсуждаемой функции.

Если и этот угол не нравится, то что ж, се ля ви. Это, впрочем, никак не влияет на факт того, что исключения отключены в llvm, это никак ему не мешает, а использованная конструкция new в связке с проверкой на nullptr вполне себе имеет смысл.
Когда приложение крашится из-за проблемы в оборудовании, вас же не волнует почему системный вызов open() не вернул ничего, и приложение скрашилось?

Вообще-то волнует. У меня там были запись об ошибке в сетевой лог, с последующим email админу. А теперь придется в дампе ковыряться на предмет причин вылета. Это при условии что дамп вообще смог сохраниться на сбойном диске.

Так вы ругаетесь на документацию, в которой не написано про краши при вызове open() или нет? Или на то, что драйвер, например, в uninterruptable sleep и ваше приложение невозможно ни убить, ни отладить по этой причине?

Думается мне, нет. Вот так же и с тем самым невозвращением nullptr.

То есть вы считаете, что кривые драйвера — это нормально и ничего исправлять тут не надо?..

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

Утомился писать в этой теме, да ещё и не по теме. Поэтому пойду в какую-нибудь другую.
а нет ли какой-то статистики по исправлениям? в смысле какой процент ошибок из прошлых проверок был исправлен?
Да, видим, что часто много правят после наших заметок. Но специально отслеживать сложно и не интересно.
в смысле у вас нет какого-то автоматического сравнения отчетов об ошибках?
Это будет очень сложная система мониторинга различных проектов. Разрабатывать её нет смысла.
В статье стоит тег информационная безопасность. Про безопасность ничего не нашел. Это опечатка или что?
На мой взгляд ошибки в компиляторе и возможность находить такие ошибки другими инструментами, имеют самое прямое отношение к информационной безопасности.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий