Zig Version
0.16.0
Steps to Reproduce, Observed Behavior, and Expected Behavior
Hi there, I stumbled upon a bug that I've been observing in my renderer for quite some time (since 0.12 at least) now but this is the first time it causes a panic because of a different code path.
When I pass a zig bool to a C function, in at least two scenarios C does not receive 1 or 0, and instead it receives another int:
- When passing a packed structs' bool.
- Comparing enums
Expected behavior: for both scenarios, when passing a boolean to a linked function I'd expect to send boolean values as value == 0 ? 0 : 1 for compatibility. I'm unaware if this is what everybody expects, but assuming sanitizers exist, I think the answer is yes.
Repro of packed structs
In this example, glfw passes me some flags about the pressed keys in an u8, I do have a packed struct (u8) for convenience. When I pass it to imgui.AddKeyModifier(.super, packed.super /* bool */), the C function receives 0x8 instead of 0x1 as bool in ReleaseSafe mode and 0x1 in Debug mode.
Notes:
- This also happens if I write
imgui.AddKeyModifier(.super, packed.super == true), it seems like an optimized noop.
- This also happens if I write
setVSync(enum == .double_buffer), here we also receive the value of .double_buffer instead of 0x1
- I'm assuming both reproduction cases are the same underlying bug
- Proven workaround
imgui.AddKeyModifier(.super, (@bitCast(packed) & 0b0000_1000) != 0)
conststd=@import("std");// The C function that receives the bool and prints its raw byte (see recv.c).externfnrecv(down:bool)void;constKeyMods=packedstruct(u8){shift:bool,control:bool,alt:bool,super:bool,caps_lock:bool,num_lock:bool,_padding:u2=0,};fnupdateMods(mods:KeyMods)void{recv(mods.shift);recv(mods.control);recv(mods.alt);recv(mods.super);}// hack to prevent compiler optimizationsvarraw_mods:u8=0x8;fnliveMods()KeyMods{constp:*volatileu8=&raw_mods;return@bitCast(p.*);}pubfnmain()void{std.debug.print("mods byte = 0x8 (only SUPER held). Correct down: control=0 shift=0 alt=0 super=1\n",.{});updateMods(liveMods());}
// recv.c
#include <stdio.h>
// a placeholder value to store whatever we receive as parameter as-is
static _Bool g_down;
void recv(_Bool down) {
g_down = down;
/* volatile load so clang can't assume a _Bool is 0/1 */
unsigned char raw = *(volatile unsigned char *)&g_down;
printf(" down(raw byte)=0x%x%s\n", raw, raw > 1 ? " <-- INVALID BOOL" : "");
}
# run.sh
zig cc -c -O2 -fno-sanitize=undefined recv.c -o recv.o
for opt in Debug ReleaseSafe; do
echo "================ -O$opt ================"
zig build-exe -O"$opt" -lc main.zig recv.o --name "repro_$opt"
"./repro_$opt"
echo
done
Output
================ -ODebug ================
mods byte = 0x8 (only SUPER held). Correct down: control=0 shift=0 alt=0 super=1
down(raw byte)=0x0
down(raw byte)=0x0
down(raw byte)=0x0
down(raw byte)=0x1
================ -OReleaseSafe ================
mods byte = 0x8 (only SUPER held). Correct down: control=0 shift=0 alt=0 super=1
down(raw byte)=0x8 <-- INVALID BOOL
down(raw byte)=0x0
down(raw byte)=0x0
down(raw byte)=0x1
### Zig Version
0.16.0
### Steps to Reproduce, Observed Behavior, and Expected Behavior
Hi there, I stumbled upon a bug that I've been observing in my renderer for quite some time (since 0.12 at least) now but this is the first time it causes a panic because of a different code path.
**When I pass a zig bool to a C function, in at least two scenarios C does not receive 1 or 0**, and instead it receives another int:
- When passing a packed structs' bool.
- Comparing enums
Expected behavior: for both scenarios, when passing a boolean to a linked function I'd expect to send boolean values as `value == 0 ? 0 : 1` for compatibility. I'm unaware if this is what everybody expects, but assuming sanitizers exist, I think the answer is yes.
## Repro of packed structs
In this example, glfw passes me some flags about the pressed keys in an `u8`, I do have a `packed struct (u8)` for convenience. When I pass it to `imgui.AddKeyModifier(.super, packed.super /* bool */)`, the C function receives 0x8 instead of 0x1 as bool in ReleaseSafe mode and 0x1 in Debug mode.
Notes:
- This also happens if I write `imgui.AddKeyModifier(.super, packed.super == true)`, it seems like an optimized noop.
- This also happens if I write `setVSync(enum == .double_buffer)`, here we also receive the value of `.double_buffer` instead of 0x1
- I'm assuming both reproduction cases are the same underlying bug
- Proven workaround `imgui.AddKeyModifier(.super, (@bitCast(packed) & 0b0000_1000) != 0)`
```zig
const std = @import("std");
// The C function that receives the bool and prints its raw byte (see recv.c).
extern fn recv(down: bool) void;
const KeyMods = packed struct(u8) {
shift: bool,
control: bool,
alt: bool,
super: bool,
caps_lock: bool,
num_lock: bool,
_padding: u2 = 0,
};
fn updateMods(mods: KeyMods) void {
recv(mods.shift);
recv(mods.control);
recv(mods.alt);
recv(mods.super);
}
// hack to prevent compiler optimizations
var raw_mods: u8 = 0x8;
fn liveMods() KeyMods {
const p: *volatile u8 = &raw_mods;
return @bitCast(p.*);
}
pub fn main() void {
std.debug.print("mods byte = 0x8 (only SUPER held). Correct down: control=0 shift=0 alt=0 super=1\n", .{});
updateMods(liveMods());
}
```
```c
// recv.c
#include <stdio.h>
// a placeholder value to store whatever we receive as parameter as-is
static _Bool g_down;
void recv(_Bool down) {
g_down = down;
/* volatile load so clang can't assume a _Bool is 0/1 */
unsigned char raw = *(volatile unsigned char *)&g_down;
printf(" down(raw byte)=0x%x%s\n", raw, raw > 1 ? " <-- INVALID BOOL" : "");
}
```
```sh
# run.sh
zig cc -c -O2 -fno-sanitize=undefined recv.c -o recv.o
for opt in Debug ReleaseSafe; do
echo "================ -O$opt ================"
zig build-exe -O"$opt" -lc main.zig recv.o --name "repro_$opt"
"./repro_$opt"
echo
done
```
## Output
```
================ -ODebug ================
mods byte = 0x8 (only SUPER held). Correct down: control=0 shift=0 alt=0 super=1
down(raw byte)=0x0
down(raw byte)=0x0
down(raw byte)=0x0
down(raw byte)=0x1
================ -OReleaseSafe ================
mods byte = 0x8 (only SUPER held). Correct down: control=0 shift=0 alt=0 super=1
down(raw byte)=0x8 <-- INVALID BOOL
down(raw byte)=0x0
down(raw byte)=0x0
down(raw byte)=0x1
```