C interop bool weirdness

Hey,
I am currently writing wrappers around XWays, a digital forensics tool. They provide a C API, which I am wrapping in Zig. Everything works so far, but I just encountered a weird bug, which I can’t explain myself. It occurred, after I decided I want to compile in Release mode, in Debug everything worked fine. I noticed, that suddenly an if check, that previously worked in Debug mode now suddenly failed.

There exists a function with the following C signature:

INT64 XWF_GetItemInformation(
  LONG nItemID,
  LONG nInfoType,
  LPBOOL lpSuccess,
);

So far so good, it takes in an c_long, a c_long and a pointer to a boolean, which it populates with its success status.

I wrapped it the following way:

XWF_GetItemInformation: *const fn (item_id: c_long, info_type: c_long, success: *bool) i64

I called it the following way:

var suc: bool = false;

std.debug.print("suc is {any}\n", .{suc});
const res = XWF_FUNCS.XWF_GetItemInformation(item_id, @intFromEnum(info_type), &suc);
std.debug.print("suc is {any}\n", .{suc});

if (!suc) return XTensionTypes.ItemInfoError.GeneralError;

This works in debug mode, where it prints

suc is false
suc is true

and doesn’t return an error.
In release mode however it return GeneralError and prints the following:

suc is false
suc is true false      °                       +       nSdφ²⌂  YTdφ²⌂  │Tdφ²⌂                                         ♂                       ♦                       9       ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

Now to the first part of my question. Why does that happen in Release, but not in Debug mode? And further: in my understanding bool is a type defined as 1 byte. So it shouldn’t print any extra memory after it, since the compiler knows the type and it has a fixed size. In case of of a null-terminated string, I could understand it, because maybe the sentinal got messed up, but why would it happen with a bool?

Second:
I fixed this dilemma by using the windows defined type BOOL, because I thought that maybe that could have something to do with that. So I switched to code over to:

  • signature
    XWF_GetItemInformation: *const fn (item_id: c_long, info_type: c_long, success: *win.BOOL) i64

  • usage
var suc: win.BOOL = win.FALSE;

std.debug.print("suc is {any}\n", .{suc});
const res = XWF_FUNCS.XWF_GetItemInformation(item_id, @intFromEnum(info_type), &suc);
std.debug.print("suc is {any}\n", .{suc});

if (suc != -win.TRUE) return XTensionTypes.ItemInfoError.GeneralError;

This works as expected in both Debug and Release, but as you might have noticed, in the if-condition for the error, there is a - before win.TRUE. This is there, because I noticed it printed the values as such:

suc is 0
suc is -1

Why would the value returned suddenly be negative, eventhough it is defined by windows as a positive 1 for True. But in this case it might just be XWays being weird (but that would make it weird, that during Debug mode with zigs own bool type, it was apparently positive 1).

I would appreciate any pointers or explanations.

Zig booleans are one bit; Windows BOOL is 4 bytes large for some reason.
You’ll also need to specify the calling convention of XWF_GetItemInformation. I’m guessing it’s the default C calling convention in this case:

// `extern` functions use the c calling convention by default 
extern "linked_library" XWF_GetItemInformation(nItemID: win.LONG, nInfoType: win.LONG, lpSuccess: *win.BOOL) i64;
// or
const XWF_GetItemInformation: *fn(nItemID: win.LONG, nInfoType: win.LONG, lpSuccess: *win.BOOL) callconv(.c) i64 = ...;
1 Like

Just to add a clarification: Zig bools are 1 bit only in the context of packed structs/unions, otherwise 1 byte, which can be checked with @sizeOf(bool) and @bitSizeOf(bool)

Windows’ BOOL is defined as int, so the appropriate native Zig type would be c_int, though I’m sure win.BOOL is already defined as such.

1 Like

Yeah, that makes sense, especially because the window bool type is an int. What I don’t get, is the fact that it prints a lot more than 4 bytes/ 1 byte and works in debug mode. The calling conventions is a good tip, I’ll remedy that

It works in debug only by chance. Debug-mode masking bugs isn’t uncommon when dealing with memory bugs, due to padding, hidden fields, debug info, lack of optimization passes, and whatnot.

When suc is a 1-byte bool, you pass a pointer to 1 byte storage on the stack when in fact 4 byte storage is needed. Your program is essentially entering undefined behavior territory at this point, and anything can happen. Including what you observed above.

Thanks, that clears that up :smiley:

1 Like

One important thing to check in older C libraries that were created before C99 is whether their own BOOL typedef is actually a one-byte value (C89 didn’t have a bool type, this was only added in C99 as _Bool which is compatible with the C++ bool and has a size of 1 byte). It’s not uncommon in such older C libs to define a boolean as a regular int (4 bytes).