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 {
66266626 .safety_check_formatted => mod.comp.bin_file.options.use_llvm,
66276627 .error_return_trace => mod.comp.bin_file.options.use_llvm,
66286628 .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(),
66306630 .field_reordering => mod.comp.bin_file.options.use_llvm,
66316631 };
66326632}
src/arch/wasm/CodeGen.zig+90-3
......@@ -1946,6 +1946,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19461946 .ret_addr => func.airRetAddr(inst),
19471947 .tag_name => func.airTagName(inst),
19481948
1949 .error_set_has_value => func.airErrorSetHasValue(inst),
1950
19491951 .mul_sat,
19501952 .mod,
19511953 .assembly,
......@@ -1967,7 +1969,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19671969 .set_err_return_trace,
19681970 .save_err_return_trace_index,
19691971 .is_named_enum_value,
1970 .error_set_has_value,
19711972 .addrspace_cast,
19721973 .vector_store_elem,
19731974 .c_va_arg,
......@@ -3338,9 +3339,14 @@ fn airCmpVector(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
33383339fn airCmpLtErrorsLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
33393340 const un_op = func.air.instructions.items(.data)[inst].un_op;
33403341 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;
3343 return func.fail("TODO implement airCmpLtErrorsLen for wasm", .{});
3345 try func.emitWValue(operand);
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});
33443350}
33453351
33463352fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
......@@ -6510,3 +6516,84 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
65106516 const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);
65116517 return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);
65126518}
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 {
12091209 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
12101210 }
12111211 }
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 }
12121217}
12131218
12141219// Tries to find a global symbol by its name. Returns null when not found,
......@@ -2185,6 +2190,43 @@ fn setupInitFunctions(wasm: *Wasm) !void {
21852190 std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
21862191}
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
21882230/// Creates a function body for the `__wasm_call_ctors` symbol.
21892231/// Loops over all constructors found in `init_funcs` and calls them
21902232/// 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
33173359 // So we can rebuild the binary file on each incremental update
33183360 defer wasm.resetState();
33193361 try wasm.setupInitFunctions();
3362 try wasm.setupErrorsLen();
33203363 try wasm.setupStart();
33213364 try wasm.setupImports();
33223365 if (wasm.base.options.module) |mod| {
test/behavior/cast.zig-1
......@@ -401,7 +401,6 @@ test "expected [*c]const u8, found [*:0]const u8" {
401401}
402402
403403test "explicit cast from integer to error type" {
404 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
405404 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
406405 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
407406 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO