ziglang / zig

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

run time panic when write to AnyWriter #20540

Closed shaozi closed 1 week ago

shaozi commented 2 weeks ago

Zig Version

0.13.0

Steps to Reproduce and Observed Behavior

  1. mkdir bug && cd bug && zig init
  2. use this main.zig:
    
    const std = @import("std");

pub fn main() !void { const stdout = std.io.getStdOut().writer(); var s = init(stdout); try s.write('x'); }

fn init(w: anytype) *S { var s = S{ .w = w.any() }; return &s; } const S = struct { w: std.io.AnyWriter,

pub fn write(self: *S, c: u8) !void {
    try self.w.writeByte(c);
}

};

3. zig build run

```sh
Bus error at address 0xc41336465a77002e
Panicked during a panic. Aborting.
run
└─ run bug failure
error: the following command terminated unexpectedly:
....../bug/zig-out/bin/bug 
Build Summary: 5/7 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run bug failure
error: the following build command failed with exit code 1:
....../bug/.zig-cache/o/8ac3b3afd11d90cff40b6e19c949ba84/build /opt/homebrew/Cellar/zig/0.13.0/bin/zig ....../bug ....../bug/.zig-cache....../.cache/zig --seed 0xdbdbd3f2 -Zba845a418ca33012 run

Expected Behavior

should not crash and output x to stdout.

Also on 0.12.0, the error message has possible race condition in it. So it could be the reason.

leecannon commented 2 weeks ago

return &s; is a pointer to stack memory, after leaving init s is invalid and its memory is reused.

Trevor-Strong commented 2 weeks ago

The S pointer you're returning is to a local the goes out of scope when the function exits

Also writer.any() takes a pointer to the writer, which also goes out of scope when the function exits.

shaozi commented 2 weeks ago

Got it. I see. Changed to this makes it work:

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    var s = S{};
    s.init(stdout);
    try s.write('x');
}

const S = struct {
    w: std.io.AnyWriter = undefined,

    pub fn init(self: *S, w: anytype) void {
        self.w = w.any();
    }
    pub fn write(self: *S, c: u8) !void {
        try self.w.writeByte(c);
    }
};

Thanks!

nektro commented 2 weeks ago

having struct fields default to undefined is a big code smell and in this case i'd drop the init method and do const s = S{ .w = stdout.any() };

squeek502 commented 1 week ago

In the future, please direct things like this to one of the community spaces first.