authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-04-23 15:39:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-04-23 15:39:36-07:00
log2dbcc03fb80e0688bba651821db6488cf950404c
tree9d5bfa6e85d587d8373bc99a8153c75093ae1a90
parent42ee364e7b698822a69cba4cd2bda17868657e05
parent6c1ab376ddcdbb05610487e5b813d42ff37da40d
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15405 from Luukdegram/wasm

wasm: implement more runtime safety checks

4 files changed, 134 insertions(+), 5 deletions(-)

src/Module.zig+1-1
...@@ -6626,7 +6626,7 @@ pub fn backendSupportsFeature(mod: Module, feature: Feature) bool {...@@ -6626,7 +6626,7 @@ pub fn backendSupportsFeature(mod: Module, feature: Feature) bool {
6626 .safety_check_formatted => mod.comp.bin_file.options.use_llvm,6626 .safety_check_formatted => mod.comp.bin_file.options.use_llvm,
6627 .error_return_trace => mod.comp.bin_file.options.use_llvm,6627 .error_return_trace => mod.comp.bin_file.options.use_llvm,
6628 .is_named_enum_value => mod.comp.bin_file.options.use_llvm,6628 .is_named_enum_value => mod.comp.bin_file.options.use_llvm,
6629 .error_set_has_value => mod.comp.bin_file.options.use_llvm,6629 .error_set_has_value => mod.comp.bin_file.options.use_llvm or mod.comp.bin_file.options.target.isWasm(),
6630 .field_reordering => mod.comp.bin_file.options.use_llvm,6630 .field_reordering => mod.comp.bin_file.options.use_llvm,
6631 };6631 };
6632}6632}
src/arch/wasm/CodeGen.zig+90-3
...@@ -1946,6 +1946,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1946,6 +1946,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1946 .ret_addr => func.airRetAddr(inst),1946 .ret_addr => func.airRetAddr(inst),
1947 .tag_name => func.airTagName(inst),1947 .tag_name => func.airTagName(inst),
19481948
1949 .error_set_has_value => func.airErrorSetHasValue(inst),
1950
1949 .mul_sat,1951 .mul_sat,
1950 .mod,1952 .mod,
1951 .assembly,1953 .assembly,
...@@ -1967,7 +1969,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1967,7 +1969,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1967 .set_err_return_trace,1969 .set_err_return_trace,
1968 .save_err_return_trace_index,1970 .save_err_return_trace_index,
1969 .is_named_enum_value,1971 .is_named_enum_value,
1970 .error_set_has_value,
1971 .addrspace_cast,1972 .addrspace_cast,
1972 .vector_store_elem,1973 .vector_store_elem,
1973 .c_va_arg,1974 .c_va_arg,
...@@ -3338,9 +3339,14 @@ fn airCmpVector(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3338,9 +3339,14 @@ fn airCmpVector(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3338fn airCmpLtErrorsLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {3339fn airCmpLtErrorsLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3339 const un_op = func.air.instructions.items(.data)[inst].un_op;3340 const un_op = func.air.instructions.items(.data)[inst].un_op;
3340 const operand = try func.resolveInst(un_op);3341 const operand = try func.resolveInst(un_op);
3342 const sym_index = try func.bin_file.getGlobalSymbol("__zig_errors_len", null);
3343 const errors_len = WValue{ .memory = sym_index };
33413344
3342 _ = operand;3345 try func.emitWValue(operand);
3343 return func.fail("TODO implement airCmpLtErrorsLen for wasm", .{});3346 const errors_len_val = try func.load(errors_len, Type.err_int, 0);
3347 const result = try func.cmp(.stack, errors_len_val, Type.err_int, .lt);
3348
3349 return func.finishAir(inst, try result.toLocal(func, Type.bool), &.{un_op});
3344}3350}
33453351
3346fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {3352fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
...@@ -6510,3 +6516,84 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {...@@ -6510,3 +6516,84 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
6510 const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);6516 const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);
6511 return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);6517 return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);
6512}6518}
6519
6520fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6521 const ty_op = func.air.instructions.items(.data)[inst].ty_op;
6522
6523 const operand = try func.resolveInst(ty_op.operand);
6524 const error_set_ty = func.air.getRefType(ty_op.ty);
6525 const result = try func.allocLocal(Type.bool);
6526
6527 const names = error_set_ty.errorSetNames();
6528 var values = try std.ArrayList(u32).initCapacity(func.gpa, names.len);
6529 defer values.deinit();
6530
6531 const module = func.bin_file.base.options.module.?;
6532 var lowest: ?u32 = null;
6533 var highest: ?u32 = null;
6534 for (names) |name| {
6535 const err_int = module.global_error_set.get(name).?;
6536 if (lowest) |*l| {
6537 if (err_int < l.*) {
6538 l.* = err_int;
6539 }
6540 } else {
6541 lowest = err_int;
6542 }
6543 if (highest) |*h| {
6544 if (err_int > h.*) {
6545 highest = err_int;
6546 }
6547 } else {
6548 highest = err_int;
6549 }
6550
6551 values.appendAssumeCapacity(err_int);
6552 }
6553
6554 // start block for 'true' branch
6555 try func.startBlock(.block, wasm.block_empty);
6556 // start block for 'false' branch
6557 try func.startBlock(.block, wasm.block_empty);
6558 // block for the jump table itself
6559 try func.startBlock(.block, wasm.block_empty);
6560
6561 // lower operand to determine jump table target
6562 try func.emitWValue(operand);
6563 try func.addImm32(@intCast(i32, lowest.?));
6564 try func.addTag(.i32_sub);
6565
6566 // Account for default branch so always add '1'
6567 const depth = @intCast(u32, highest.? - lowest.? + 1);
6568 const jump_table: Mir.JumpTable = .{ .length = depth };
6569 const table_extra_index = try func.addExtra(jump_table);
6570 try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
6571 try func.mir_extra.ensureUnusedCapacity(func.gpa, depth);
6572
6573 var value: u32 = lowest.?;
6574 while (value <= highest.?) : (value += 1) {
6575 const idx: u32 = blk: {
6576 for (values.items) |val| {
6577 if (val == value) break :blk 1;
6578 }
6579 break :blk 0;
6580 };
6581 func.mir_extra.appendAssumeCapacity(idx);
6582 }
6583 try func.endBlock();
6584
6585 // 'false' branch (i.e. error set does not have value
6586 // ensure we set local to 0 in case the local was re-used.
6587 try func.addImm32(0);
6588 try func.addLabel(.local_set, result.local.value);
6589 try func.addLabel(.br, 1);
6590 try func.endBlock();
6591
6592 // 'true' branch
6593 try func.addImm32(1);
6594 try func.addLabel(.local_set, result.local.value);
6595 try func.addLabel(.br, 0);
6596 try func.endBlock();
6597
6598 return func.finishAir(inst, result, &.{ty_op.operand});
6599}
src/link/Wasm.zig+43
...@@ -1209,6 +1209,11 @@ fn resolveLazySymbols(wasm: *Wasm) !void {...@@ -1209,6 +1209,11 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
1209 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);1209 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
1210 }1210 }
1211 }1211 }
1212 if (wasm.undefs.fetchSwapRemove("__zig_errors_len")) |kv| {
1213 const loc = try wasm.createSyntheticSymbol("__zig_errors_len", .data);
1214 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
1215 _ = wasm.resolved_symbols.swapRemove(kv.value);
1216 }
1212}1217}
12131218
1214// Tries to find a global symbol by its name. Returns null when not found,1219// Tries to find a global symbol by its name. Returns null when not found,
...@@ -2185,6 +2190,43 @@ fn setupInitFunctions(wasm: *Wasm) !void {...@@ -2185,6 +2190,43 @@ fn setupInitFunctions(wasm: *Wasm) !void {
2185 std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);2190 std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
2186}2191}
21872192
2193/// Generates an atom containing the global error set' size.
2194/// This will only be generated if the symbol exists.
2195fn setupErrorsLen(wasm: *Wasm) !void {
2196 const loc = wasm.findGlobalSymbol("__zig_errors_len") orelse return;
2197
2198 const errors_len = wasm.base.options.module.?.global_error_set.count();
2199 // overwrite existing atom if it already exists (maybe the error set has increased)
2200 // if not, allcoate a new atom.
2201 const atom_index = if (wasm.symbol_atom.get(loc)) |index| blk: {
2202 const atom = wasm.getAtomPtr(index);
2203 if (atom.next) |next_atom_index| {
2204 const next_atom = wasm.getAtomPtr(next_atom_index);
2205 next_atom.prev = atom.prev;
2206 atom.next = null;
2207 }
2208 if (atom.prev) |prev_index| {
2209 const prev_atom = wasm.getAtomPtr(prev_index);
2210 prev_atom.next = atom.next;
2211 atom.prev = null;
2212 }
2213 atom.deinit(wasm);
2214 break :blk index;
2215 } else new_atom: {
2216 const atom_index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
2217 try wasm.symbol_atom.put(wasm.base.allocator, loc, atom_index);
2218 try wasm.managed_atoms.append(wasm.base.allocator, undefined);
2219 break :new_atom atom_index;
2220 };
2221 const atom = wasm.getAtomPtr(atom_index);
2222 atom.* = Atom.empty;
2223 atom.sym_index = loc.index;
2224 atom.size = 2;
2225 try atom.code.writer(wasm.base.allocator).writeIntLittle(u16, @intCast(u16, errors_len));
2226
2227 try wasm.parseAtom(atom_index, .{ .data = .read_only });
2228}
2229
2188/// Creates a function body for the `__wasm_call_ctors` symbol.2230/// Creates a function body for the `__wasm_call_ctors` symbol.
2189/// Loops over all constructors found in `init_funcs` and calls them2231/// Loops over all constructors found in `init_funcs` and calls them
2190/// respectively based on their priority which was sorted by `setupInitFunctions`.2232/// respectively based on their priority which was sorted by `setupInitFunctions`.
...@@ -3317,6 +3359,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -3317,6 +3359,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
3317 // So we can rebuild the binary file on each incremental update3359 // So we can rebuild the binary file on each incremental update
3318 defer wasm.resetState();3360 defer wasm.resetState();
3319 try wasm.setupInitFunctions();3361 try wasm.setupInitFunctions();
3362 try wasm.setupErrorsLen();
3320 try wasm.setupStart();3363 try wasm.setupStart();
3321 try wasm.setupImports();3364 try wasm.setupImports();
3322 if (wasm.base.options.module) |mod| {3365 if (wasm.base.options.module) |mod| {
test/behavior/cast.zig-1
...@@ -401,7 +401,6 @@ test "expected [*c]const u8, found [*:0]const u8" {...@@ -401,7 +401,6 @@ test "expected [*c]const u8, found [*:0]const u8" {
401}401}
402402
403test "explicit cast from integer to error type" {403test "explicit cast from integer to error type" {
404 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
405 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;404 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
406 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO405 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
407 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO406 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO