Skip to content

syntax quirks allow leaking values outside their scope, resulting in dangling pointers without pointers #21357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Fri3dNstuff opened this issue Sep 9, 2024 · 1 comment
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@Fri3dNstuff
Copy link
Contributor

Zig Version

0.14.0-dev.1472+3929cac15

Steps to Reproduce and Observed Behavior

compile and run the following program:

const std = @import("std");

pub fn main() void {
    (blk: {
        defer std.debug.print("block exit, x goes out of scope\n", .{});
        var x: usize = undefined;
        break :blk x;
    }) = foo();
}

fn foo() usize {
    std.debug.print("foo evaluated\n", .{});
    return 0;
}

the code is accepted by the compiler, and produces the following output:

block exit, x goes out of scope
foo evaluated

Expected Behavior

the compiler should either reject the program, or the proposed semantics for the language spec be changed.

from my understanding, though current compiler behaviour is more liberal, the proposed language spec declares all local runtime variables as scope bound - pointers to them being invalidated the moment execution leaves their enclosing block. that means that the above program assigns to x after it has died - that, without any explicit pointer types.

this seems like a simple oversight in the interaction between Zig's unusual lvalue propagation rules and variables' lifetimes.

the issue might be deemed as too much of an edge-case to fix, however, I feel like such code as the one above, that has no syntactic indications of the usage of pointers, should not be able to express lifetime errors.

this is also a greater issue than the single program above: because references to rvalues live longer than their enclosing scope, combined with Zig's lvalue propagation rules, programmers can easily accidentally create a reference to an lvalue instead of a reference to an rvalue - resulting in a pointer with an unexpectedly short lifetime, referencing memory that may be mutated.

@Fri3dNstuff Fri3dNstuff added the bug Observed behavior contradicts documented or intended behavior label Sep 9, 2024
@mlugg
Copy link
Member

mlugg commented Sep 9, 2024

This is working as designed. The following 3 functions are exactly equivalent in Zig:

fn f() void {
    (blk: {
        var x: u32 = undefined;
        break :blk x;
    }) = something;
}

fn g() void {
    const ptr = &(blk: {
        var x: u32 = undefined;
        break :blk x;
    });
    ptr.* = something;
}

fn h() void {
    const ptr = blk: {
        var x: u32 = undefined;
        break :blk &x;
    };
    ptr.* = something;
}

Looking solely at the h example, it would indeed be desirable for this to be a compile error or runtime safety panic, but this is effectively just a sub-proposal of #5725. Since f and g are exactly eqivalent, the same applies there too. There's no point going to lengths to disallow f/g but allow h.

@mlugg mlugg closed this as not planned Won't fix, can't repro, duplicate, stale Sep 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior
Projects
None yet
Development

No branches or pull requests

2 participants