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..797bc299c34d8831c6a287106c35878a1d8f8152 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", .{}); } } @@ -28241,9 +28258,94 @@ fn coerceExtra( }, else => {}, }, - .one => {}, + // []T to *[n]T + .one => slice_to_array_ptr: { + if (!inst_ty.isSlice(zcu)) break :slice_to_array_ptr; + if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :slice_to_array_ptr; + const array_ty: Type = .fromInterned(dest_info.child); + if (array_ty.zigTypeTag(zcu) != .array) break :slice_to_array_ptr; + const inst_val = maybe_inst_val orelse { + if (!opts.report_err) return error.NotCoercible; + return sema.fail( + block, + inst_src, + "coercion from slice to array pointer type '{f}' requires length to be known at compile-time", + .{dest_ty.fmt(pt)}, + ); + }; + + const slice: InternPool.Key.Slice = slice: { + switch (ip.indexToKey(inst_val.toIntern())) { + .undef => {}, + .slice => |slice| if (slice.len != .undef_usize) break :slice slice, + else => unreachable, + } + if (!opts.report_err) return error.NotCoercible; + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(inst_src, "slice with undefined length cannot cast into array pointer type '{f}'", .{ + dest_ty.fmt(pt), + }); + errdefer msg.destroy(gpa); + try sema.errNote(inst_src, msg, "length of slice must be defined and match length of array type", .{}); + break :msg msg; + }); + }; + const slice_len = Value.fromInterned(slice.len).toUnsignedInt(zcu); + if (array_ty.arrayLen(zcu) != slice_len) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(inst_src, "slice of length {d} cannot cast into array pointer type '{f}'", .{ + slice_len, dest_ty.fmt(pt), + }); + errdefer msg.destroy(gpa); + try sema.errNote(inst_src, msg, "length of slice must match length of array type", .{}); + break :msg msg; + }); + } + + const inst_elem_ty = inst_ty.childType(zcu); + const dest_elem_ty = array_ty.childType(zcu); + const dest_is_mut = !dest_info.flags.is_const; + switch (try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, dest_is_mut, target, dest_ty_src, inst_src, null)) { + .ok => {}, + else => |elem_res| { + in_memory_result = .{ .ptr_child = .{ + .child = try elem_res.dupe(sema.arena), + .actual = inst_elem_ty, + .wanted = dest_elem_ty, + } }; + break :slice_to_array_ptr; + }, + } + + if (array_ty.sentinel(zcu)) |array_sentinel| { + if (inst_ty.sentinel(zcu)) |slice_sentinel| { + if (array_sentinel.toIntern() != + (try pt.getCoerced(slice_sentinel, dest_elem_ty)).toIntern()) + { + in_memory_result = .{ .ptr_sentinel = .{ + .actual = slice_sentinel, + .wanted = array_sentinel, + .ty = dest_elem_ty, + } }; + break :slice_to_array_ptr; + } + } else { + in_memory_result = .{ .ptr_sentinel = .{ + .actual = .@"unreachable", + .wanted = array_sentinel, + .ty = dest_elem_ty, + } }; + break :slice_to_array_ptr; + } + } + + const array_ptr = try pt.sliceToArrayPtr(slice); + return sema.coerceCompatiblePtrs(block, dest_ty, .fromValue(array_ptr), inst_src); + }, .slice => to_slice: { if (inst_ty.zigTypeTag(zcu) == .array) { + if (!opts.report_err) return error.NotCoercible; return sema.fail( block, inst_src, @@ -28271,6 +28373,7 @@ fn coerceExtra( // pointer to tuple to slice if (!dest_info.flags.is_const) { + if (!opts.report_err) return error.NotCoercible; const err_msg = err_msg: { const err_msg = try sema.errMsg(inst_src, "cannot cast pointer to tuple to '{f}'", .{dest_ty.fmt(pt)}); errdefer err_msg.destroy(sema.gpa); @@ -28366,6 +28469,7 @@ fn coerceExtra( if (maybe_inst_val) |val| { const result_val = try val.floatCast(dest_ty, pt); if (!val.eql(try result_val.floatCast(inst_ty, pt), inst_ty, zcu)) { + if (!opts.report_err) return error.NotCoercible; return sema.fail( block, inst_src, @@ -28423,12 +28527,15 @@ fn coerceExtra( break :fits result_big_int.toConst().eql(operand_big_int); }, }; - if (!fits) return sema.fail( - block, - inst_src, - "type '{f}' cannot represent integer value '{f}'", - .{ dest_ty.fmt(pt), val.fmtValue(pt) }, - ); + if (!fits) { + if (!opts.report_err) return error.NotCoercible; + return sema.fail( + block, + inst_src, + "type '{f}' cannot represent integer value '{f}'", + .{ dest_ty.fmt(pt), val.fmtValue(pt) }, + ); + } return .fromValue(result_val); }, else => {}, @@ -28439,6 +28546,7 @@ fn coerceExtra( const val = sema.resolveValue(inst).?; const string = zcu.intern_pool.indexToKey(val.toIntern()).enum_literal; const field_index = dest_ty.enumFieldIndex(string, zcu) orelse { + if (!opts.report_err) return error.NotCoercible; return sema.fail(block, inst_src, "no field named '{f}' in enum '{f}'", .{ string.fmt(&zcu.intern_pool), dest_ty.fmt(pt), }); @@ -30940,8 +31048,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), + else => try sema.pointerDeref(block, src, ptr_val, ptr_ty), + }) |elem_val| { + return .fromValue(elem_val); } } @@ -34615,7 +34726,6 @@ fn maybeDerefSliceAsArray( ) 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) { @@ -34623,26 +34733,14 @@ fn maybeDerefSliceAsArray( .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..247c3d1be8f3a15ab8b53f9d0a47f2bdea02cc4f 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -1089,3 +1089,85 @@ 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); + } +} + +test "coerce slice with comptime-known length to array pointer" { + { + const slice: []const u16 = &.{ 1, 2, 3 }; + const array_ptr: *const [3]u16 = slice; + + 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: *const [3:0]u16 = slice; + + comptime assert(array_ptr[0] == 1); + comptime assert(array_ptr[1] == 2); + comptime assert(array_ptr[2] == 3); + comptime assert(array_ptr[3] == 0); + } + { + const slice: [:0]const u16 = &.{ 1, 2, 3 }; + const array_ptr: *const [3]u16 = slice; + + comptime assert(array_ptr[0] == 1); + comptime assert(array_ptr[1] == 2); + comptime assert(array_ptr[2] == 3); + } +} + +test "modify slice through coerced array pointer" { + comptime { + var array: [3]u16 = .{ 1, 2, 3 }; + const slice: []u16 = &array; + const array_ptr: *[3]u16 = slice; + array_ptr[2] = 0; + assert(slice[2] == 0); + } +} diff --git a/test/cases/compile_errors/coerce_pointers_with_uncoercable_child_pointers.zig b/test/cases/compile_errors/coerce_pointers_with_uncoercable_child_pointers.zig index bef21b2777ff5503899741f812dcbf7bc3b61b68..77f48e656c0a81c94353a555542a4b3e73ace4e8 100644 --- a/test/cases/compile_errors/coerce_pointers_with_uncoercable_child_pointers.zig +++ b/test/cases/compile_errors/coerce_pointers_with_uncoercable_child_pointers.zig @@ -28,6 +28,16 @@ export fn entry5() void { _ = q; } +export fn entry6(p: **[3]u8) void { + const q: *[]u8 = p; + _ = q; +} + +export fn entry7(p: *[]u8) void { + const q: **[3]u8 = p; + _ = q; +} + // error // // :3:22: error: expected type '**i32', found '**u32' @@ -50,3 +60,7 @@ export fn entry5() void { // :27:24: note: pointer type child '*[1:42]u8' cannot cast into pointer type child '*[1]u8' // :27:24: note: pointer type child '[1:42]u8' cannot cast into pointer type child '[1]u8' // :27:24: note: source array cannot be guaranteed to maintain '42' sentinel +// :32:22: error: expected type '*[]u8', found '**[3]u8' +// :32:22: note: pointer type child '*[3]u8' cannot cast into pointer type child '[]u8' +// :37:24: error: expected type '**[3]u8', found '*[]u8' +// :37:24: note: pointer type child '[]u8' cannot cast into pointer type child '*[3]u8' 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 diff --git a/test/cases/compile_errors/slice_to_array_pointer.zig b/test/cases/compile_errors/slice_to_array_pointer.zig new file mode 100644 index 0000000000000000000000000000000000000000..9e4750a3645820240e505ab6d471be99d18877cc --- /dev/null +++ b/test/cases/compile_errors/slice_to_array_pointer.zig @@ -0,0 +1,70 @@ +export fn entry1() void { + var array: [2]u16 = .{ 1, 2 }; + const slice: []const u16 = &array; + foo(slice); +} + +export fn entry2() void { + const slice: []const u16 = undefined; + foo(slice); +} + +export fn entry3() void { + comptime var slice: []const u16 = &.{ 1, 2 }; + slice.len = undefined; + foo(slice); +} + +export fn entry4() void { + const slice: []const u16 = &.{ 1, 2, 3 }; + foo(slice); +} + +export fn entry5() void { + const slice: []const u8 = &.{ 1, 2 }; + foo(slice); +} + +fn foo(x: *const [2]u16) void { + _ = x; +} + +export fn entry6() void { + const slice: [:0]const u16 = &.{ 1, 2, 3 }; + bar(slice); +} + +export fn entry7() void { + const slice: [:1]const u16 = &.{ 1, 2 }; + bar(slice); +} + +export fn entry8() void { + const slice: []const u16 = &.{ 1, 2 }; + bar(slice); +} + +fn bar(x: *const [2:0]u16) void { + _ = x; +} + +// error +// +// :4:9: error: coercion from slice to array pointer type '*const [2]u16' requires length to be known at compile-time +// :9:9: error: slice with undefined length cannot cast into array pointer type '*const [2]u16' +// :9:9: note: length of slice must be defined and match length of array type +// :15:9: error: slice with undefined length cannot cast into array pointer type '*const [2]u16' +// :15:9: note: length of slice must be defined and match length of array type +// :20:9: error: slice of length 3 cannot cast into array pointer type '*const [2]u16' +// :20:9: note: length of slice must match length of array type +// :25:9: error: expected type '*const [2]u16', found '[]const u8' +// :25:9: note: pointer type child 'u8' cannot cast into pointer type child 'u16' +// :28:11: note: parameter type declared here +// :34:9: error: slice of length 3 cannot cast into array pointer type '*const [2:0]u16' +// :34:9: note: length of slice must match length of array type +// :39:9: error: expected type '*const [2:0]u16', found '[:1]const u16' +// :39:9: note: pointer sentinel '1' cannot cast into pointer sentinel '0' +// :47:11: note: parameter type declared here +// :44:9: error: expected type '*const [2:0]u16', found '[]const u16' +// :44:9: note: destination pointer requires '0' sentinel +// :47:11: note: parameter type declared here