Live data from Hacker News

Why undefined behavior may call a never-called function

kristerw.blogspot.com

11–20 of 183 posts

Re: Why undefined behavior may call a never-called function

#11
I have become convinced that the current screw-the-programmer interpretation of ‘undefined behaviour’ was not intended, or even imagined, by the original ANSI C committee. Within the committee's mandate to ‘standardize existing practice’, it was simply an acknowledgement that C compilers translated straightforward C code into straightforward machine code without adding safety checks, and that simple code might —​ in some circumstances on some machines — do very strange things.

In the Rationale, the committee listed places where they intentionally diverged from existing practice. They considered “the most serious semantic change” to be requiring value-preserving (vs unsigned-preserving) integer promotion. They didn't mention ‘undefined behaviour' at all.

During the standards process, Dennis Ritchie described the ‘noalias’ proposal as “a license for the compiler to undertake aggressive optimizations that are completely legal by the committee's rules, but make hash of apparently safe programs”. That's exactly what ‘undefined behavior’ has turned into. If anyone had foreseen that at the time, the reaction would have been the same: “Noalias must go. This is non-negotiable.”

Re: Why undefined behavior may call a never-called function

#12
post #10
post #4

Earlier quoted context omitted.

Sure, but you could also replace "rm -rf /" with "id" or something else non destructive. Somebody is going to copy paste that snippet and have a bad day.

I believe "rm -rf /" illustrates the point quite well, better than id. I'm more in the common sense is more common than people give credit and in the situations where that is not the case then rm has built-in protections. It literally says: > That is, the compiled program executes “rm -rf /” The next line following the code. It's not tricking anyone.

Gnu rm has built in protections. Not sure that's the case on OSX, Linux distributions that use busybox, etc.

Re: Why undefined behavior may call a never-called function

#13
post #2

Interesting choice of code for a demo. I wonder if anyone hosed themselves running this. It seems gnu rm has "-preserve-root" as a default, but that's not guaranteed to be on every rm.

If it's run as an unprivileged user rather than root, it won't be able to delete the whole system. I'm pretty sure that means it won't do anything, but I don't feel like trying it.

Re: Why undefined behavior may call a never-called function

#14
post #2

Interesting choice of code for a demo. I wonder if anyone hosed themselves running this. It seems gnu rm has "-preserve-root" as a default, but that's not guaranteed to be on every rm.

If it's run as an unprivileged user rather than root, it won't be able to delete the whole system. I'm pretty sure that means it won't do anything, but I don't feel like trying it.

>I'm pretty sure that means it won't do anything

Assuming an rm without the "-preserve-root" default, it would remove everything that it had permission to. So, eventually, for example, it would wipe your home directory.

I suspect this to be the case for OSX, Alpine Linux (or other distros that use busybox), probably some of the BSD distributions, etc.

Re: Why undefined behavior may call a never-called function

#15

If you make one small change to this file you can cause clang and gcc to both prevent this from compiling if you are using warnings. namespace { void NeverCalled() { Do = EraseAll; } } or marking NeverCalled as static itself. Results in warning: unused function NeverCalled. In general this is best practice for functions defined and used in a single translation unit.

That's a vital part of the setup.

You could hypothetically link this compilation unit against another unit which included:

  void NeverCalled();
  struct A {
    A() { NeverCalled(); }
  };
  A a;

Re: Why undefined behavior may call a never-called function

#16
I suspect this is another critical point:

as NeverCalled may have been called from, for example, a global constructor in another file before main is run

clang doesn't analyse across module boundaries --- even when it theoretically could --- so it doesn't know for certain that NeverCalled() is indeed never called.

Throughout the years I've grown increasingly displeased at how compilers handle even trivial cases like this[1][2][3], and in particular their treatment of UB[4][5][6][7]; as one of the comments on the article implies, UB was unlikely intended by the standard's authors as a "you should let the most inane things happen" but more as a "do what makes the most sense".

