LINUX.ORG.RU
ФорумTalks

Оптимизатор НАКАЗАЛ разработчика Clang за UB в коде

 , ,


2

6

Изложение https://qinsb.blogspot.ru/2018/03/ub-will-delete-your-null-checks.html

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

Вот код, который моделирует поведение кода из коммита (Код покоцан движком блога, вырезаны аргументы шаблонов. Я не буду пытаться их восстановить, т.к. аргумент-тип у llvm::SmallVector может быть понят из контекста, где надо, а аргумент-число — не особо важен):

struct Foo {
  llvm::SmallVector Vals;
};
struct Bar {
  Foo *getLastFoo() { return Foos.empty() ? nullptr : Foos.back(); }
  llvm::SmallVector Foos;
  void *Pad;
  void doUB();
};
void __attribute__((noinline)) Bar::doUB() {
  if (getLastFoo()->Vals.empty())
    puts("Vals empty");
  else
    puts("Vals non-empty");
}
int main() {
  Bar b;
  b.doUB();
}

В SmallVector::back стоит assert на не-пустоту вектора (!empty()). Т.к. код этих методов инлайнится (в getLastFoo) и компилятор видит, что проверка на пустоту в assert-е находится в false-ветке тернарного оператора, в условии которого та же самая проверка уже сделана, то в assert подставляется константа true и, в общем, можно считать, что assert выкинут.

Далее. Компилятор видит, что результат вызова getLastFoo в Bar::doUB() всегда разыменовывается, поэтому он не может быть нулевым указателем. Следовательно, условие тернарного оператора в getLastFoo не может быть истинным и при инлайнинге оператор безусловно заменяется на свою false-ветку (Foos.back() (в котором уже нет проверки на empty, т.к. assert был выкинут ранее)).

SmallVector устроен так, что в пустом векторе back() указывает внутрь самого SmallVector, и, короче говоря, код интерпретирует кусок памяти объекта Bar b как объект типа struct Foo и выполняет проверку Vals.empty() с непредсказуемым результатом.

Автор заканчивает тем, что радуется новому юз-кейсу для опции -fno-delete-null-checks, но по-прежнему не считает, что эту опцию стоит использовать для решения проблем ядра linux.

(видимо, имеется в виду типичный говнокод ядра

struct foo* p = bar->p;

if (!bar) {
...
}
который ядрописатели не считают багом, а винят в своих кривых руках компиляторы)

Перемещено tailgunner из development

видимо, имеется в виду типичный говнокод ядра

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

anonymous ()

Ну и? Давайте сюда постить всё из bugzilla.llvm.org?

DELIRIUM ★★★★★ ()

имеется в виду типичный говнокод ядра

А в чём именно проблема такого кода? Объясните, а то я не очень умный.

Deleted ()
Ответ на: комментарий от Deleted

Или речь про то, что компилятор считает, что раз по указателю обратились, то он «валидный», а if (ptr) - проверка на «валидность», которая в этом случае не может быть false?

Deleted ()
Ответ на: комментарий от anonymous

А как тогда такой код нормально переписать? С тем учётом, что с точки зрения конкретной платформы, (0) может быть валидным адресом. А нам нужна проверка (ptr == 0) которая проверяет именно значение, без какого-либо дополнительного смысла.

Deleted ()
Ответ на: комментарий от Deleted

С тем учётом, что с точки зрения конкретной платформы, (0) может быть валидным адресом.

Назови эту платформу.

нам нужна проверка (ptr == 0) которая проверяет именно значение, без какого-либо дополнительного смысла.

wat

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от Legioner

В DOS 0 тоже был невалидным адресом. И с каких пор DOS стала аппаратной платформой?

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Хз, вроде видел на некоторых китайских микроконтроллерах. С нулевого адреса начинается выполнение программы, по спецификации там возможно только команда jmp $addr. При jmp 0 - ресет процессора. Читать оттуда можно - ту самую первую команду jmp. То есть нулевой адрес имеет смысл, хоть и очень специфический. Но не является строго не валидным.

Deleted ()
Ответ на: комментарий от tailgunner

Разве? Там же с нулевого адреса была таблица interrupt обработчиков, которую можно менять. https://wiki.osdev.org/Interrupt_Vector_Table по нулевому адресу, соответственно, адрес обработчика прерывания divide by 0.

Legioner ★★★★★ ()
Ответ на: комментарий от tailgunner

wat

Чтобы получить специфичную для конкретного применения проверку невалидного адреса, не привязанную к фантазиям компилятора.

Deleted ()
Ответ на: комментарий от Deleted

То есть ни на одной платформе, поддерживаемой Linux, этого нет. Речь вроде бы шла о коде ядра Linux.

То есть нулевой адрес имеет смысл, хоть и очень специфический. Но не является строго не валидным.

Мде. Ладно, здесь всё понятно.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

То есть ни на одной платформе, поддерживаемой Linux, этого нет. Речь вроде бы шла о коде ядра Linux.

Ну вон выше Legioner вспомнил про interrupt vector. Он тоже с нулевого адреса начинается. На x86, на котором линукс вроде как работает.

Мде. Ладно, здесь всё понятно.

А можно нормально ответить, без царизмов?

Deleted ()
Ответ на: комментарий от Legioner

В DOS 0 тоже был невалидным адресом.

Разве?

Да. Но ты так и не ответил - с каких пор DOS стала аппаратной платформой?

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

И это, в твоем понимании, делает 0 валидным адресом в Си-программе?

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

И это, в твоем понимании, делает 0 валидным адресом в Си-программе?

То, что по нулевому адресу можно читать/писать и это имеет практический смысл.

Deleted ()
Ответ на: комментарий от Deleted

А можно нормально ответить, без царизмов?

Проблема с царем (одна из) в том, что у него в голове каша, а я вроде не давал повода так думать обо мне. Мой ответ кажется тебе «не нормальным» только потому, что ты не знаешь этого:

This International Standard specifies the form and establishes the interpretation of programs written in the C programming language

[...]

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

То есть так делать нельзя потому, что стандарт C явно запрещает нулевому указателю указывать на какой-нибудь валидный объект? И следовательно: если надо писать/читать/вызывать_функцию по нулевому адресу, то правильное решение - писать такие куски на ассемблере?

Теперь я правильно понял?

Deleted ()
Ответ на: комментарий от Deleted

То есть так делать нельзя потому, что стандарт C явно запрещает нулевому указателю указывать на какой-нибудь валидный объект?

То есть литерал 0 преобразуется в невалидное значение адреса (при этом побитовое представление указателя, получающегося из преобразования, может отличаться от «все 0»). Поэтому утверждение «с точки зрения конкретной платформы, (0) может быть валидным адресом» неверно; утверждение же «нам нужна проверка (ptr == 0) которая проверяет именно значение, без какого-либо дополнительного смысла» - вообще какая-то бессмыслица.

стандарт C явно запрещает нулевому указателю указывать на какой-нибудь валидный объект?

«Нулевой указатель» - это жаргон для невалидного указателя, в который преобразуется целый литерал 0. Во всех известных мне случаях это дает битовое значение указателя «все 0» (отсюда и «нулевой указатель»), но такое значение не гарантируется. А невалидный указатель, конечно, не может указывать ни на какой валидный объект.

если надо писать/читать/вызывать_функцию по нулевому адресу, то правильное решение - писать такие куски на ассемблере?

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

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)

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

Справедливости ради следует сказать, что эта оптимизация тоже весьма сомнительна т.к. исходит из предположений что:

- размер структуры не может быть больше N байт

- доступ к первым N байт запрещен

Да, в большинстве случаев userspace это так, но с чего бы на это расчитывать разработчикам ядра?

sergej ★★★★★ ()
Ответ на: комментарий от Deleted

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

KivApple ★★★★★ ()
Ответ на: комментарий от tailgunner

Проблема с царем (одна из) в том, что у него в голове каша

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

Ладно, мне лень с вами, убогими, о чём-то разговаривать. Что здесь, что на хабре - я убедился в одном. Все активные балаболы, которых я видел - это тотальные клоуны. Хотя нет, на хабре хоть не такие убогие, как ты, были.

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

Ну отвечу по теме:

This International Standard specifies the form and establishes the interpretation of programs written in the C programming language

Этими писаками ты можешь только подтереться. Никто ядро ни на каких «International Standard» и «C programming language» не пишет, успокойся и не позорься. Да, тут тебя не макнут куда следует за балабольство, радуйся, пока можешь.

LjubaSherif ()
Ответ на: комментарий от LjubaSherif

некомпетентное трепло, ты никогда мне ничего не скажешь и никогда мне кажу не покажешь.

Я помню, как AIv, мир его виртуальному праху, пытался тебя чему-то научить. У него не получилось - при том, что он, в отличие от меня, был настоящим преподавателем. Так что я и не стану пытаться объяснять тебе что-нибудь - ты необучаем (вероятно, по причине тупости). Тупость хорошо объяснила бы твое невежество, самоуверенность и агрессивность.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от Deleted

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

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

Поэтому логика тут проста и понятна. Хочешь сувать в компилятор колхоз убогий, который очередной вася вчера сваял на лабе - суй, но только в рамки псевдо-стандартного таргета. В нормальные не лезь. А хочешь «по стандарту» - выкидывай всё нестандартной и живи с этим, в мусорке.

LjubaSherif ()

C++ assert в GCC никогда не соответствовали ассерам в LLVM

сам многократно сталкивался,считал «фичей» ллвма

missxu ()
Ответ на: комментарий от LjubaSherif

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

тут плюс

стандарт такой какой реализован в компиляторе

missxu ()
Ответ на: комментарий от tailgunner

Я помню, как AIv, мир его виртуальному праху, пытался тебя чему-то научить.

Я поехал к этому пациенту, пил с ним чай, выполнил его условия, дал ему код. Он слился. Ничего из того, что обещал - не выполнил.

него не получилось - при том, что он, в отличие от меня, был настоящим преподавателем.

Он переоценил свои силы. Я - это не его колхозники, которых он учил за партой. Это там он что-то знает, что-то понимает. В ситуации же со мной, знаю я и понимаю я. Даже если не сейчас, но завтра.

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

Так что я и не стану пытаться объяснять тебе что-нибудь - ты необучаем (вероятно, по причине тупости). Тупость хорошо объяснила бы твое невежество, самоуверенность и агрессивность.

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

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

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

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

Меня же это не волнует. И у тебя случилась ошибка. Нельзя меня научить принятию твоей тупости за норму. Как это делают и тут, и на том же хабре. Именно этому я и не обучаем. И это правильно.

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

LjubaSherif ()
Ответ на: комментарий от LjubaSherif

А кстати что можно лучше сделать ради оптимизаций для современных процессоров вместо UB ? Например пишут что мелкософтовский hlsl компилятор всё сам инлайнит-анроллит и тд настолько, что трудно что-то ещё наоптимизировать.

anonymous ()
Ответ на: комментарий от LjubaSherif

Ты заметил, что никто не пишет тебе длинных простыней? Это потому, что всем плевать на мнение психа. А вот ты пишешь длинные унылые простыни - это потому, что ты псих, которому нужно признание.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от LjubaSherif

щелкаю вас как орешки, а вы продолжаете эту клоунаду.

Ты когда что-нибудь работающее напишешь, а?

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Ты когда что-нибудь работающее напишешь, а?

Убожество малоразвитое. Я тебя тут в дерьмо макал, на хабре в дерьмо макал, ты опять лезешь? Нахрена ты мне что-то пишешь, поехавший? Ты влюбился? Лечись.

Меня мало волнуют твои глупые потуги, твои оценки от ламерка, который даже хевлорд написать не может. Потолок которого - это 20строк дерьма за 40баксов для левой либы, рядом с которой ты мыл полы.

Повторю ещё раз, есть что-то конкретного - валяй. Нету - иди отсюда и не заспамливай меня своей ахинеей. Повторюсь, мнение обо мне от какого-то ламерка, рассуждения лсного балабола о чём-то рабочем - меня не волнуют и волновать недолжны.

Иди дальше ходи по помойкам и вещай о херне, авось кто поведётся и тебе 40баксов занесёт. Хотя не тебе, ты опять там просто моешь полы рядом. Побольше тусуйся с левыми пацанами и левыми поделками. Колхозники в это верят.

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

LjubaSherif ()
Ответ на: комментарий от LjubaSherif

Ну т.е. кроме словесного поноса в камментах ты больше ни на что не способен?

Хотя бы одну работающую программу в своей жизни написал?

eao197 ★★★★★ ()
Ответ на: комментарий от tailgunner

Ты заметил, что никто не пишет тебе длинных простыней?

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

Это потому, что всем плевать на мнение психа.

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

А вот ты пишешь длинные унылые простыни - это потому, что ты псих, которому нужно признание.

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

Признание несовместимо с конфронтацией, непопулярным мнением, матчастью и прочим. Если ты сказал клоуну «ты идиот», то клоун тебя никогда не признает. Делай ты хоть что. Это очевидно. И ты тому пример.

99% признания лежит в хайповой теме, в мейнстриме. Чем дальше твоя тема от хайпа, от мейнстрима - тем меньше твои шансы. Это просто никто не поймёт, да и не поймёт назначения даже.

Мои действия, мои интересы, моё поведение - опровергает твои потуги. Такие дела.

LjubaSherif ()

(видимо, имеется в виду типичный говнокод ядра

А что в нем говнокодного, типичный односвязный список на Си, пустой список установлен в null или чего я не понял, как правильно?

praseodim ★★★★ ()
Ответ на: комментарий от praseodim

Правильно

struct foo* p;

if (!bar) {
  p = bar->p;
...
}

потому что ((struct bar*)NULL)->p компилироваться не должно.

monk ★★★★★ ()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

А, я думал где-то инициализируется.

потому что ((struct bar*)NULL)->p компилироваться не должно.

Исполняться, хотел сказать? Потому что, по-моему, компилятор имеет право максимум warning выставить. Это же Си, все-таки, «высокоуровневый ассемблер» . Может этот null каким извращенным способом инициализируется чем-то в ходе работы.

praseodim ★★★★ ()
Последнее исправление: praseodim (всего исправлений: 2)
Ответ на: комментарий от tailgunner

Назови эту платформу.

Да какбэ это. Ну, m68k, например. Или хренова туча 8-и биток.

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 1)
Ответ на: комментарий от lenin386

Да какбэ это. Ну, m68k, например.

Да какбэ нет.

Или хренова туча 8-и биток.

Назови мне 8-битку, на которой работает Linux.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 2)
Ответ на: комментарий от Legioner

Товарищ. А компилятор о _физических адресах ничего и не знает. С учётом расположения сегмента, смещение вполне может быть нулевым, и результирующий физический адрес будет весьма не нуль.

lenin386 ★★★ ()
Ответ на: комментарий от tailgunner

Да какбэ нет.

Да какбэ да. m68k после ресета стартует с адреса 0. Там ПЗУ, конечно, но это валидный адрес.

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 1)
Ответ на: комментарий от tailgunner

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

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 3)
Ответ на: комментарий от lenin386

m68k после ресета стартует с адреса 0. Там ПЗУ, конечно, но это валидный адрес.

«Товарищ. А компилятор о _физических адресах ничего и не знает.» (ц)

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

Более того, понимаешь ли ты, щто компилятор генерит _объектный файл

Утипути. Нет, конечно, такие сложные вещи понимают только ленины.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 2)
Ответ на: комментарий от praseodim

А, я думал где-то инициализируется.

Тогда должно быть

struct foo* p = bar->p;

if (1) {
...
}

Собственно, оптимизатор компилятора в это и превращает. А потом приходит неинициализированный bar и выполняется код, который не должен по мнению программиста.

monk ★★★★★ ()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)