zig icon indicating copy to clipboard operation
zig copied to clipboard

Segmentation fault when calling function pointer directly from packed struct

Open mvenditto opened this issue 7 months ago • 0 comments

Zig Version

0.13.0

Steps to Reproduce and Observed Behavior

Calling the function pointer directly from the packed struct in the example causes a segmentation fault.

Is this an expected undefined behavior due to a misaligned pointer or the compiler should take care of it?

Example

pub const X = packed struct {
    b: bool = false,
    func: *const fn () void,

    const Self = @This();

    pub fn init(
        func: *const fn () void,
    ) !*Self {
        const s = try std.heap.page_allocator.create(Self);
        s.func = func;
        return s;
    }

    pub fn foo(self: *Self) void {
        self.func();
    }
};

Test:

test "func pointer in packed struct" {
    const S = struct {
        pub fn foo() void {
            std.debug.print("Hi!\n", .{});
        }
    };

    std.debug.print("funcOffset={d}, bOffset={d}\n", .{
        @offsetOf(X, "func"),
        @offsetOf(X, "b"),
    });

    const x = try X.init(S.foo);

    x.foo();
}

Output:

funcOffset=0, bOffset=0
Segmentation fault at address 0x12229a0
C:\Users\dev\Source\Repos\repro\src\test_tmp.zig:31:18: 0x911507 in foo (test.exe.obj)
        self.func();
                 ^
C:\Users\dev\Source\Repos\repro\src\test_tmp.zig:49:10: 0x9112dd in test.Test (test.exe.obj)
    x.foo();
         ^
C:\Users\dev\AppData\Local\Microsoft\WinGet\Packages\zig.zig_Microsoft.Winget.Source_8wekyb3d8bbwe\zig-windows-x86_64-0.13.0\lib\compiler\test_runner.zig:157:25: 0x91995d in mainTerminal (test.exe.obj)
        if (test_fn.func()) |_| {
                        ^
C:\Users\dev\AppData\Local\Microsoft\WinGet\Packages\zig.zig_Microsoft.Winget.Source_8wekyb3d8bbwe\zig-windows-x86_64-0.13.0\lib\compiler\test_runner.zig:37:28: 0x9117a8 in main (test.exe.obj)
        return mainTerminal();
                           ^
C:\Users\dev\AppData\Local\Microsoft\WinGet\Packages\zig.zig_Microsoft.Winget.Source_8wekyb3d8bbwe\zig-windows-x86_64-0.13.0\lib\std\start.zig:363:53: 0x911523 in WinStartup (test.exe.obj)
    std.os.windows.ntdll.RtlExitUserProcess(callMain());
                                                    ^
???:?:?: 0x7ffc4ef3257c in ??? (KERNEL32.DLL)
???:?:?: 0x7ffc4fbcaf27 in ??? (ntdll.dll)
error: the following test command failed with exit code 3:

Swapping the b and func fields makes the segmentation fault go away:

pub const X = packed struct {
-   b: bool = false,
-   func: *const fn () void,
+   func: *const fn () void,
+   b: bool = false,

    const Self = @This();

    pub fn init(
        func: *const fn () void,
    ) !*Self {
        const s = try std.heap.page_allocator.create(Self);
        s.func = func;
        return s;
    }

    pub fn foo(self: *Self) void {
        self.func();
    }
};

Output 2

funcOffset=0, bOffset=8
Hi!
All 1 tests passed.

also adding padding after b makes it work:

b: bool = false,
+ _padd: std.meta.Int(.unsigned, @bitSizeOf(*const fn () void) - @bitSizeOf(bool)),
func: *const fn () void,

A few other observation emerged from this short discussion on reddit:

  • Replacing the function pointer with a pointer to any other value and dereferencing it directly seems to work fine.
  • assigning self.func to a local variable and calling that instead of self.func() works (e.g const a = z.func; a();)
  • changing the alignment of the func pointer also works (e.g (@as(*const fn () void, @alignCast(self.func)))();)

Thanks!

Expected Behavior

Being able to call the function pointer directly from the packed struct.

mvenditto avatar Jul 08 '24 10:07 mvenditto