From e69d19d1ee06ebb50af191d2911355d817eb1249 Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Sun, 17 May 2026 19:11:16 +0200 Subject: [PATCH] Sema: allow direct dereference of comptime-known slices A comptime-known `[]T` should behave as similarly as possible to a comptime- known `*[n]T`. It is now possible to directly dereference both to an array. Also adds checks for slices with undefined length in code paths where a slice is dereferenced as an array which would previously crash. --- lib/std/zig/Zir.zig | 2 +- src/Sema.zig | 68 ++++++++++--------- src/Zcu/PerThread.zig | 19 ++++++ test/behavior/slice.zig | 44 ++++++++++++ .../deref_slice_and_get_len_field.zig | 2 +- .../deref_slice_with_undef_len.zig | 23 +++++++ .../compile_errors/dereference_slice.zig | 2 +- 7 files changed, 126 insertions(+), 34 deletions(-) create mode 100644 test/cases/compile_errors/deref_slice_with_undef_len.zig diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 1ec523e8694eb854692a94fbe560e25b11046a34..d79251c7db366d256a43487c0722c6810050ab31 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -584,7 +584,7 @@ pub const Inst = struct { /// containing the instruction. /// Uses the `un_tok` union field. ref, - /// Implements the dereference operand (`.*`). Checks that operand is a pointer + /// Implements the dereference operator (`.*`). Checks that operand is a pointer /// that supports being directly dereferenced. /// Uses the `un_node` union field. deref, diff --git a/src/Sema.zig b/src/Sema.zig index ba5ad7eb1566c69ee5377a8269d3ac512e073d31..c81b50fb18214901ef61e9a4a8e07903fd0240c6 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2352,6 +2352,10 @@ pub fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc, vector_in }); } +pub fn failWithUndefSliceLen(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError { + return sema.fail(block, src, "use of slice with undefined length here causes illegal behavior", .{}); +} + pub fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError { return sema.fail(block, src, "division by zero here causes illegal behavior", .{}); } @@ -3113,9 +3117,14 @@ fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai try sema.validateDeref(block, src, operand, operand_ty); const ptr_info = operand_ty.ptrInfo(zcu); - return switch (ptr_info.flags.size) { - .many, .slice => unreachable, // cannot be dereferenced - .c => single_ptr: { + return single_ptr: switch (ptr_info.flags.size) { + .many => unreachable, // cannot be dereferenced directly + .slice => { + const slice_val = sema.resolveValue(operand).?; + const slice = zcu.intern_pool.indexToKey(slice_val.toIntern()).slice; + break :single_ptr .fromValue(try pt.sliceToArrayPtr(slice)); + }, + .c => { const single_ptr_ty = try pt.ptrType(p: { var p = ptr_info; p.flags.size = .one; @@ -3149,18 +3158,26 @@ fn validateDeref( ) CompileError!void { const pt = sema.pt; const zcu = pt.zcu; + const ip = &zcu.intern_pool; if (ty.zigTypeTag(zcu) != .pointer) { return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{ty.fmt(pt)}); - } else switch (ty.ptrSize(zcu)) { - .one, .c => {}, + } + const size = ty.ptrSize(zcu); + switch (size) { .many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{f}'", .{ty.fmt(pt)}), - .slice => return sema.fail(block, src, "index syntax required for slice type '{f}'", .{ty.fmt(pt)}), + .one, .c, .slice => {}, } if (sema.resolveValue(ref)) |val| { // Error for deref of undef pointer, unless the pointee is OPV in which case it's legal. if (val.isUndef(zcu) and ty.childType(zcu).classify(zcu) != .one_possible_value) { return sema.fail(block, src, "cannot dereference undefined value", .{}); } + // We need a defined slice length for the array type the slice should be dereferenced to. + if (size == .slice and ip.indexToKey(val.toIntern()).slice.len == .undef_usize) { + return sema.fail(block, src, "cannot dereference slice with undefined length", .{}); + } + } else if (size == .slice) { + return sema.fail(block, src, "index syntax required to access runtime-known slice", .{}); } } @@ -13570,14 +13587,14 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu)) try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src else if (lhs_ty.isSlice(zcu)) - try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :rs lhs_src + try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src else lhs_val; const rhs_sub_val = if (rhs_ty.isSinglePointer(zcu)) try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src else if (rhs_ty.isSlice(zcu)) - try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val) orelse break :rs rhs_src + try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src else rhs_val; @@ -30940,8 +30957,11 @@ fn analyzeLoad( }; if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { - if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |elem_val| { - return Air.internedToRef(elem_val.toIntern()); + if (switch (ptr_ty.ptrSize(zcu)) { + .slice => try sema.maybeDerefSliceAsArray(block, src, ptr_val, ptr_ty), + else => try sema.pointerDeref(block, src, ptr_val, ptr_ty), + }) |elem_val| { + return .fromValue(elem_val); } } @@ -34558,7 +34578,7 @@ fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool { .slice => { // If the slice contents are runtime-known, reification will fail later on with a // specific error message. - const arr = try sema.maybeDerefSliceAsArray(block, src, val) orelse return false; + const arr = try sema.maybeDerefSliceAsArray(block, src, val, val.typeOf(zcu)) orelse return false; return sema.anyUndef(block, src, arr); }, .aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| { @@ -34599,7 +34619,7 @@ fn derefSliceAsArray( /// being comptime-resolved is that the block is being comptime-evaluated. reason: ?ComptimeReason, ) CompileError!Value { - return try sema.maybeDerefSliceAsArray(block, src, slice_val) orelse { + return try sema.maybeDerefSliceAsArray(block, src, slice_val, slice_val.typeOf(sema.pt.zcu)) orelse { return sema.failWithNeededComptime(block, src, reason); }; } @@ -34612,37 +34632,23 @@ fn maybeDerefSliceAsArray( block: *Block, src: LazySrcLoc, slice_val: Value, + slice_ty: Type, ) CompileError!?Value { const pt = sema.pt; const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const slice_ty = slice_val.typeOf(zcu); - assert(slice_ty.zigTypeTag(zcu) == .pointer); switch (slice_ty.ptrInfo(zcu).flags.size) { .slice => {}, .one => return sema.pointerDeref(block, src, slice_val, slice_ty), .many, .c => unreachable, } - const slice = switch (ip.indexToKey(slice_val.toIntern())) { + const slice = switch (zcu.intern_pool.indexToKey(slice_val.toIntern())) { .undef => return sema.failWithUseOfUndef(block, src, null), .slice => |slice| slice, else => unreachable, }; - const elem_ty = Type.fromInterned(slice.ty).childType(zcu); - const len = Value.fromInterned(slice.len).toUnsignedInt(zcu); - const array_ty = try pt.arrayType(.{ - .child = elem_ty.toIntern(), - .len = len, - }); - const ptr_ty = try pt.ptrType(p: { - var p = Type.fromInterned(slice.ty).ptrInfo(zcu); - p.flags.size = .one; - p.child = array_ty.toIntern(); - p.sentinel = .none; - break :p p; - }); - const casted_ptr = try pt.getCoerced(Value.fromInterned(slice.ptr), ptr_ty); - return sema.pointerDeref(block, src, casted_ptr, ptr_ty); + if (slice.len == .undef_usize) return sema.failWithUndefSliceLen(block, src); + const casted_ptr = try pt.sliceToArrayPtr(slice); + return sema.pointerDeref(block, src, casted_ptr, casted_ptr.typeOf(zcu)); } fn analyzeUnreachable(sema: *Sema, block: *Block, src: LazySrcLoc, safety_check: bool) !void { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 4092f27b8b7ca16a56b412805779584b801561f9..bf72a73851b39910c3d43250a286efa98f780b20 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -3498,6 +3498,25 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(gpa, io, name)); } +/// Asserts that `slice.len` is *not* undef. +pub fn sliceToArrayPtr(pt: Zcu.PerThread, slice: InternPool.Key.Slice) Allocator.Error!Value { + const zcu = pt.zcu; + const slice_info = Type.fromInterned(slice.ty).ptrInfo(zcu); + const array_ty = try pt.arrayType(.{ + .len = Value.fromInterned(slice.len).toUnsignedInt(zcu), + .child = slice_info.child, + .sentinel = slice_info.sentinel, + }); + const ptr_ty = try pt.ptrType(ptr_info: { + var ptr_info = slice_info; + ptr_info.flags.size = .one; + ptr_info.child = array_ty.toIntern(); + ptr_info.sentinel = .none; + break :ptr_info ptr_info; + }); + return pt.getCoerced(.fromInterned(slice.ptr), ptr_ty); +} + /// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed. /// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry. fn lockAndClearFileCompileError(pt: Zcu.PerThread, file_index: Zcu.File.Index, file: *Zcu.File) void { diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index f1efaa9f43686a0d3570c7e33c335a0f1e6c84e3..7021b76c69dff0f63e1bb26165aa5fae57e6a1d6 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -1089,3 +1089,47 @@ test "slice field alignment" { var arr: [10]u8 = @splat(0); try S.doTheTest(&&arr); } + +test "directly deref slice with comptime-known length" { + { + const slice: []const u16 = &.{ 1, 2, 3 }; + const array = slice.*; + + comptime assert(@TypeOf(array) == [3]u16); + comptime assert(array[0] == 1); + comptime assert(array[1] == 2); + comptime assert(array[2] == 3); + } + { + const slice: [:0]const u16 = &.{ 1, 2, 3 }; + const array = slice.*; + + comptime assert(@TypeOf(array) == [3:0]u16); + comptime assert(array[0] == 1); + comptime assert(array[1] == 2); + comptime assert(array[2] == 3); + comptime assert(array[3] == 0); + } +} + +test "address of dereferenced slice is array pointer" { + { + const slice: []const u16 = &.{ 1, 2, 3 }; + const array_ptr = &slice.*; + + comptime assert(@TypeOf(array_ptr) == *const [3]u16); + comptime assert(array_ptr[0] == 1); + comptime assert(array_ptr[1] == 2); + comptime assert(array_ptr[2] == 3); + } + { + const slice: [:0]const u16 = &.{ 1, 2, 3 }; + const array_ptr = &slice.*; + + comptime assert(@TypeOf(array_ptr) == *const [3:0]u16); + comptime assert(array_ptr[0] == 1); + comptime assert(array_ptr[1] == 2); + comptime assert(array_ptr[2] == 3); + comptime assert(array_ptr[3] == 0); + } +} diff --git a/test/cases/compile_errors/deref_slice_and_get_len_field.zig b/test/cases/compile_errors/deref_slice_and_get_len_field.zig index 57459f488eca026cb7a4f27bc82d7d2ef13e4c5b..8917259ccbce1c5c31d13ae9722cc38d6fb7b03f 100644 --- a/test/cases/compile_errors/deref_slice_and_get_len_field.zig +++ b/test/cases/compile_errors/deref_slice_and_get_len_field.zig @@ -6,4 +6,4 @@ export fn entry() void { // error // -// :3:10: error: index syntax required for slice type '[]u8' +// :3:10: error: index syntax required to access runtime-known slice diff --git a/test/cases/compile_errors/deref_slice_with_undef_len.zig b/test/cases/compile_errors/deref_slice_with_undef_len.zig new file mode 100644 index 0000000000000000000000000000000000000000..e3dcd2e158719cff1b14213c5b1cc2c05ef333e4 --- /dev/null +++ b/test/cases/compile_errors/deref_slice_with_undef_len.zig @@ -0,0 +1,23 @@ +export fn entry2() void { + comptime var slice: []const u16 = &.{ 1, 2, 3 }; + slice.len = undefined; + _ = slice.*; +} + +export fn entry3() void { + comptime var slice: []const u16 = &.{ 1, 2, 3 }; + slice.len = undefined; + _ = &slice.*; +} + +export fn entry4() void { + comptime var slice: []const u8 = "hello"; + slice.len = undefined; + @compileError(slice); +} + +// error +// +// :4:14: error: cannot dereference slice with undefined length +// :10:15: error: cannot dereference slice with undefined length +// :16:19: error: use of slice with undefined length here causes illegal behavior diff --git a/test/cases/compile_errors/dereference_slice.zig b/test/cases/compile_errors/dereference_slice.zig index e2221ea609bf5c36a0304360dabd2e35aedd998f..73db0ff950e63718421846e645aba6ac7f19a23f 100644 --- a/test/cases/compile_errors/dereference_slice.zig +++ b/test/cases/compile_errors/dereference_slice.zig @@ -7,4 +7,4 @@ comptime { // error // -// :2:13: error: index syntax required for slice type '[]i32' +// :2:13: error: index syntax required to access runtime-known slice -- 2.54.0