zig icon indicating copy to clipboard operation
zig copied to clipboard

Coercion of tuple pointer to slice violates aliasing and lifetime expectations

Open topolarity opened this issue 3 years ago • 2 comments

Zig Version

0.10.0-dev.1246+e67c756b91

Steps to Reproduce and Observed Behavior

const std = @import("std");
const expect = std.testing.expect;

fn clobberStack() void { var array: [20]u32 = undefined; _ = array[1]; }
fn asSlice(comptime T: type, ptr: anytype) []const T {
    switch (@typeInfo(@TypeOf(ptr))) {
        .Pointer => return @as([]const T, ptr),
        else => unreachable,
    }
}

test "implicit tuple-ptr to array-ptr cast refers to temporary with shorter lifetime" {
    const queue = try std.heap.page_allocator.create(std.meta.Tuple(&[_]type{ u32, u32 }));
    queue.* = .{ 1, 2 };

    // Unlike every other ptr->ptr cast in Zig, slice ends up referring to a temporary 
    // in asSlice rather than to the heap-allocated `queue`
    var slice = asSlice(u32, queue);
    _ = clobberStack();

    // Surely `slice` is still valid here? Nope.
    // This fails:
    try expect(std.mem.eql(u32, slice, &[_]u32{1, 2}));
    
    //  The conversion in `asSlice` implicitly shortened the lifetime of the cast result
}

test "aliasing not respected" {
    var x: usize = 1;
    var a = .{ x, x + 1, x + 2, x + 3 };
    var b: []const usize = &a;
    
    a[0] = 7; // Changes to a are not reflected in b

    try expect(a[0] == b[0]); // fails
}

Both tests fail

Expected Behavior

Both of these behaviors are incongruous with how reference semantics typically work in Zig. In every other pointer-to-pointer cast (implicit or explicit), aliasing is respected and the valid lifetime of the created pointer is the same as the pointer being cast from.

topolarity avatar Oct 28 '22 15:10 topolarity

Wanted to file this to follow-up on the discussion in https://github.com/ziglang/zig/pull/12826

A possible solution was proposed in that PR: Make this a question of type inference rather than coercion, by allowing &.{...} to coerce to the type of its result location, but not allowing this for any tuple pointer created after the fact.

In that case, there is no tuple pointer to alias against at all. We are essentially inferring that .{...} should have been an array from the beginning.

topolarity avatar Oct 28 '22 15:10 topolarity

These concerns were brought up when this coercion was initially implemented and if only allowing &.{} gets rid of them then we should go with that since &.{} is the only intended use case in the first place (at least IMO).

Vexu avatar Nov 03 '22 14:11 Vexu

Note that as of #16512, this coercion is not used for the &.{ ... } case. Also note that the accepted proposal #16865 eliminates anonymous struct types entirely, so implicitly removes this coercion.

mlugg avatar Nov 11 '23 23:11 mlugg