Live data from Hacker News

Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

devlog.hexops.com

11–20 of 60 posts

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#11
post #8

The article discusses some undefined behaviour resulting from shifting an unsigned char left by 24 places. Any idea why the compiler wasn't warning about that? It seems like it would be easy to implement that warning in the compiler. I feel that I'm missing something.

That’s the point made at the end of the article. The compiler check does exist, but because it isn’t defaulted to on, no body in 6 years use it. Zig has the checks on by default, so the first time someone used it, they found and fixed it for everyone. > Anybody using GLFW could have enabled UBSan in their C compiler. Anybody could have run into this same crash and debugged it in the last 6 years. But they didn’t. Onl…

I have fixed similar issues in Allegro and SDL (which are libraries that cover a superset of GLFW's scope) because I do use UBSan in my projects. Unfortunately, I haven't used GLFW in any project yet, so I couldn't fix this one :)

(the point on good defaults stays valid though)

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#12

The article discusses some undefined behaviour resulting from shifting an unsigned char left by 24 places. Any idea why the compiler wasn't warning about that? It seems like it would be easy to implement that warning in the compiler. I feel that I'm missing something.

It's not actually shifting a char, because integer promotion happens first.

https://github.com/glfw/glfw/pull/1986#issuecomment-95578417...

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#13
post #8

The article discusses some undefined behaviour resulting from shifting an unsigned char left by 24 places. Any idea why the compiler wasn't warning about that? It seems like it would be easy to implement that warning in the compiler. I feel that I'm missing something.

That’s the point made at the end of the article. The compiler check does exist, but because it isn’t defaulted to on, no body in 6 years use it. Zig has the checks on by default, so the first time someone used it, they found and fixed it for everyone. > Anybody using GLFW could have enabled UBSan in their C compiler. Anybody could have run into this same crash and debugged it in the last 6 years. But they didn’t. Onl…

I was expecting a compile time warning and thought ubsan shouldn't be necessary. But CUViper explains why the UB can only be caught at runtime.

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#14
post #3
post #2

Can anyone provide some context? I don't know what GLFW is, and the blog post doesn't really explain. From Googling, I see what it is now, but I don't really have a sense of how important GLFW bindings are. Is this very niche? Or, like is this a major contribution to game development? For that matter, is this mainly going to help Zig game developers, or is this a bit of tooling for everyone, kind of like zig cc?

Author here, sorry for the missing context. GLFW is very popular among game developers for opening graphics windows in a cross-platform way. This helps just Zig game developers for now, it's not a major contribution to game development (though I hope other things I do with Mach engine in the future will be.) I do think with some minor tweaks this could be used to make bindings for GLFW in other languages easier to in…

Thanks! No need to apologize, I'm not in your target audience, and that's fine. Was just curious, is all.

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#15
tl;dr:

Glfw is a library used most often by beginners learning graphics programming [but also in production systems]. A bit of code in that library shifts a char value c left by 24 places, yielding a 32-bit int value. If the top bit of the char (c & 0x80) is 1, that shifts it into the sign bit of the int, changing its sign, which is Undefined Behavior, and the compiler is free to do anything, including both what you wanted, and crashing. Compiling with "undefined behavior sanitizer" on, as Zig does by default, traps the event, causing the requested crash. Building without sanitization, nothing notices, and everything "just works".

A simple way to fix this would be to shift [wrong: c by 24u, "(c Among permitted behaviors is launching missiles and just not even executing. In this case the compiler was not able to prove that a char value with the high bit set certainly would or certainly could not appear in that context. If it could prove it could not, then there would be no UB and everything would be fine. If it could prove such a value would appear, then it would consider itself justified in deleting all the code both after and leading up to the misuse, back to the point where a choice determined whether to enter the erroneous bit, and also the code making the choice, and likely the code that computes the value used in making the choice.

Perhaps surprisingly, this happens all the time and makes our programs smaller and faster than they would be otherwise. Still, people often complain about it.

If your testing does not exercise the code path that was deleted, then even with the UB sanitizer turned on, you might not notice that the compiler would have, without, chosen to delete that code. But deleting it is more likely to get your mistake noticed.

The most common source of this particular brand of UB is multiplying int values where the product does not properly fit in an int result. (Even Rust allows this, although it can be persuaded to trap misuses, with some care.) You might be tempted to do all your multiplications with unsigned ints, but you do not really get better results that way; often that just makes it even harder to find mistakes.

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#16
post #15

tl;dr: Glfw is a library used most often by beginners learning graphics programming [but also in production systems]. A bit of code in that library shifts a char value c left by 24 places, yielding a 32-bit int value. If the top bit of the char (c & 0x80) is 1, that shifts it into the sign bit of the int, changing its sign, which is Undefined Behavior, and the compiler is free to do anything, including both what you…

It should be made clear that glfw is also used by production software as well; I’ve spotted it in games, and also my terminal emulator of choice (kitty.) It is often a reasonable alternative to SDL if you just want to get a window with an OpenGL or Vulkan context and don’t need many of the other features SDL offers.

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#18

The article discusses some undefined behaviour resulting from shifting an unsigned char left by 24 places. Any idea why the compiler wasn't warning about that? It seems like it would be easy to implement that warning in the compiler. I feel that I'm missing something.

gcc and clang should warn about this: #include void foo() { uint32_t foo = 1; foo According to godbolt, the following warning is emitted for gcc: warning: left shift count >= width of type [-Wshift-count-overflow] And for clang: warning: shift count >= width of type [-Wshift-count-overflow] https://godbolt.org/z/ffccWexMP

Different issue, that's shifting so far it would never fit. Here's an example with clang ubsan: https://godbolt.org/z/6Wvhn5rP9

Note that with foo = 0 there's no UB.

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#19
post #15

tl;dr: Glfw is a library used most often by beginners learning graphics programming [but also in production systems]. A bit of code in that library shifts a char value c left by 24 places, yielding a 32-bit int value. If the top bit of the char (c & 0x80) is 1, that shifts it into the sign bit of the int, changing its sign, which is Undefined Behavior, and the compiler is free to do anything, including both what you…

> A simple way to fix this would be to shift c by 24u, "(c This works for most arithmetic operators, but not shift: "The type of the result is that of the promoted left operand". The type of the right-hand-side has no effect on the type of the result.

To fix this, you'd need to cast the left-hand-side to a type that is wide enough.

Re: Perfecting GLFW for Zig, and finding lurking undefined behavior that went unnot

#20
post #15

tl;dr: Glfw is a library used most often by beginners learning graphics programming [but also in production systems]. A bit of code in that library shifts a char value c left by 24 places, yielding a 32-bit int value. If the top bit of the char (c & 0x80) is 1, that shifts it into the sign bit of the int, changing its sign, which is Undefined Behavior, and the compiler is free to do anything, including both what you…

[deleted]
Post reply on HN