Labeled blocks for dangling pointers?

While experimenting with a dynamic buffer, I noticed a weird thing: one could use labeled blocks to resolve invalid pointers.

somehow this works:

const char: [:0]u8 = switch (handler.mode) {
   .binary, .decimal => Buf: {
        var buf: [2]u8 = undefined;
        break :Buf try std.fmt.bufPrintZ(&buf, "{d}", .{rand_int});
    },
    .hexadecimal => hexBuf: {
        var buf: [3]u8 = undefined;
        break :hexBuf try std.fmt.bufPrintZ(&buf, "{c}", .{assets.hex_chars[rand_int]});
    },
    .textual =>  texBuf: {
        var buf: [4]u8 = undefined;
        break :texBuf try std.fmt.bufPrintZ(&buf, "{u}", .{assets.tex_chars[rand_int]});
    },
};

Is this a bug or undocumented magic?

It is neither a zig bug nor magic.

Zig does not guarantee anything about dangling pointers. And what you found is not something that can only happen in labeled blocks. For example:

fn foo() ![:0]u8 {
    var buf: [2]u8 = undefined;
    return try std.fmt.bufPrintZ(&buf, "{d}", .{rand_int});
}

This is code that perfectly compiles, but foo() returns a dangling pointer in stack.

1 Like

I could see the compiler allows this, but I did not expect it to work—usually I am getting far from valid output in such cases.

It works because the compiler allocates stack on function entry and not on each block. But it is undefined behavior.

4 Likes

I think that would be a great addition to the pointers to temporary memory doc.

2 Likes