3 ms·
> Including undefined behavior around memory allocation, in particular use-after-free? There are obviously going to be limits to what can be done. If you acces
by byuu 11y ago
> Including undefined behavior around memory allocation, in particular use-after-free?
There are obviously going to be limits to what can be done. If you access beyond memory, you get "bad data" if the address is mapped by the OS, or a crash if it's not. That's a clear bug, and we can't make C a language that is incapable of producing programs with bugs. I don't really think of this as "undefined" ... we define very clearly that one of two things happens, based on the OS' memory layout. That's very different from GCC's understanding, where undefined == "if I want to have the program upload a cat picture to Reddit instead of shift a signed integer right, then that's what I'll do." (facetious, but you get the idea. Many of GCC's 'optimizations' cause outright security vulnerabilities, and defy all logic, like deleting chunks of code entirely.)
We want the most logical thing to happen when a user does something, not a completely unexpected thing just because it happens to make some compiler benchmark test look a little better.
> Other forms of UB can mostly be patched up straightforwardly with a clean design
I'm betting there aren't any C programmers out there that know 100% of the behaviors that are undefined. I've been programming for 18 years, and I got bit the other day because I had "print(sqlRow.integer(), ", ", sqlRow.integer());" ... where the .integer() call incremented the internal read position. MinGW decided to evaluate the second call first, and then the first one, so my output ended up backward. You may think that one's obvious, just like I might think that a shift by more bits than the integer type holds being undefined is obvious, but there are people that would be surprised by both.
Stating that function arguments evaluate left-to-right, just like "operator," does in expressions, would be an infinitesimal speed hit on strange systems, and no speed hit at all on modern systems that can just as easily use an indirect move to set up the stack frame.
And if you have a processor that can't do arithmetic shift right, which would be extremely rare, then generate that processor's equivalent of "((x & m) ^ b) - b" after the shift.
- pcwalton 11y ago> I don't really think of this as "undefined" ... we define very clearly that one of two things happens, based on the OS' memory layout. That's very different from GCC's understanding, where undefined == "if I want to have the program upload a cat picture to Reddit instead of shift a signed integer right, then that's what I'll do." I don't think there's much of a difference in practice between the two. If you admit "bad data" into your language, you very quickly spiral into true undefined behavior. For example, call a "bad data" function pointer—what happens then? (This is basically how UAF tends to get weaponized in the wild, BTW.) Or use the "bad data" to index into a jump table—what happens then?
- byuu 11y ago> I don't think there's much of a difference in practice between the two. If you admit "bad data" into your language, you very quickly spiral into true undefined behavior. Well, by your definition, it would indeed be basically impossible to turn C into a well-defined language. You'd have to make absolutely radical changes to memory management, pointers, etc. So then, can we at least agree that it would be a good idea to minimize undefined behavior in C? Sure, we can't fix bad pointer accesses, and I get why this stuff was there in the '70s. But modern CPUs have largely homogenized on some basic attributes. How about we decide that "two's complement has won" and thus clearly define what happens on signed integer overflow? How about we state that "a byte is 8-bits"? And so on ... all of the things that are true of basically every major CPU in use, and that would be exceedingly unlikely to ever change again in the future. And this can still be offered as an optional flag. But when enabled, it's just a little bit of added protection against an oversight turning into a major security vulnerability, and at virtually no cost.
- ectoplasm 11y agoOut of curiosity, are there expensive runtime checks needed to handle signed integer overflow or do processors actually know about two's complement? If you had to check every single time you touched an int that could be bad. In general, what UB can be defined with static fixes, and what UB can only be defined with dynamic fixes?
- spc476 11y agoIt depends on the architecture. The VAX could be set (on a function-by-function basis) to either ignore 2's complement overflow, or automatically trap. The Intel x86 line can trap, but you have to add the INTO instruction, possibly after each math operation that could overflow. I don't think the Motorola 68k could trap on overflow. The MIPS has two sets of math operations, one that will automatically trap on 2's complement overflow, and a set that won't (and at the time, the C compiler I used only used the non-trap instructions). That's why the C standard is so weasly with overflow---it varies widely per CPU.