authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-08-05 18:15:31+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-08-05 22:13:58+03:00
logf46d7304b176cdc77053225943a7d5030dd0d4ee
tree483f0aa5303fe5a1ab4fbe9140d6dabf916d88b1
parent19d5ffc710faa23cd07a6dff23d4afc43c0c7f63

stage2: add runtime safety for invalid enum values


15 files changed, 131 insertions(+), 7 deletions(-)

src/Air.zig+5
...@@ -660,6 +660,10 @@ pub const Inst = struct {...@@ -660,6 +660,10 @@ pub const Inst = struct {
660 /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`.660 /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`.
661 atomic_rmw,661 atomic_rmw,
662662
663 /// Returns true if enum tag value has a name.
664 /// Uses the `un_op` field.
665 is_named_enum_value,
666
663 /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive.667 /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive.
664 /// Result type is always `[:0]const u8`.668 /// Result type is always `[:0]const u8`.
665 /// Uses the `un_op` field.669 /// Uses the `un_op` field.
...@@ -1057,6 +1061,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {...@@ -1057,6 +1061,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
1057 .is_non_err,1061 .is_non_err,
1058 .is_err_ptr,1062 .is_err_ptr,
1059 .is_non_err_ptr,1063 .is_non_err_ptr,
1064 .is_named_enum_value,
1060 => return Type.bool,1065 => return Type.bool,
10611066
1062 .const_ty => return Type.type,1067 .const_ty => return Type.type,
src/Liveness.zig+2
...@@ -291,6 +291,7 @@ pub fn categorizeOperand(...@@ -291,6 +291,7 @@ pub fn categorizeOperand(
291 .is_non_err_ptr,291 .is_non_err_ptr,
292 .ptrtoint,292 .ptrtoint,
293 .bool_to_int,293 .bool_to_int,
294 .is_named_enum_value,
294 .tag_name,295 .tag_name,
295 .error_name,296 .error_name,
296 .sqrt,297 .sqrt,
...@@ -858,6 +859,7 @@ fn analyzeInst(...@@ -858,6 +859,7 @@ fn analyzeInst(
858 .bool_to_int,859 .bool_to_int,
859 .ret,860 .ret,
860 .ret_load,861 .ret_load,
862 .is_named_enum_value,
861 .tag_name,863 .tag_name,
862 .error_name,864 .error_name,
863 .sqrt,865 .sqrt,
src/Sema.zig+13-2
...@@ -6933,8 +6933,12 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A...@@ -6933,8 +6933,12 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
6933 }6933 }
69346934
6935 try sema.requireRuntimeBlock(block, src, operand_src);6935 try sema.requireRuntimeBlock(block, src, operand_src);
6936 // TODO insert safety check to make sure the value matches an enum value6936 const result = try block.addTyOp(.intcast, dest_ty, operand);
6937 return block.addTyOp(.intcast, dest_ty, operand);6937 if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum() and sema.mod.comp.bin_file.options.use_llvm) {
6938 const ok = try block.addUnOp(.is_named_enum_value, result);
6939 try sema.addSafetyCheck(block, ok, .invalid_enum_value);
6940 }
6941 return result;
6938}6942}
69396943
6940/// Pointer in, pointer out.6944/// Pointer in, pointer out.
...@@ -15887,6 +15891,11 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air...@@ -15887,6 +15891,11 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
15887 const field_name = enum_ty.enumFieldName(field_index);15891 const field_name = enum_ty.enumFieldName(field_index);
15888 return sema.addStrLit(block, field_name);15892 return sema.addStrLit(block, field_name);
15889 }15893 }
15894 try sema.requireRuntimeBlock(block, src, operand_src);
15895 if (block.wantSafety() and sema.mod.comp.bin_file.options.use_llvm) {
15896 const ok = try block.addUnOp(.is_named_enum_value, casted_operand);
15897 try sema.addSafetyCheck(block, ok, .invalid_enum_value);
15898 }
15890 // In case the value is runtime-known, we have an AIR instruction for this instead15899 // In case the value is runtime-known, we have an AIR instruction for this instead
15891 // of trying to lower it in Sema because an optimization pass may result in the operand15900 // of trying to lower it in Sema because an optimization pass may result in the operand
15892 // being comptime-known, which would let us elide the `tag_name` AIR instruction.15901 // being comptime-known, which would let us elide the `tag_name` AIR instruction.
...@@ -20019,6 +20028,7 @@ pub const PanicId = enum {...@@ -20019,6 +20028,7 @@ pub const PanicId = enum {
20019 integer_part_out_of_bounds,20028 integer_part_out_of_bounds,
20020 corrupt_switch,20029 corrupt_switch,
20021 shift_rhs_too_big,20030 shift_rhs_too_big,
20031 invalid_enum_value,
20022};20032};
2002320033
20024fn addSafetyCheck(20034fn addSafetyCheck(
...@@ -20316,6 +20326,7 @@ fn safetyPanic(...@@ -20316,6 +20326,7 @@ fn safetyPanic(
20316 .integer_part_out_of_bounds => "integer part of floating point value out of bounds",20326 .integer_part_out_of_bounds => "integer part of floating point value out of bounds",
20317 .corrupt_switch => "switch on corrupt value",20327 .corrupt_switch => "switch on corrupt value",
20318 .shift_rhs_too_big => "shift amount is greater than the type size",20328 .shift_rhs_too_big => "shift amount is greater than the type size",
20329 .invalid_enum_value => "invalid enum value",
20319 };20330 };
2032020331
20321 const msg_inst = msg_inst: {20332 const msg_inst = msg_inst: {
src/arch/aarch64/CodeGen.zig+2
...@@ -753,6 +753,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -753,6 +753,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
753 .float_to_int_optimized,753 .float_to_int_optimized,
754 => return self.fail("TODO implement optimized float mode", .{}),754 => return self.fail("TODO implement optimized float mode", .{}),
755755
756 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
757
756 .wasm_memory_size => unreachable,758 .wasm_memory_size => unreachable,
757 .wasm_memory_grow => unreachable,759 .wasm_memory_grow => unreachable,
758 // zig fmt: on760 // zig fmt: on
src/arch/arm/CodeGen.zig+2
...@@ -768,6 +768,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -768,6 +768,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
768 .float_to_int_optimized,768 .float_to_int_optimized,
769 => return self.fail("TODO implement optimized float mode", .{}),769 => return self.fail("TODO implement optimized float mode", .{}),
770770
771 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
772
771 .wasm_memory_size => unreachable,773 .wasm_memory_size => unreachable,
772 .wasm_memory_grow => unreachable,774 .wasm_memory_grow => unreachable,
773 // zig fmt: on775 // zig fmt: on
src/arch/riscv64/CodeGen.zig+2
...@@ -693,6 +693,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -693,6 +693,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
693 .float_to_int_optimized,693 .float_to_int_optimized,
694 => return self.fail("TODO implement optimized float mode", .{}),694 => return self.fail("TODO implement optimized float mode", .{}),
695695
696 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
697
696 .wasm_memory_size => unreachable,698 .wasm_memory_size => unreachable,
697 .wasm_memory_grow => unreachable,699 .wasm_memory_grow => unreachable,
698 // zig fmt: on700 // zig fmt: on
src/arch/sparc64/CodeGen.zig+2
...@@ -705,6 +705,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -705,6 +705,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
705 .float_to_int_optimized,705 .float_to_int_optimized,
706 => @panic("TODO implement optimized float mode"),706 => @panic("TODO implement optimized float mode"),
707707
708 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
709
708 .wasm_memory_size => unreachable,710 .wasm_memory_size => unreachable,
709 .wasm_memory_grow => unreachable,711 .wasm_memory_grow => unreachable,
710 // zig fmt: on712 // zig fmt: on
src/arch/wasm/CodeGen.zig+1
...@@ -1621,6 +1621,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1621,6 +1621,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1621 .tag_name,1621 .tag_name,
1622 .err_return_trace,1622 .err_return_trace,
1623 .set_err_return_trace,1623 .set_err_return_trace,
1624 .is_named_enum_value,
1624 => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),1625 => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
16251626
1626 .add_optimized,1627 .add_optimized,
src/arch/x86_64/CodeGen.zig+2
...@@ -775,6 +775,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -775,6 +775,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
775 .float_to_int_optimized,775 .float_to_int_optimized,
776 => return self.fail("TODO implement optimized float mode", .{}),776 => return self.fail("TODO implement optimized float mode", .{}),
777777
778 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
779
778 .wasm_memory_size => unreachable,780 .wasm_memory_size => unreachable,
779 .wasm_memory_grow => unreachable,781 .wasm_memory_grow => unreachable,
780 // zig fmt: on782 // zig fmt: on
src/codegen/c.zig+2
...@@ -1952,6 +1952,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO...@@ -1952,6 +1952,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
1952 .reduce_optimized,1952 .reduce_optimized,
1953 .float_to_int_optimized,1953 .float_to_int_optimized,
1954 => return f.fail("TODO implement optimized float mode", .{}),1954 => return f.fail("TODO implement optimized float mode", .{}),
1955
1956 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
1955 // zig fmt: on1957 // zig fmt: on
1956 };1958 };
1957 switch (result_value) {1959 switch (result_value) {
src/codegen/llvm.zig+87
...@@ -201,6 +201,8 @@ pub const Object = struct {...@@ -201,6 +201,8 @@ pub const Object = struct {
201 /// * it works for functions not all globals.201 /// * it works for functions not all globals.
202 /// Therefore, this table keeps track of the mapping.202 /// Therefore, this table keeps track of the mapping.
203 decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value),203 decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value),
204 /// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction.
205 named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value),
204 /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of206 /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
205 /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.207 /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
206 /// TODO we need to remove entries from this map in response to incremental compilation208 /// TODO we need to remove entries from this map in response to incremental compilation
...@@ -377,6 +379,7 @@ pub const Object = struct {...@@ -377,6 +379,7 @@ pub const Object = struct {
377 .target_data = target_data,379 .target_data = target_data,
378 .target = options.target,380 .target = options.target,
379 .decl_map = .{},381 .decl_map = .{},
382 .named_enum_map = .{},
380 .type_map = .{},383 .type_map = .{},
381 .type_map_arena = std.heap.ArenaAllocator.init(gpa),384 .type_map_arena = std.heap.ArenaAllocator.init(gpa),
382 .di_type_map = .{},385 .di_type_map = .{},
...@@ -396,6 +399,7 @@ pub const Object = struct {...@@ -396,6 +399,7 @@ pub const Object = struct {
396 self.llvm_module.dispose();399 self.llvm_module.dispose();
397 self.context.dispose();400 self.context.dispose();
398 self.decl_map.deinit(gpa);401 self.decl_map.deinit(gpa);
402 self.named_enum_map.deinit(gpa);
399 self.type_map.deinit(gpa);403 self.type_map.deinit(gpa);
400 self.type_map_arena.deinit();404 self.type_map_arena.deinit();
401 self.extern_collisions.deinit(gpa);405 self.extern_collisions.deinit(gpa);
...@@ -4180,6 +4184,8 @@ pub const FuncGen = struct {...@@ -4180,6 +4184,8 @@ pub const FuncGen = struct {
4180 .union_init => try self.airUnionInit(inst),4184 .union_init => try self.airUnionInit(inst),
4181 .prefetch => try self.airPrefetch(inst),4185 .prefetch => try self.airPrefetch(inst),
41824186
4187 .is_named_enum_value => try self.airIsNamedEnumValue(inst),
4188
4183 .reduce => try self.airReduce(inst, false),4189 .reduce => try self.airReduce(inst, false),
4184 .reduce_optimized => try self.airReduce(inst, true),4190 .reduce_optimized => try self.airReduce(inst, true),
41854191
...@@ -7882,6 +7888,87 @@ pub const FuncGen = struct {...@@ -7882,6 +7888,87 @@ pub const FuncGen = struct {
7882 }7888 }
7883 }7889 }
78847890
7891 fn airIsNamedEnumValue(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
7892 if (self.liveness.isUnused(inst)) return null;
7893
7894 const un_op = self.air.instructions.items(.data)[inst].un_op;
7895 const operand = try self.resolveInst(un_op);
7896 const enum_ty = self.air.typeOf(un_op);
7897
7898 const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty);
7899 const params = [_]*const llvm.Value{operand};
7900 return self.builder.buildCall(llvm_fn, &params, params.len, .Fast, .Auto, "");
7901 }
7902
7903 fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !*const llvm.Value {
7904 const enum_decl = enum_ty.getOwnerDecl();
7905
7906 // TODO: detect when the type changes and re-emit this function.
7907 const gop = try self.dg.object.named_enum_map.getOrPut(self.dg.gpa, enum_decl);
7908 if (gop.found_existing) return gop.value_ptr.*;
7909 errdefer assert(self.dg.object.named_enum_map.remove(enum_decl));
7910
7911 var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
7912 defer arena_allocator.deinit();
7913 const arena = arena_allocator.allocator();
7914
7915 const mod = self.dg.module;
7916 const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{s}", .{
7917 try mod.declPtr(enum_decl).getFullyQualifiedName(mod),
7918 });
7919
7920 var int_tag_type_buffer: Type.Payload.Bits = undefined;
7921 const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
7922 const param_types = [_]*const llvm.Type{try self.dg.lowerType(int_tag_ty)};
7923
7924 const llvm_ret_ty = try self.dg.lowerType(Type.bool);
7925 const fn_type = llvm.functionType(llvm_ret_ty, &param_types, param_types.len, .False);
7926 const fn_val = self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
7927 fn_val.setLinkage(.Internal);
7928 fn_val.setFunctionCallConv(.Fast);
7929 self.dg.addCommonFnAttributes(fn_val);
7930 gop.value_ptr.* = fn_val;
7931
7932 const prev_block = self.builder.getInsertBlock();
7933 const prev_debug_location = self.builder.getCurrentDebugLocation2();
7934 defer {
7935 self.builder.positionBuilderAtEnd(prev_block);
7936 if (self.di_scope != null) {
7937 self.builder.setCurrentDebugLocation2(prev_debug_location);
7938 }
7939 }
7940
7941 const entry_block = self.dg.context.appendBasicBlock(fn_val, "Entry");
7942 self.builder.positionBuilderAtEnd(entry_block);
7943 self.builder.clearCurrentDebugLocation();
7944
7945 const fields = enum_ty.enumFields();
7946 const named_block = self.dg.context.appendBasicBlock(fn_val, "Named");
7947 const unnamed_block = self.dg.context.appendBasicBlock(fn_val, "Unnamed");
7948 const tag_int_value = fn_val.getParam(0);
7949 const switch_instr = self.builder.buildSwitch(tag_int_value, unnamed_block, @intCast(c_uint, fields.count()));
7950
7951 for (fields.keys()) |_, field_index| {
7952 const this_tag_int_value = int: {
7953 var tag_val_payload: Value.Payload.U32 = .{
7954 .base = .{ .tag = .enum_field_index },
7955 .data = @intCast(u32, field_index),
7956 };
7957 break :int try self.dg.lowerValue(.{
7958 .ty = enum_ty,
7959 .val = Value.initPayload(&tag_val_payload.base),
7960 });
7961 };
7962 switch_instr.addCase(this_tag_int_value, named_block);
7963 }
7964 self.builder.positionBuilderAtEnd(named_block);
7965 _ = self.builder.buildRet(self.dg.context.intType(1).constInt(1, .False));
7966
7967 self.builder.positionBuilderAtEnd(unnamed_block);
7968 _ = self.builder.buildRet(self.dg.context.intType(1).constInt(0, .False));
7969 return fn_val;
7970 }
7971
7885 fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {7972 fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
7886 if (self.liveness.isUnused(inst)) return null;7973 if (self.liveness.isUnused(inst)) return null;
78877974
src/print_air.zig+1
...@@ -170,6 +170,7 @@ const Writer = struct {...@@ -170,6 +170,7 @@ const Writer = struct {
170 .bool_to_int,170 .bool_to_int,
171 .ret,171 .ret,
172 .ret_load,172 .ret_load,
173 .is_named_enum_value,
173 .tag_name,174 .tag_name,
174 .error_name,175 .error_name,
175 .sqrt,176 .sqrt,
test/cases/safety/@intToEnum - no matching tag value.zig +6-3
...@@ -1,9 +1,11 @@...@@ -1,9 +1,11 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
5 _ = stack_trace;4 _ = stack_trace;
6 std.process.exit(0);5 if (std.mem.eql(u8, message, "invalid enum value")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
7}9}
8const Foo = enum {10const Foo = enum {
9 A,11 A,
...@@ -18,6 +20,7 @@ fn bar(a: u2) Foo {...@@ -18,6 +20,7 @@ fn bar(a: u2) Foo {
18 return @intToEnum(Foo, a);20 return @intToEnum(Foo, a);
19}21}
20fn baz(_: Foo) void {}22fn baz(_: Foo) void {}
23
21// run24// run
22// backend=stage125// backend=llvm
23// target=native26// target=native
test/cases/safety/@tagName on corrupted enum value.zig +2-1
...@@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur...@@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur
1010
11const E = enum(u32) {11const E = enum(u32) {
12 X = 1,12 X = 1,
13 Y = 2,
13};14};
1415
15pub fn main() !void {16pub fn main() !void {
...@@ -21,5 +22,5 @@ pub fn main() !void {...@@ -21,5 +22,5 @@ pub fn main() !void {
21}22}
2223
23// run24// run
24// backend=stage125// backend=llvm
25// target=native26// target=native
test/cases/safety/@tagName on corrupted union value.zig +2-1
...@@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur...@@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur
1010
11const U = union(enum(u32)) {11const U = union(enum(u32)) {
12 X: u8,12 X: u8,
13 Y: i8,
13};14};
1415
15pub fn main() !void {16pub fn main() !void {
...@@ -22,5 +23,5 @@ pub fn main() !void {...@@ -22,5 +23,5 @@ pub fn main() !void {
22}23}
2324
24// run25// run
25// backend=stage126// backend=llvm
26// target=native27// target=native