Remix.run Logo
ajross 4 days ago

> This is patently false. Any Undefined Behavior is harmful because it allows the optimizer to insert totally random code

Undefined to who, though? Specific platforms and toolchains have always attached defined behavior to stuff the standard lists as undefined, and provided ways (e.g. toolchain-specific volatile semantics, memory barriers, intrinsic functions) to exploit that. Even things like inline assembly live in this space of dancing around what the standard allows. And real systems have been written to those tools, successfully. At the bottom of the stack, you basically always have to deal with stuff like this.

Your point is a pedantic proscription, basically. It's (heh) "patently false" to say that "Any Undefined Behavior is harmful".

LegionMammal978 4 days ago | parent | next [-]

Yeah, this is especially true if you're writing a libc. E.g., every libc allocator in existence invokes UB with respect to ISO C when it reads metadata placed before a malloc()ed block of memory. Doubly so, since ISO C arguably doesn't even allow a container_of() mechanism at all. At some point, you have to look at what the implementation is actually expecting of your code.

senderista 4 days ago | parent [-]

To pick a slightly less obvious example, I doubt (but haven't tried to prove) that it's possible to use the POSIX ancillary data API for Unix domain sockets (i.e., SCM_RIGHTS) without invoking UB.

LegionMammal978 4 days ago | parent | next [-]

I think that's technically possible, but you have to use a freshly allocated (and zeroed) buffer every single time, since after it's been written to once, it's 'tainted' with the effective types of the structs stored in it. (Though it looks like the ISO C2y draft finally has language to address this case, with a concept of "byte arrays" that can hold objects of other types, as long as you keep alignments in mind.)

The bigger issue with ISO C and POSIX is everything around 'struct sockaddr': you don't have any way of knowing what types the implementation is internally reading in or writing out. If you give it a casted pointer to a 'struct sockaddr_in' but it reads the sa_family from the 'struct sockaddr *', that's UB; ditto if listen() gives you a 'struct sockaddr_in' and you read the sa_family from a 'struct sockaddr *'. Or if you use 'struct sockaddr_storage' at all, that's also UB. IIRC, the latest POSIX edition just tells implementations to "pretty please allow aliasing between these types in particular!"

Of course, POSIX has nothing on Windows APIs, many of which encourage the caller to cast around pointers with impunity. As far as I'm aware, MSVC doesn't care about strict aliasing at all, and only has a minimal set of optimizations for 'restrict' pointers.

ajross 4 days ago | parent | prev [-]

Or use the results of mmap() or futex() system calls, or to model the behavior around barrier/serializing instruction. MMIO is likewise right out. It's just asking too much of the poor language standard to rigorously specify every possible thing you might do with C syntax, even though many of those things can be very valuably implemented in high level languages.

So they punted and left it up to the toolchains, and the toolchains admirably picked up the slack and provided good tools. The problem then becomes the pedants who invent rules like "any UB is harmful" above, not realizing the UB-reliant code is plugging the holes keeping their system afloat.

Krssst 4 days ago | parent | prev [-]

Ubsan docs do mention one case where UB is defined by the implementation: floating point division by zero: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html

By contrast, I'd assume any other report by ubsan to be fair game for the optimizer to do its thing and generate whatever code is going to be different from what was likely the developer's intention. If not in the current version, maybe in a future one.

ajross 4 days ago | parent [-]

Unless the optimizer has rules around its own intrinsics or whatever, though. Again, this isn't a black/white issue. You need to know what specific toolchains are going to do when writing base level software like C libraries (or RTOS kernels, which is my wheelhouse). The semantics defined by the standard are not sufficient.

Krssst 4 days ago | parent [-]

Memory barrier builtins, inline assembly and other builtins may not be defined by the standard itself but won't lead in themselves to undefined behavior in my understanding since those are compiler extensions and defined by the implementation. (though invalid use can lead to UB, such as passing 0 to builtin_clz or modifying registers outside of the clobber list in inline assembly)

With that being said, I would definitely expect that the small set of UB that ubsan reports about is actually undefined for the compiler that implements the sanitizer (meaning: either problematic now or problematic in some future update).

uecker 4 days ago | parent [-]

I agree. The typical ubsan sanitizers (not all sanitizers though) detect serious issues which should be fixed in all cases and I would definitely consider it best practice to run testing with sanitizers (and I would also would recommend to run many in production).