Merge pull request #15405 from Luukdegram/wasm

wasm: implement more runtime safety checks
This commit is contained in:
Andrew Kelley
2023-04-23 15:39:36 -07:00
committed by GitHub
4 changed files with 134 additions and 5 deletions

View File

@@ -6626,7 +6626,7 @@ pub fn backendSupportsFeature(mod: Module, feature: Feature) bool {
.safety_check_formatted => mod.comp.bin_file.options.use_llvm,
.error_return_trace => mod.comp.bin_file.options.use_llvm,
.is_named_enum_value => mod.comp.bin_file.options.use_llvm,
.error_set_has_value => mod.comp.bin_file.options.use_llvm,
.error_set_has_value => mod.comp.bin_file.options.use_llvm or mod.comp.bin_file.options.target.isWasm(),
.field_reordering => mod.comp.bin_file.options.use_llvm,
};
}

View File

@@ -1946,6 +1946,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.ret_addr => func.airRetAddr(inst),
.tag_name => func.airTagName(inst),
.error_set_has_value => func.airErrorSetHasValue(inst),
.mul_sat,
.mod,
.assembly,
@@ -1967,7 +1969,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.set_err_return_trace,
.save_err_return_trace_index,
.is_named_enum_value,
.error_set_has_value,
.addrspace_cast,
.vector_store_elem,
.c_va_arg,
@@ -3338,9 +3339,14 @@ fn airCmpVector(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
fn airCmpLtErrorsLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const un_op = func.air.instructions.items(.data)[inst].un_op;
const operand = try func.resolveInst(un_op);
const sym_index = try func.bin_file.getGlobalSymbol("__zig_errors_len", null);
const errors_len = WValue{ .memory = sym_index };
_ = operand;
return func.fail("TODO implement airCmpLtErrorsLen for wasm", .{});
try func.emitWValue(operand);
const errors_len_val = try func.load(errors_len, Type.err_int, 0);
const result = try func.cmp(.stack, errors_len_val, Type.err_int, .lt);
return func.finishAir(inst, try result.toLocal(func, Type.bool), &.{un_op});
}
fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
@@ -6510,3 +6516,84 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);
return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);
}
fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const ty_op = func.air.instructions.items(.data)[inst].ty_op;
const operand = try func.resolveInst(ty_op.operand);
const error_set_ty = func.air.getRefType(ty_op.ty);
const result = try func.allocLocal(Type.bool);
const names = error_set_ty.errorSetNames();
var values = try std.ArrayList(u32).initCapacity(func.gpa, names.len);
defer values.deinit();
const module = func.bin_file.base.options.module.?;
var lowest: ?u32 = null;
var highest: ?u32 = null;
for (names) |name| {
const err_int = module.global_error_set.get(name).?;
if (lowest) |*l| {
if (err_int < l.*) {
l.* = err_int;
}
} else {
lowest = err_int;
}
if (highest) |*h| {
if (err_int > h.*) {
highest = err_int;
}
} else {
highest = err_int;
}
values.appendAssumeCapacity(err_int);
}
// start block for 'true' branch
try func.startBlock(.block, wasm.block_empty);
// start block for 'false' branch
try func.startBlock(.block, wasm.block_empty);
// block for the jump table itself
try func.startBlock(.block, wasm.block_empty);
// lower operand to determine jump table target
try func.emitWValue(operand);
try func.addImm32(@intCast(i32, lowest.?));
try func.addTag(.i32_sub);
// Account for default branch so always add '1'
const depth = @intCast(u32, highest.? - lowest.? + 1);
const jump_table: Mir.JumpTable = .{ .length = depth };
const table_extra_index = try func.addExtra(jump_table);
try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
try func.mir_extra.ensureUnusedCapacity(func.gpa, depth);
var value: u32 = lowest.?;
while (value <= highest.?) : (value += 1) {
const idx: u32 = blk: {
for (values.items) |val| {
if (val == value) break :blk 1;
}
break :blk 0;
};
func.mir_extra.appendAssumeCapacity(idx);
}
try func.endBlock();
// 'false' branch (i.e. error set does not have value
// ensure we set local to 0 in case the local was re-used.
try func.addImm32(0);
try func.addLabel(.local_set, result.local.value);
try func.addLabel(.br, 1);
try func.endBlock();
// 'true' branch
try func.addImm32(1);
try func.addLabel(.local_set, result.local.value);
try func.addLabel(.br, 0);
try func.endBlock();
return func.finishAir(inst, result, &.{ty_op.operand});
}

View File

@@ -1209,6 +1209,11 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
}
}
if (wasm.undefs.fetchSwapRemove("__zig_errors_len")) |kv| {
const loc = try wasm.createSyntheticSymbol("__zig_errors_len", .data);
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
_ = wasm.resolved_symbols.swapRemove(kv.value);
}
}
// Tries to find a global symbol by its name. Returns null when not found,
@@ -2185,6 +2190,43 @@ fn setupInitFunctions(wasm: *Wasm) !void {
std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
}
/// Generates an atom containing the global error set' size.
/// This will only be generated if the symbol exists.
fn setupErrorsLen(wasm: *Wasm) !void {
const loc = wasm.findGlobalSymbol("__zig_errors_len") orelse return;
const errors_len = wasm.base.options.module.?.global_error_set.count();
// overwrite existing atom if it already exists (maybe the error set has increased)
// if not, allcoate a new atom.
const atom_index = if (wasm.symbol_atom.get(loc)) |index| blk: {
const atom = wasm.getAtomPtr(index);
if (atom.next) |next_atom_index| {
const next_atom = wasm.getAtomPtr(next_atom_index);
next_atom.prev = atom.prev;
atom.next = null;
}
if (atom.prev) |prev_index| {
const prev_atom = wasm.getAtomPtr(prev_index);
prev_atom.next = atom.next;
atom.prev = null;
}
atom.deinit(wasm);
break :blk index;
} else new_atom: {
const atom_index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
try wasm.symbol_atom.put(wasm.base.allocator, loc, atom_index);
try wasm.managed_atoms.append(wasm.base.allocator, undefined);
break :new_atom atom_index;
};
const atom = wasm.getAtomPtr(atom_index);
atom.* = Atom.empty;
atom.sym_index = loc.index;
atom.size = 2;
try atom.code.writer(wasm.base.allocator).writeIntLittle(u16, @intCast(u16, errors_len));
try wasm.parseAtom(atom_index, .{ .data = .read_only });
}
/// Creates a function body for the `__wasm_call_ctors` symbol.
/// Loops over all constructors found in `init_funcs` and calls them
/// 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
// So we can rebuild the binary file on each incremental update
defer wasm.resetState();
try wasm.setupInitFunctions();
try wasm.setupErrorsLen();
try wasm.setupStart();
try wasm.setupImports();
if (wasm.base.options.module) |mod| {

View File

@@ -401,7 +401,6 @@ test "expected [*c]const u8, found [*:0]const u8" {
}
test "explicit cast from integer to error type" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO