LINUX.ORG.RU

Вызов никогда не вызываемой функции

 ,


3

5

Ваши ставки, господа: насколько безопасно на своём компьютере запускать такую программу? Не сотрет ли она вам корень?

Люблю C++.

#include <cstdlib>

typedef int (*Function)();

static Function Do;

static int EraseAll() {
  return system("yes");
}

void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}
★★★★☆

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

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

Кстати, решил попробовать добавить в описание NeverCalled static, чтоб его нельзя было вызвать из другого модуля. И вот что у меня получилось. utf8nowhere, maverik, annulen, ckotinko, Dendy, numas13 и tim239, возможно, вам будет интересно, ну а если нет, то звиняйте. Оказывается, разрабы clang не ошиблись, а те ещё троли, нещадно тролят нашего брата-быдлокодера этим вашим стандартом!

#include <cstdlib>

typedef int (*Function)();

static Function Do;

static int EraseAll() {
  return system("yes");
}

static void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}
$ clang -o test_fnull -Os test_fnull.cpp
$ ./test_fnull
Недопустимая инструкция
$ clang -S -Os test_fnull.cpp

Теперь смотрим сгенерированный ассемблерный код в test_fnull.s:

        .text
        .file   "test_fnull.cpp"
        .globl  main
        .type   main,@function
main:                                   # @main
        .cfi_startproc
# BB#0:
        ud2
.Ltmp0:
        .size   main, .Ltmp0-main
        .cfi_endproc


        .ident  "Debian clang version 3.5.0-10 (tags/RELEASE_350/final) (based on LLVM 3.5.0)"
        .section        ".note.GNU-stack","",@progbits

Недопустимой инструкцией здесь является ud2. Вот зачем так делать? Они увидели, что код некорректный и никакие другие модули не могут вызвать NeverCalled или как-то иначе изменить Do. Вот почему бы не выдать предупреждение? Но его нет. Почему бы не вызвать 0 из объектника, как и написано в программе, вызвав тем самым сегфолт? Тогда погромист запустит код в дебагере, наткнётся на этот самый 0 и всё поймёт, если он не до конца пропил мозги. Но компилятор намеренно вставляет в код недопустимую инструкцию, чтоб программист и в дебагере ничего не увидел кроме того, что компилятор сгенерил какую-то хрень. Причём хрень эта никак не связана с ошибкой программиста, т. к. недопустимые инструкции компилятор генерить не должен. Он или компилит нормально, или вообще не компилит, выводя ошибки. Это тоже такая фича? По аналогии с автомобилем это явный намеренный выезд влево на встречку, зашитый в автомобиль, при отпускании водителем руля. Нормальная такая фича. Главное, шо по стандарту!

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

Почему бы не вызвать 0 из объектника, как и написано в программе, вызвав тем самым сегфолт?

Ты хотел чтобы прога упала — они упала. Какая разница, от SIGSEGV или ud2.

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

Почему бы не вызвать 0 из объектника, как и написано в программе, вызвав тем самым сегфолт?

Ты хотел чтобы прога упала — они упала. Какая разница, от SIGSEGV или ud2.

Я хотел другого:

Тогда погромист запустит код в дебагере, наткнётся на этот самый 0 и всё поймёт, если он не до конца пропил мозги.

А этого:

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

я не хотел.

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

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

если ты хочешь вместо гоночного автомобиля получить городской - брешь джаву. Если нужен блондинкокар, то берешь Golang (в котором вообще ничего нет, но и закиси азота тоже нет!) или JavaScript. А если ты вдруг родился с руками из жопы, то это сразу Clojure (которая медленней Джавы в 20 и более раз, и медленней C++ в незнамо сколько раз) - там накосячить вообще невозможно =)

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

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

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

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

а они и не поворачивают в данном случае. Есть один общий алгоритм, обрабатывающий инлайнинги. Не только этот случай, а еще тысячу разных. И они там внутри постоянно придумывают, как разруливать всевозможные UB. На некоторых случаях он работает хорошо, на некоторых - плохо (как сейчас). Причем, сейчас это не «плохо», а «возможно плохо», ибо у многих в этом треде не возникает проблем таким поведением. И ты что думаешь, ради одного сраного корнер кейса они пойдут править весь обобщенный алгоритм? Им делать больше нечего, что ли? Разработка проекта стоит бешеного бабла лучших специалистов, они его явно направят на реализацию чего-то более осмысленного.

олсо вангую, что чем более навороченными будут становиться оптимизации, тем меньше поведение в случае UB будет поддаваться какому-то рациональному объяснению с точки зрения «человеческой» логики

и другие сложные случаи тоже - например, многопоточность совершенно не понимабельна обычным человеком. И в джаве тоже! Можно просто в рамках карго-культа выполнять набор определенных «best practices» и молиться на иконы Энтони Вильямса, и может быть оно не сломается

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

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

Я тоже сначала так подумал, когда сказал, что это баг, который, по всей видимости, скоро будет исправлен. Но после того, как я увидел, что в другой ситуации они вместо 0 подставляют неопределённую инструкцию ud2, я начал в этом сильно сомневаться. Что за алгоритм может заставить компилятор генерить неисполняемые инструкции? Или это намёк на неопределённость кода? Но зачем так намекать, когда можно всё оставить как есть, чтоб программист сам нашёл ошибку в дебагере, а ещё лучше и ворнинг выдать, коли они чётко видят эту неопределённость.

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

у многих в этом треде не возникает проблем таким поведением.

Проблемы возникнут, когда им придётся дебажить чужой код из 100 модулей в 500 строк каждый, вылетающий из-за такой неинициализированной переменной, а вместо предупреждения и вызова 0 адреса они увидят в дебагере недопустимую ассемблерную команду ud2. Но это ещё пол беды: подумаешь, провозятся 2 дня вместо получаса, невелика потеря, типа сами виноваты, даже если не они этот код писали. Куда хуже будет, когда программа вообще не будет вылетать, но будет неправильно выполняться, потому что вместо предполагаемой, но не реализованной ветки выполнения будет выполняться случайная ветка. Такая программа легко может попасть в релиз и успешно эксплуатироваться многие годы, пока гром не грянет. А громом в зависимости от назначения программы могут оказаться и не сохранённые данные, и потерянные деньги, и даже человеческие жизни.

А на форуме конечно не возникает проблем, пока всё на стадии бла-бла-бла. Мы, типа, тру-программисты, а быдлокодеры пусть страдают.

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

про ud2 - лень читать комменты - но кто-то должен был оставить вот этот цикл из трех постов:

http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html

в частности, обрати внимание на

Dereferencing a NULL Pointer: contrary to popular belief, dereferencing a null pointer in C is undefined. It is not defined to trap, and if you mmap a page at 0, it is not defined to access that page. This falls out of the rules that forbid dereferencing wild pointers and the use of NULL as a sentinel. NULL pointer dereferences being undefined enables a broad range of optimizations: in contrast, Java makes it invalid for the compiler to move a side-effecting operation across any object pointer dereference that cannot be proven by the optimizer to be non-null. This significantly punishes scheduling and other optimizations. In C-based languages, NULL being undefined enables a large number of simple scalar optimizations that are exposed as a result of macro expansion and inlining.

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

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

Another interesting case that bit someone recently happened when they had a (global) function pointer. A simplified example looks like this:

static void (*FP)() = 0;
static void impl() {
  printf("hello\n");
}
void set() {
  FP = impl;
}
void call() {
  FP();
}

which clang optimizes into:

void set() {}
void call() {
  printf("hello\n");
}

It is allowed to do this because calling a null pointer is undefined, which permits it to assume that set() must be called before call(). In this case, the developer forgot to call «set», did not crash with a null pointer dereference, and their code broke when someone else did a debug build.

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

это был хитрый план - я надеялся прижать оппонента тем, что он как истинный Ъ не ходит по ссылкам. Кажется, весь план вговно. Тем не менее, это официальное мнение разработчиков clang :)

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

It turns out that C is not a «high level assembler» like many experienced C programmers (particularly folks with a low-level focus) like to think, and that C++ and Objective-C have directly inherited plenty of issues from it.

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

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

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

Экий конфуз.

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

У меня вообще не собирается, что gcc, что clang:

11 : <source>:11:13: error: 'void NeverCalled()' defined but not used [-Werror=unused-function]
 static void NeverCalled() {
             ^~~~~~~~~~~
cc1plus: all warnings being treated as errors
Compiler exited with result code 1
maverik ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.