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 {
660660 /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`.
661661 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
663667 /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive.
664668 /// Result type is always `[:0]const u8`.
665669 /// Uses the `un_op` field.
......@@ -1057,6 +1061,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
10571061 .is_non_err,
10581062 .is_err_ptr,
10591063 .is_non_err_ptr,
1064 .is_named_enum_value,
10601065 => return Type.bool,
10611066
10621067 .const_ty => return Type.type,
src/Liveness.zig+2
......@@ -291,6 +291,7 @@ pub fn categorizeOperand(
291291 .is_non_err_ptr,
292292 .ptrtoint,
293293 .bool_to_int,
294 .is_named_enum_value,
294295 .tag_name,
295296 .error_name,
296297 .sqrt,
......@@ -858,6 +859,7 @@ fn analyzeInst(
858859 .bool_to_int,
859860 .ret,
860861 .ret_load,
862 .is_named_enum_value,
861863 .tag_name,
862864 .error_name,
863865 .sqrt,
src/Sema.zig+13-2
......@@ -6933,8 +6933,12 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
69336933 }
69346934
69356935 try sema.requireRuntimeBlock(block, src, operand_src);
6936 // TODO insert safety check to make sure the value matches an enum value
6937 return block.addTyOp(.intcast, dest_ty, operand);
6936 const result = try 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;
69386942}
69396943
69406944/// Pointer in, pointer out.
......@@ -15887,6 +15891,11 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
1588715891 const field_name = enum_ty.enumFieldName(field_index);
1588815892 return sema.addStrLit(block, field_name);
1588915893 }
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 }
1589015899 // In case the value is runtime-known, we have an AIR instruction for this instead
1589115900 // of trying to lower it in Sema because an optimization pass may result in the operand
1589215901 // being comptime-known, which would let us elide the `tag_name` AIR instruction.
......@@ -20019,6 +20028,7 @@ pub const PanicId = enum {
2001920028 integer_part_out_of_bounds,
2002020029 corrupt_switch,
2002120030 shift_rhs_too_big,
20031 invalid_enum_value,
2002220032};
2002320033
2002420034fn addSafetyCheck(
......@@ -20316,6 +20326,7 @@ fn safetyPanic(
2031620326 .integer_part_out_of_bounds => "integer part of floating point value out of bounds",
2031720327 .corrupt_switch => "switch on corrupt value",
2031820328 .shift_rhs_too_big => "shift amount is greater than the type size",
20329 .invalid_enum_value => "invalid enum value",
2031920330 };
2032020331
2032120332 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 {
753753 .float_to_int_optimized,
754754 => return self.fail("TODO implement optimized float mode", .{}),
755755
756 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
757
756758 .wasm_memory_size => unreachable,
757759 .wasm_memory_grow => unreachable,
758760 // zig fmt: on
src/arch/arm/CodeGen.zig+2
......@@ -768,6 +768,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
768768 .float_to_int_optimized,
769769 => return self.fail("TODO implement optimized float mode", .{}),
770770
771 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
772
771773 .wasm_memory_size => unreachable,
772774 .wasm_memory_grow => unreachable,
773775 // zig fmt: on
src/arch/riscv64/CodeGen.zig+2
......@@ -693,6 +693,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
693693 .float_to_int_optimized,
694694 => return self.fail("TODO implement optimized float mode", .{}),
695695
696 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
697
696698 .wasm_memory_size => unreachable,
697699 .wasm_memory_grow => unreachable,
698700 // zig fmt: on
src/arch/sparc64/CodeGen.zig+2
......@@ -705,6 +705,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
705705 .float_to_int_optimized,
706706 => @panic("TODO implement optimized float mode"),
707707
708 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
709
708710 .wasm_memory_size => unreachable,
709711 .wasm_memory_grow => unreachable,
710712 // zig fmt: on
src/arch/wasm/CodeGen.zig+1
......@@ -1621,6 +1621,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
16211621 .tag_name,
16221622 .err_return_trace,
16231623 .set_err_return_trace,
1624 .is_named_enum_value,
16241625 => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
16251626
16261627 .add_optimized,
src/arch/x86_64/CodeGen.zig+2
......@@ -775,6 +775,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
775775 .float_to_int_optimized,
776776 => return self.fail("TODO implement optimized float mode", .{}),
777777
778 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
779
778780 .wasm_memory_size => unreachable,
779781 .wasm_memory_grow => unreachable,
780782 // zig fmt: on
src/codegen/c.zig+2
......@@ -1952,6 +1952,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
19521952 .reduce_optimized,
19531953 .float_to_int_optimized,
19541954 => 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", .{}),
19551957 // zig fmt: on
19561958 };
19571959 switch (result_value) {
src/codegen/llvm.zig+87
......@@ -201,6 +201,8 @@ pub const Object = struct {
201201 /// * it works for functions not all globals.
202202 /// Therefore, this table keeps track of the mapping.
203203 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),
204206 /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
205207 /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
206208 /// TODO we need to remove entries from this map in response to incremental compilation
......@@ -377,6 +379,7 @@ pub const Object = struct {
377379 .target_data = target_data,
378380 .target = options.target,
379381 .decl_map = .{},
382 .named_enum_map = .{},
380383 .type_map = .{},
381384 .type_map_arena = std.heap.ArenaAllocator.init(gpa),
382385 .di_type_map = .{},
......@@ -396,6 +399,7 @@ pub const Object = struct {
396399 self.llvm_module.dispose();
397400 self.context.dispose();
398401 self.decl_map.deinit(gpa);
402 self.named_enum_map.deinit(gpa);
399403 self.type_map.deinit(gpa);
400404 self.type_map_arena.deinit();
401405 self.extern_collisions.deinit(gpa);
......@@ -4180,6 +4184,8 @@ pub const FuncGen = struct {
41804184 .union_init => try self.airUnionInit(inst),
41814185 .prefetch => try self.airPrefetch(inst),
41824186
4187 .is_named_enum_value => try self.airIsNamedEnumValue(inst),
4188
41834189 .reduce => try self.airReduce(inst, false),
41844190 .reduce_optimized => try self.airReduce(inst, true),
41854191
......@@ -7882,6 +7888,87 @@ pub const FuncGen = struct {
78827888 }
78837889 }
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
78857972 fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
78867973 if (self.liveness.isUnused(inst)) return null;
78877974
src/print_air.zig+1
......@@ -170,6 +170,7 @@ const Writer = struct {
170170 .bool_to_int,
171171 .ret,
172172 .ret_load,
173 .is_named_enum_value,
173174 .tag_name,
174175 .error_name,
175176 .sqrt,
test/cases/safety/@intToEnum - no matching tag value.zig +6-3
......@@ -1,9 +1,11 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = 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);
79}
810const Foo = enum {
911 A,
......@@ -18,6 +20,7 @@ fn bar(a: u2) Foo {
1820 return @intToEnum(Foo, a);
1921}
2022fn baz(_: Foo) void {}
23
2124// run
22// backend=stage1
25// backend=llvm
2326// 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
1010
1111const E = enum(u32) {
1212 X = 1,
13 Y = 2,
1314};
1415
1516pub fn main() !void {
......@@ -21,5 +22,5 @@ pub fn main() !void {
2122}
2223
2324// run
24// backend=stage1
25// backend=llvm
2526// 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
1010
1111const U = union(enum(u32)) {
1212 X: u8,
13 Y: i8,
1314};
1415
1516pub fn main() !void {
......@@ -22,5 +23,5 @@ pub fn main() !void {
2223}
2324
2425// run
25// backend=stage1
26// backend=llvm
2627// target=native