A more sensical approach to analysing this program, e.g. as employed by a human, would be to see that NeverCalled() is the only function that can write Do, but then further ascertain whether it is actually called. Since this is the entire program, it's trivial to see that it's not, and thus that possibility should also have been removed from the set of possible values for Do. Thus, Do can neither be EraseAll nor 0 --- so a "contradiction" has occurred, the code is likely bugged or the programmer intentionally wants the UB, and the sane choice at this point would be to forget about trying to optimise and just generate the obvious code. "I can't figure out how to optimise this, so I'll do the simplest thing that works."

The question then becomes, why can a human see something so straightforward but the compiler can't? I think that's the deeper issue here with how the compilers like gcc/clang today work --- they're too opaque and complex, and their authors take The Holy Standard as gospel while ignoring the practical realities of their decisions.

Unfortunately a lot of programmers have gotten the notion that they can rely on the compiler to do "amazing" optimisation, and therefore they can write horrible code, leading to horribly unintuitive and "overly aggressive" optimisation like this.

I'm sure that me, along with quite a few others, have some ideas for how to make a C/C++ compiler which is both powerful in optimisation and code generation, but more predictable and "obvious" in terms of UB. Unfortunately, I'm also sure that we don't have the time to do it.

[1] https://news.ycombinator.com/item?id=15006090

[2] https://news.ycombinator.com/item?id=9397924

[3] https://news.ycombinator.com/item?id=15188416

[4] https://news.ycombinator.com/item?id=11147598

[5] http://blog.metaobject.com/2014/04/cc-osmartass.html

[6] https://news.ycombinator.com/item?id=7960219

[7] https://news.ycombinator.com/item?id=9809885

Re: Why undefined behavior may call a never-called function

#17
post #2

Interesting choice of code for a demo. I wonder if anyone hosed themselves running this. It seems gnu rm has "-preserve-root" as a default, but that's not guaranteed to be on every rm.

If it's run as an unprivileged user rather than root, it won't be able to delete the whole system. I'm pretty sure that means it won't do anything, but I don't feel like trying it.

[deleted]

Re: Why undefined behavior may call a never-called function

#18
post #4
post #3

Earlier quoted context omitted.

One of the advantages of using Docker! You can safely run this code multiple times and play with it without affecting your machine.

Sure, but you could also replace "rm -rf /" with "id" or something else non destructive. Somebody is going to copy paste that snippet and have a bad day.

I usually use "cowsay" for demonstrating that arbitrary code execution is possible. Less destructive, and more entertaining when someone actually tries to run the code. :-)

Re: Why undefined behavior may call a never-called function

#19
post #11

I have become convinced that the current screw-the-programmer interpretation of ‘undefined behaviour’ was not intended, or even imagined, by the original ANSI C committee. Within the committee's mandate to ‘standardize existing practice’, it was simply an acknowledgement that C compilers translated straightforward C code into straightforward machine code without adding safety checks, and that simple code might —​ in…

This, just so much this!

I've been longing for a C compiler with a "sane optimizations only"-switch like forever. I'd gladly give up on the additional couple of per cent speed improvement obsessive compulsive compiler writers managed to eke out by ignoring the source codes' obvious intentions and defending it with "but technically it's undefined behaviour"!

Re: Why undefined behavior may call a never-called function

#20
post #19
post #11

I have become convinced that the current screw-the-programmer interpretation of ‘undefined behaviour’ was not intended, or even imagined, by the original ANSI C committee. Within the committee's mandate to ‘standardize existing practice’, it was simply an acknowledgement that C compilers translated straightforward C code into straightforward machine code without adding safety checks, and that simple code might —​ in…

This, just so much this! I've been longing for a C compiler with a "sane optimizations only"-switch like forever. I'd gladly give up on the additional couple of per cent speed improvement obsessive compulsive compiler writers managed to eke out by ignoring the source codes' obvious intentions and defending it with "but technically it's undefined behaviour"!

What would that look like? How would this prevent me from returning a pointer to a stack frame that no longer exists, just for example? Clearly undefined behavior, but C doesn't seem capable of expressing this safely, with or without the compiler's help?
Post reply on HN