What should happen when control reaches __builtin_unreachable?

107 Views Asked by At

I wrote this code:

enum Color
{
    kRed,
    kGreen,
    kBlue,
    kUnexistingColor
};

const char* foo(Color color)
{
    switch (color)
    {
        case kRed:
            return "red";
        case kGreen:
            return "green";
        case kBlue:
            return "blue";
        case kUnexistingColor:
        default:
            __builtin_unreachable();

    }
    return "";
}

If I call foo with an invalid value, my program just terminates silently or returns "blue" in some other cases. What should really happen if control reaches the point where __builtin_unreachable(); is? Should some error message box on Windows appear, or what?

2

There are 2 best solutions below

1
paddy On

You told the compiler that the code is unreachable. If you break that promise, you can expect undefined behavior. This is documented here:

If control flow reaches the point of the __builtin_unreachable, the program is undefined.

If you want to have well-defined behavior, use something else. It's common to put debug-only assertions in such places so you can detect them when running debug builds.

Another common approach is to throw an exception. When you see a crash dump for an unhandled exception raised at that point, you can deduce the supposed "unreachable" code was reached.

You can also write out a message to your log file. Or you can silently return an "error" value and handle that cleanly by not taking any action.

All this entirely depends on how important it is that you know about it and how critical it is to the continued functioning of your program.

0
Alex Guteniev On

The undefined behavior is useful to make optimizations as the compiler assumes that the situation that leads to undefined behavior will not happen. The particular placement of __builtin_unreachable() is likely to be helpful for this.


A possible outcome for an optimizing compiler is to replace the switch with array lookup, based on color as index. This would be very good optimization, considering it produces branchless code.

Now, without __builtin_unreachable() the compiler will still have to check the index bounds, and produce conditional jumps. With __builtin_unreachable() this can be skipped. Then if you pass unexpected value, the function would give garbage string that may ruin you program, most likely because it will be too long, or it also may potentially have inaccessible memory before the \0-terminator, or it can reach inaccessible memory immediately.

If your program has some security implications, then returning a valid string from some unpredicted memory area can be worse than a crash, because it may expose some data not meant to be exposed, or a string not meant to be a part of some script or query becomes that.


Consider using std::unreachable() as a portable equivalent, if you can afford switching to C++23.


If you don't want the UB on unexpected code branch, instead of exceptions, consider using abort(). This will keep some code part exception-free, which may aid optimizations, also if you don't really except some situation, then probably attempting to handle it, even with some written exception handling, may go further wrong.

Returning nullptr, as suggested in one of the comments, can only be a good idea if you check the returned value for nullptr before any indirections. Otherwise, if you don't check for that, you still have the UB, as with __builtin_unreachable(), with optimizations less likely to engage, but still possible, so you don't obtain neither robust optimization, nor safe behavior for unexpected parameter value.