zig icon indicating copy to clipboard operation
zig copied to clipboard

Mutable tuples coerce un-checked into mutable slices

Open InKryption opened this issue 2 years ago • 2 comments

Zig Version

0.10.0-dev.3981+60678f5ba

Steps to Reproduce

const std = @import("std");

pub fn main() void {
    // add explicit type annotation, like
    // `std.meta.Tuple(&.{ u7, u7, u7, u7 })`,
    // to avoid the segfault triggered by the commented line below.
    var a = .{ 1, 2, 3, 4 };
    var b: []u8 = &a;
    // b[0] = 7; // <- un-comment to observe a segfault.
    std.debug.print(
        \\a: (@{x}){any}
        \\b: (@{x}){any}
        \\
    , .{ @ptrToInt(&a), a, @ptrToInt(b.ptr), b });
}

Expected Behavior

I would expect only comptime tuples to coerce to arrays this way, or at most, that the slice would alias the tuple memory when the tuple contains fields with a homogeneous field type, also equal to the type of the slice, instead of creating an equivalent temporary.

Actual Behavior

Running with zig run:

Output with var a: std.meta.Tuple(&[_]type{u8} ** 4) = .{ 1, 2, 3, 4 };:

a: (@7ffe6db49f18){ 1, 2, 3, 4 }
b: (@7ffe6db49f34){ 1, 2, 3, 4 }

Output with var a: std.meta.Tuple(&[_]type{u7} ** 4) = .{ 1, 2, 3, 4 };:

a: (@7fff80bacae8){ 1, 2, 3, 4 }
b: (@7fff80bacb04){ 1, 2, 3, 4 }

Output with var a: std.meta.Tuple(&[_]type{u8} ** 4) = .{ 1, 2, 3, 4 }; & b[0] = 7;:

a: (@7ffc9c319158){ 1, 2, 3, 4 }
b: (@7ffc9c319174){ 7, 2, 3, 4 }

Output with var a = .{ 1, 2, 3, 4 };:

a: (@aaaaaaaaaaaaaaaa){ 1, 2, 3, 4 }
b: (@20bb28){ 1, 2, 3, 4 }

Output with var b: []u32 = &a;:

a: (@aaaaaaaaaaaaaaaa){ 1, 2, 3, 4 }
b: (@208690){ 1, 2, 3, 4 }

Output with var a: std.meta.Tuple(&[_]type{u7} ** 4) = .{ 1, 2, 3, 4 };, var b: []u32 = &a; & b[0] = 7;:

a: (@7fff8cbcd1d8){ 1, 2, 3, 4 }
b: (@7fff8cbcd200){ 7, 2, 3, 4 }

Output with var a = .{ 1, 2, 3, 4 }; & b[0] = 7;:

Segmentation fault at address 0x20bb28
src/main.zig:6:5: 0x20db72 in main (main)
    b[0] = 7;
    ^
{zig install}/files/lib/std/start.zig:568:22: 0x20d41d in posixCallMainAndExit (main)
            root.main();
                     ^
{zig install}/files/lib/std/start.zig:340:5: 0x20ce22 in _start (main)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
Aborted (core dumped)

InKryption avatar Sep 12 '22 10:09 InKryption

Extra observation: when a's fields aren't comprised of comptime_int (has an explicit tuple type), and thus is placed on the stack, the distance between the memory address of a, and the address pointed to by b acts like this:

  • a.len == 0: 0
  • a.len == 1: 31
  • a.len == 2: 30
  • a.len == 3 or a.len == 4: 28
  • a.len >= 5 and a.len <= 8: 32
  • a.len >= 9 and a.len <= 16: 48
  • a.len >= 17 and a.len <= 24: 64
  • a.len >= 25 and a.len <= 32: 80

and then seemingly continues like that, the difference incrementing by 16 for every 7 additional elements in a. This seems to indicate that there is indeed a temporary array being created for b to point to in this case, but for some reason not when a is comprised of comptime_ints.

InKryption avatar Sep 12 '22 11:09 InKryption

Another observation: stage1 also allows coercion from a mutable tuple to a mutable slice, apparently.

InKryption avatar Sep 12 '22 17:09 InKryption