| author | |
| committer | |
| log | 5f74e4f3f8b909835ef794253a98a77868b3880e |
| tree | 302677d0bc78a562cff04f6a8fa8f7418f57b6cd |
| parent | 8cb4459f36704a3660dcbce30e80f6e01a66b39e |
For generic code, it is far more useful to consider this equivalent to
`unreachable`. Meanwhile, it is difficult to accidentally write concrete
code which performs this operation, so very little is actually lost from
not having a compile error.
If we accept that this operation does not trigger a compile error, then
it already has the potential to invoke Illegal Behavior today, because
uninstantiable types have an unspecified size in memory, so the
dereference always potentially exceeds the pointer's provenance. (That
said, this only means it is *possible* for the operation to invoke IB,
so the langspec should nonetheless explicitly specify that dereferencing
a pointer to an uninstantiable type is itself Illegal Behavior.)
Storing uninstantiable types into memory does not require any specific
language rules, because that operation can never be reached anyway due
to it requiring an operand (the RHS of `a = b`) whose type is the store
type, which is (by definition) impossible for uninstantiable types.
Resolves: https://codeberg.org/ziglang/zig/issues/3624711 files changed, 169 insertions(+), 33 deletions(-)
lib/std/debug.zig+4| ... | @@ -207,6 +207,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type { | ... | @@ -207,6 +207,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type { |
| 207 | @branchHint(.cold); | 207 | @branchHint(.cold); |
| 208 | call("'noreturn' function returned", @returnAddress()); | 208 | call("'noreturn' function returned", @returnAddress()); |
| 209 | } | 209 | } |
| 210 | pub fn loadUninstantiableType() noreturn { | ||
| 211 | @branchHint(.cold); | ||
| 212 | call("attempt to load uninstantiable type", @returnAddress()); | ||
| 213 | } | ||
| 210 | }; | 214 | }; |
| 211 | } | 215 | } |
| 212 | 216 |
lib/std/debug/no_panic.zig+5| ... | @@ -134,3 +134,8 @@ pub fn noreturnReturned() noreturn { | ... | @@ -134,3 +134,8 @@ pub fn noreturnReturned() noreturn { |
| 134 | @branchHint(.cold); | 134 | @branchHint(.cold); |
| 135 | @trap(); | 135 | @trap(); |
| 136 | } | 136 | } |
| 137 | |||
| 138 | pub fn loadUninstantiableType() noreturn { | ||
| 139 | @branchHint(.cold); | ||
| 140 | @trap(); | ||
| 141 | } |
lib/std/debug/simple_panic.zig+4| ... | @@ -126,3 +126,7 @@ pub fn memcpyAlias() noreturn { | ... | @@ -126,3 +126,7 @@ pub fn memcpyAlias() noreturn { |
| 126 | pub fn noreturnReturned() noreturn { | 126 | pub fn noreturnReturned() noreturn { |
| 127 | call("'noreturn' function returned", null); | 127 | call("'noreturn' function returned", null); |
| 128 | } | 128 | } |
| 129 | |||
| 130 | pub fn loadUninstantiableType() noreturn { | ||
| 131 | call("attempt to load uninstantiable type", null); | ||
| 132 | } |
src/Sema.zig+61-2| ... | @@ -31040,7 +31040,18 @@ fn analyzeLoad( | ... | @@ -31040,7 +31040,18 @@ fn analyzeLoad( |
| 31040 | const comptime_only = switch (elem_ty.classify(zcu)) { | 31040 | const comptime_only = switch (elem_ty.classify(zcu)) { |
| 31041 | .no_possible_value => switch (elem_ty.zigTypeTag(zcu)) { | 31041 | .no_possible_value => switch (elem_ty.zigTypeTag(zcu)) { |
| 31042 | .@"opaque" => return sema.fail(block, src, "cannot load opaque type '{f}'", .{elem_ty.fmt(pt)}), | 31042 | .@"opaque" => return sema.fail(block, src, "cannot load opaque type '{f}'", .{elem_ty.fmt(pt)}), |
| 31043 | else => return sema.fail(block, src, "cannot load uninstantiable type '{f}'", .{elem_ty.fmt(pt)}), | 31043 | else => { |
| 31044 | // Loading an uninstantiable type always invokes Illegal Behavior. | ||
| 31045 | if (block.isComptime()) { | ||
| 31046 | return sema.fail(block, src, "cannot load uninstantiable type '{f}'", .{elem_ty.fmt(pt)}); | ||
| 31047 | } else if (block.wantSafety()) { | ||
| 31048 | try sema.safetyPanic(block, src, .load_uninstantiable_type); | ||
| 31049 | return .unreachable_value; | ||
| 31050 | } else { | ||
| 31051 | _ = try block.addNoOp(.unreach); | ||
| 31052 | return .unreachable_value; | ||
| 31053 | } | ||
| 31054 | }, | ||
| 31044 | }, | 31055 | }, |
| 31045 | .one_possible_value => return .fromValue((try elem_ty.onePossibleValue(pt)).?), | 31056 | .one_possible_value => return .fromValue((try elem_ty.onePossibleValue(pt)).?), |
| 31046 | .runtime => false, | 31057 | .runtime => false, |
| ... | @@ -35059,12 +35070,60 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.StdLangDecl) CompileError!Typ | ... | @@ -35059,12 +35070,60 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.StdLangDecl) CompileError!Typ |
| 35059 | .@"panic.copyLenMismatch", | 35070 | .@"panic.copyLenMismatch", |
| 35060 | .@"panic.memcpyAlias", | 35071 | .@"panic.memcpyAlias", |
| 35061 | .@"panic.noreturnReturned", | 35072 | .@"panic.noreturnReturned", |
| 35073 | .@"panic.loadUninstantiableType", | ||
| 35062 | => try pt.funcType(.{ | 35074 | => try pt.funcType(.{ |
| 35063 | .param_types = &.{}, | 35075 | .param_types = &.{}, |
| 35064 | .return_type = .noreturn_type, | 35076 | .return_type = .noreturn_type, |
| 35065 | }), | 35077 | }), |
| 35066 | 35078 | ||
| 35067 | else => unreachable, | 35079 | .StackTrace, |
| 35080 | .CallingConvention, | ||
| 35081 | .SourceLocation, | ||
| 35082 | .Signedness, | ||
| 35083 | .AddressSpace, | ||
| 35084 | .VaList, | ||
| 35085 | .CallModifier, | ||
| 35086 | .AtomicOrder, | ||
| 35087 | .AtomicRmwOp, | ||
| 35088 | .ReduceOp, | ||
| 35089 | .FloatMode, | ||
| 35090 | .PrefetchOptions, | ||
| 35091 | .ExportOptions, | ||
| 35092 | .ExternOptions, | ||
| 35093 | .BranchHint, | ||
| 35094 | .assembly, | ||
| 35095 | .@"assembly.Clobbers", | ||
| 35096 | .Type, | ||
| 35097 | .@"Type.Fn", | ||
| 35098 | .@"Type.Fn.ParamAttributes", | ||
| 35099 | .@"Type.Fn.Attributes", | ||
| 35100 | .@"Type.Int", | ||
| 35101 | .@"Type.Float", | ||
| 35102 | .@"Type.Pointer", | ||
| 35103 | .@"Type.Pointer.Size", | ||
| 35104 | .@"Type.Pointer.Attributes", | ||
| 35105 | .@"Type.Array", | ||
| 35106 | .@"Type.Vector", | ||
| 35107 | .@"Type.Optional", | ||
| 35108 | .@"Type.ErrorUnion", | ||
| 35109 | .@"Type.ErrorSet", | ||
| 35110 | .@"Type.Enum", | ||
| 35111 | .@"Type.Enum.Mode", | ||
| 35112 | .@"Type.Union", | ||
| 35113 | .@"Type.Union.FieldAttributes", | ||
| 35114 | .@"Type.Struct", | ||
| 35115 | .@"Type.Struct.FieldAttributes", | ||
| 35116 | .@"Type.ContainerLayout", | ||
| 35117 | .@"Type.Opaque", | ||
| 35118 | .@"Type.Spirv", | ||
| 35119 | .@"Type.Spirv.Image", | ||
| 35120 | .@"Type.Spirv.Image.Usage", | ||
| 35121 | .@"Type.Spirv.Image.Format", | ||
| 35122 | .@"Type.Spirv.Image.Dimensionality", | ||
| 35123 | .@"Type.Spirv.Image.Depth", | ||
| 35124 | .@"Type.Spirv.Image.Access", | ||
| 35125 | .panic, | ||
| 35126 | => unreachable, // not a function (`decl.kind() != .func`) | ||
| 35068 | }; | 35127 | }; |
| 35069 | } | 35128 | } |
| 35070 | 35129 |
src/Zcu.zig+4| ... | @@ -517,6 +517,7 @@ pub const StdLangDecl = enum { | ... | @@ -517,6 +517,7 @@ pub const StdLangDecl = enum { |
| 517 | @"panic.copyLenMismatch", | 517 | @"panic.copyLenMismatch", |
| 518 | @"panic.memcpyAlias", | 518 | @"panic.memcpyAlias", |
| 519 | @"panic.noreturnReturned", | 519 | @"panic.noreturnReturned", |
| 520 | @"panic.loadUninstantiableType", | ||
| 520 | 521 | ||
| 521 | VaList, | 522 | VaList, |
| 522 | 523 | ||
| ... | @@ -606,6 +607,7 @@ pub const StdLangDecl = enum { | ... | @@ -606,6 +607,7 @@ pub const StdLangDecl = enum { |
| 606 | .@"panic.copyLenMismatch", | 607 | .@"panic.copyLenMismatch", |
| 607 | .@"panic.memcpyAlias", | 608 | .@"panic.memcpyAlias", |
| 608 | .@"panic.noreturnReturned", | 609 | .@"panic.noreturnReturned", |
| 610 | .@"panic.loadUninstantiableType", | ||
| 609 | => .func, | 611 | => .func, |
| 610 | }; | 612 | }; |
| 611 | } | 613 | } |
| ... | @@ -679,6 +681,7 @@ pub const SimplePanicId = enum { | ... | @@ -679,6 +681,7 @@ pub const SimplePanicId = enum { |
| 679 | copy_len_mismatch, | 681 | copy_len_mismatch, |
| 680 | memcpy_alias, | 682 | memcpy_alias, |
| 681 | noreturn_returned, | 683 | noreturn_returned, |
| 684 | load_uninstantiable_type, | ||
| 682 | 685 | ||
| 683 | pub fn toStdLangDecl(id: SimplePanicId) StdLangDecl { | 686 | pub fn toStdLangDecl(id: SimplePanicId) StdLangDecl { |
| 684 | return switch (id) { | 687 | return switch (id) { |
| ... | @@ -702,6 +705,7 @@ pub const SimplePanicId = enum { | ... | @@ -702,6 +705,7 @@ pub const SimplePanicId = enum { |
| 702 | .copy_len_mismatch => .@"panic.copyLenMismatch", | 705 | .copy_len_mismatch => .@"panic.copyLenMismatch", |
| 703 | .memcpy_alias => .@"panic.memcpyAlias", | 706 | .memcpy_alias => .@"panic.memcpyAlias", |
| 704 | .noreturn_returned => .@"panic.noreturnReturned", | 707 | .noreturn_returned => .@"panic.noreturnReturned", |
| 708 | .load_uninstantiable_type => .@"panic.loadUninstantiableType", | ||
| 705 | // zig fmt: on | 709 | // zig fmt: on |
| 706 | }; | 710 | }; |
| 707 | } | 711 | } |
test/cases/compile_errors/initialize_empty_union.zig-31| ... | @@ -28,25 +28,6 @@ export fn init5() void { | ... | @@ -28,25 +28,6 @@ export fn init5() void { |
| 28 | _ = @as(U5, undefined); | 28 | _ = @as(U5, undefined); |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | export fn deref0(ptr: *const U0) void { | ||
| 32 | _ = ptr.*; | ||
| 33 | } | ||
| 34 | export fn deref1(ptr: *const U1) void { | ||
| 35 | _ = ptr.*; | ||
| 36 | } | ||
| 37 | export fn deref2(ptr: *const U2) void { | ||
| 38 | _ = ptr.*; | ||
| 39 | } | ||
| 40 | export fn deref3(ptr: *const U3) void { | ||
| 41 | _ = ptr.*; | ||
| 42 | } | ||
| 43 | export fn deref4(ptr: *const U4) void { | ||
| 44 | _ = ptr.*; | ||
| 45 | } | ||
| 46 | export fn deref5(ptr: *const U5) void { | ||
| 47 | _ = ptr.*; | ||
| 48 | } | ||
| 49 | |||
| 50 | // error | 31 | // error |
| 51 | // | 32 | // |
| 52 | // :13:17: error: expected type 'tmp.U0', found '@TypeOf(undefined)' | 33 | // :13:17: error: expected type 'tmp.U0', found '@TypeOf(undefined)' |
| ... | @@ -67,15 +48,3 @@ export fn deref5(ptr: *const U5) void { | ... | @@ -67,15 +48,3 @@ export fn deref5(ptr: *const U5) void { |
| 67 | // :28:17: error: expected type 'tmp.U5', found '@TypeOf(undefined)' | 48 | // :28:17: error: expected type 'tmp.U5', found '@TypeOf(undefined)' |
| 68 | // :28:17: note: cannot coerce to uninstantiable type 'tmp.U5' | 49 | // :28:17: note: cannot coerce to uninstantiable type 'tmp.U5' |
| 69 | // :10:12: note: union declared here | 50 | // :10:12: note: union declared here |
| 70 | // :32:12: error: cannot load uninstantiable type 'tmp.U0' | ||
| 71 | // :5:12: note: union declared here | ||
| 72 | // :35:12: error: cannot load uninstantiable type 'tmp.U1' | ||
| 73 | // :6:12: note: union declared here | ||
| 74 | // :38:12: error: cannot load uninstantiable type 'tmp.U2' | ||
| 75 | // :7:12: note: union declared here | ||
| 76 | // :41:12: error: cannot load uninstantiable type 'tmp.U3' | ||
| 77 | // :8:12: note: union declared here | ||
| 78 | // :44:12: error: cannot load uninstantiable type 'tmp.U4' | ||
| 79 | // :9:12: note: union declared here | ||
| 80 | // :47:12: error: cannot load uninstantiable type 'tmp.U5' | ||
| 81 | // :10:12: note: union declared here |
test/cases/safety/load_uninstantiable_enum.zig created+20| ... | @@ -0,0 +1,20 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { | ||
| 4 | _ = stack_trace; | ||
| 5 | if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) { | ||
| 6 | std.process.exit(0); | ||
| 7 | } | ||
| 8 | std.process.exit(1); | ||
| 9 | } | ||
| 10 | |||
| 11 | const E = enum {}; | ||
| 12 | pub fn main() error{TestFailed}!void { | ||
| 13 | const bytes: [32]u8 = @splat(0); | ||
| 14 | const ptr: *const E = @ptrCast(&bytes); | ||
| 15 | _ = ptr.*; | ||
| 16 | return error.TestFailed; | ||
| 17 | } | ||
| 18 | // run | ||
| 19 | // backend=selfhosted,llvm | ||
| 20 | // target=x86_64-linux,aarch64-linux,wasm32-wasi | ||
test/cases/safety/load_uninstantiable_enum_from_slice.zig created+21| ... | @@ -0,0 +1,21 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { | ||
| 4 | _ = stack_trace; | ||
| 5 | if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) { | ||
| 6 | std.process.exit(0); | ||
| 7 | } | ||
| 8 | std.process.exit(1); | ||
| 9 | } | ||
| 10 | |||
| 11 | const E = enum {}; | ||
| 12 | pub fn main() error{TestFailed}!void { | ||
| 13 | const bytes: [32]u8 = @splat(0); | ||
| 14 | const ptr: *const [1]E = @ptrCast(&bytes); | ||
| 15 | const slice: []const E = ptr; | ||
| 16 | _ = slice[0]; | ||
| 17 | return error.TestFailed; | ||
| 18 | } | ||
| 19 | // run | ||
| 20 | // backend=selfhosted,llvm | ||
| 21 | // target=x86_64-linux,aarch64-linux,wasm32-wasi | ||
test/cases/safety/load_uninstantiable_union.zig created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { | ||
| 4 | _ = stack_trace; | ||
| 5 | if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) { | ||
| 6 | std.process.exit(0); | ||
| 7 | } | ||
| 8 | std.process.exit(1); | ||
| 9 | } | ||
| 10 | |||
| 11 | const U = union { | ||
| 12 | foo: struct { a: u8, b: noreturn, }, | ||
| 13 | bar: enum {}, | ||
| 14 | }; | ||
| 15 | pub fn main() error{TestFailed}!void { | ||
| 16 | const bytes: [32]u8 = @splat(0); | ||
| 17 | const ptr: *const U = @ptrCast(&bytes); | ||
| 18 | _ = ptr.*; | ||
| 19 | return error.TestFailed; | ||
| 20 | } | ||
| 21 | // run | ||
| 22 | // backend=selfhosted,llvm | ||
| 23 | // target=x86_64-linux,aarch64-linux,wasm32-wasi | ||
test/cases/safety/load_uninstantiable_union_from_slice.zig created+24| ... | @@ -0,0 +1,24 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { | ||
| 4 | _ = stack_trace; | ||
| 5 | if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) { | ||
| 6 | std.process.exit(0); | ||
| 7 | } | ||
| 8 | std.process.exit(1); | ||
| 9 | } | ||
| 10 | |||
| 11 | const U = union { | ||
| 12 | foo: struct { a: u8, b: noreturn, }, | ||
| 13 | bar: enum {}, | ||
| 14 | }; | ||
| 15 | pub fn main() error{TestFailed}!void { | ||
| 16 | const bytes: [32]u8 = @splat(0); | ||
| 17 | const ptr: *const [1]U = @ptrCast(&bytes); | ||
| 18 | const slice: []const U = ptr; | ||
| 19 | _ = slice[0]; | ||
| 20 | return error.TestFailed; | ||
| 21 | } | ||
| 22 | // run | ||
| 23 | // backend=selfhosted,llvm | ||
| 24 | // target=x86_64-linux,aarch64-linux,wasm32-wasi | ||
test/incremental/change_panic_handler_explicit+3| ... | @@ -36,6 +36,7 @@ pub const panic = struct { | ... | @@ -36,6 +36,7 @@ pub const panic = struct { |
| 36 | pub const copyLenMismatch = no_panic.copyLenMismatch; | 36 | pub const copyLenMismatch = no_panic.copyLenMismatch; |
| 37 | pub const memcpyAlias = no_panic.memcpyAlias; | 37 | pub const memcpyAlias = no_panic.memcpyAlias; |
| 38 | pub const noreturnReturned = no_panic.noreturnReturned; | 38 | pub const noreturnReturned = no_panic.noreturnReturned; |
| 39 | pub const loadUninstantiableType = no_panic.loadUninstantiableType; | ||
| 39 | }; | 40 | }; |
| 40 | fn myPanic(msg: []const u8, _: ?usize) noreturn { | 41 | fn myPanic(msg: []const u8, _: ?usize) noreturn { |
| 41 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | 42 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); |
| ... | @@ -84,6 +85,7 @@ pub const panic = struct { | ... | @@ -84,6 +85,7 @@ pub const panic = struct { |
| 84 | pub const copyLenMismatch = no_panic.copyLenMismatch; | 85 | pub const copyLenMismatch = no_panic.copyLenMismatch; |
| 85 | pub const memcpyAlias = no_panic.memcpyAlias; | 86 | pub const memcpyAlias = no_panic.memcpyAlias; |
| 86 | pub const noreturnReturned = no_panic.noreturnReturned; | 87 | pub const noreturnReturned = no_panic.noreturnReturned; |
| 88 | pub const loadUninstantiableType = no_panic.loadUninstantiableType; | ||
| 87 | }; | 89 | }; |
| 88 | fn myPanic(msg: []const u8, _: ?usize) noreturn { | 90 | fn myPanic(msg: []const u8, _: ?usize) noreturn { |
| 89 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | 91 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); |
| ... | @@ -132,6 +134,7 @@ pub const panic = struct { | ... | @@ -132,6 +134,7 @@ pub const panic = struct { |
| 132 | pub const copyLenMismatch = no_panic.copyLenMismatch; | 134 | pub const copyLenMismatch = no_panic.copyLenMismatch; |
| 133 | pub const memcpyAlias = no_panic.memcpyAlias; | 135 | pub const memcpyAlias = no_panic.memcpyAlias; |
| 134 | pub const noreturnReturned = no_panic.noreturnReturned; | 136 | pub const noreturnReturned = no_panic.noreturnReturned; |
| 137 | pub const loadUninstantiableType = no_panic.loadUninstantiableType; | ||
| 135 | }; | 138 | }; |
| 136 | fn myPanicNew(msg: []const u8, _: ?usize) noreturn { | 139 | fn myPanicNew(msg: []const u8, _: ?usize) noreturn { |
| 137 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | 140 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); |