ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.45k stars 2.52k forks source link

compiler crash writing pointer to `comptime var` to reinterpreted memory #21216

Open rohlem opened 1 month ago

rohlem commented 1 month ago

Zig Version

0.14.0-dev.617+208baa37c

Steps to Reproduce and Observed Behavior

(For context, I was basically playing around with a userland implementation of a safety feature to simulate the pinned struct/type feature proposed in https://github.com/ziglang/zig/issues/7769.) minimized repro:

const P = packed struct {
    p: *P,
};
comptime {
    var buffer: [@sizeOf(P)]u8 align(@alignOf(P)) = undefined;
    const s: *P = @ptrCast(&buffer);
    s.p = s;
}

zig test .zig outputs a brief progress display, then crashes (exits with non-0 exit code).

Expected Behavior

Certainly the compiler shouldn't crash. The same code is valid at runtime (if you change the comptime block to a test block), so ideally I'd like it to work at comptime.

I agree that it's tricky though, the compiler would have to implement some serialization / bit representation of comptime-only pointers (if that doesn't exist yet). To be fully deterministic it should also assert that none of the bits used here escape to program runtime, i.e. the memory should not be allowed into runtime. (Though as a further improvement, if all affected bits have been overwritten, it should then again be allowed into runtime.) Because of that complexity I expect the most likely short-term resolution to be a compile error that blocks this code from executing at comptime. A workaround would have to check @inComptime() and replace the affected logic with manual bookkeeping of some sort.

Note that equivalent reinterpreting code using packed union leads to the other union fields being treated as a runtime value, and only crashes if trying to read these bits at runtime:

const P2 = packed union {
    buffer: @Type(.{ .Int = .{
        .signedness = .unsigned,
        .bits = 8 * @sizeOf(*P2),
    } }),
    p: *P2,
};
test {
    comptime var b: P2 = undefined;
    const s = &b;
    s.p = s;
    //@compileLog(s.buffer); //@as(u64, [runtime value])
    comptime {
        //_ = s.buffer + 2; //treated as runtime value, correctly triggers `error: unable to evaluate comptime expression`
    }
    var x = s.buffer; //crashes
    _ = &x;
}
Vexu commented 1 month ago

That reduction looks like a duplicate of #20765, not sure if fixing that would fix the other example.

rohlem commented 1 month ago

Might be related, although from a quick glance I don't see the code in the other issue writing a pointer value to a reinterpreted buffer. Regardless, the repro here is shorter and doesn't depend on std, so I assume it would be easier to track down/reduce, for what it's worth.