authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-30 17:35:25-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-30 18:34:08-07:00
log56cfa8f22f69d813efedb1fa01fdcb7077ca0e5a
treeabefcd7eb5be87432d75e8f3abd971539a3938d3
parent35e0ff7c364487152d786347cf70f47b2a390f12

Sema: prevent access of undefined fields

When instantiating a generic function, there is a period of time where the function is inserted into monomorphed_funcs map, but is not yet initialized. Despite semantic analysis being single-threaded, generic function instantiation can happen recursively, meaning that the hash and equality functions for monomorphed_funcs entries are potentially invoked for an uninitialized function. This problem was mitigated by pre-setting the hash field on the newly allocated function, however it did not solve the problem for hash collisions in which case the equality function would be invoked. That it was solved for hash() but not eql() explains why the problem was difficult to observe. I tested this patch by temporarily sabotaging the hash and making it always return 0. This fix is centered on adding a new field to Module.Fn which is the one checked by eql() and is populated pre-initialization. closes #12643

2 files changed, 26 insertions(+), 6 deletions(-)

src/Module.zig+16
...@@ -1496,6 +1496,22 @@ pub const Fn = struct {...@@ -1496,6 +1496,22 @@ pub const Fn = struct {
1496 /// active Sema context. Importantly, this value is also updated when an existing1496 /// active Sema context. Importantly, this value is also updated when an existing
1497 /// generic function instantiation is found and called.1497 /// generic function instantiation is found and called.
1498 branch_quota: u32,1498 branch_quota: u32,
1499
1500 /// If this is not none, this function is a generic function instantiation, and
1501 /// this is the generic function decl from which the instance was derived.
1502 /// This information is redundant with a combination of checking if comptime_args is
1503 /// not null and looking at the first decl dependency of owner_decl. This redundant
1504 /// information is useful for three reasons:
1505 /// 1. Improved perf of monomorphed_funcs when checking the eql() function because it
1506 /// can do two fewer pointer chases by grabbing the info from this field directly
1507 /// instead of accessing the decl and then the dependencies set.
1508 /// 2. While a generic function instantiation is being initialized, we need hash()
1509 /// and eql() to work before the initialization is complete. Completing the
1510 /// insertion into the decl dependency set has more fallible operations than simply
1511 /// setting this field.
1512 /// 3. I forgot what the third thing was while typing up the other two.
1513 generic_owner_decl: Decl.OptionalIndex,
1514
1499 state: Analysis,1515 state: Analysis,
1500 is_cold: bool = false,1516 is_cold: bool = false,
1501 is_noinline: bool,1517 is_noinline: bool,
src/Sema.zig+10-6
...@@ -5590,11 +5590,10 @@ const GenericCallAdapter = struct {...@@ -5590,11 +5590,10 @@ const GenericCallAdapter = struct {
55905590
5591 pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool {5591 pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool {
5592 _ = adapted_key;5592 _ = adapted_key;
5593 // The generic function Decl is guaranteed to be the first dependency5593 // Checking for equality may happen on an item that has been inserted
5594 // of each of its instantiations.5594 // into the map but is not yet fully initialized. In such case, the
5595 const other_owner_decl = ctx.module.declPtr(other_key.owner_decl);5595 // two initialized fields are `hash` and `generic_owner_decl`.
5596 const generic_owner_decl = other_owner_decl.dependencies.keys()[0];5596 if (ctx.generic_fn.owner_decl != other_key.generic_owner_decl.unwrap().?) return false;
5597 if (ctx.generic_fn.owner_decl != generic_owner_decl) return false;
55985597
5599 const other_comptime_args = other_key.comptime_args.?;5598 const other_comptime_args = other_key.comptime_args.?;
5600 for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| {5599 for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| {
...@@ -6447,11 +6446,14 @@ fn instantiateGenericCall(...@@ -6447,11 +6446,14 @@ fn instantiateGenericCall(
6447 const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);6446 const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);
6448 const callee = if (!gop.found_existing) callee: {6447 const callee = if (!gop.found_existing) callee: {
6449 const new_module_func = try gpa.create(Module.Fn);6448 const new_module_func = try gpa.create(Module.Fn);
6449 errdefer gpa.destroy(new_module_func);
6450
6450 // This ensures that we can operate on the hash map before the Module.Fn6451 // This ensures that we can operate on the hash map before the Module.Fn
6451 // struct is fully initialized.6452 // struct is fully initialized.
6452 new_module_func.hash = precomputed_hash;6453 new_module_func.hash = precomputed_hash;
6454 new_module_func.generic_owner_decl = module_fn.owner_decl.toOptional();
6455 new_module_func.comptime_args = null;
6453 gop.key_ptr.* = new_module_func;6456 gop.key_ptr.* = new_module_func;
6454 errdefer gpa.destroy(new_module_func);
6455 errdefer assert(mod.monomorphed_funcs.remove(new_module_func));6457 errdefer assert(mod.monomorphed_funcs.remove(new_module_func));
64566458
6457 try namespace.anon_decls.ensureUnusedCapacity(gpa, 1);6459 try namespace.anon_decls.ensureUnusedCapacity(gpa, 1);
...@@ -8032,11 +8034,13 @@ fn funcCommon(...@@ -8032,11 +8034,13 @@ fn funcCommon(
8032 } else null;8034 } else null;
80338035
8034 const hash = new_func.hash;8036 const hash = new_func.hash;
8037 const generic_owner_decl = if (comptime_args == null) .none else new_func.generic_owner_decl;
8035 const fn_payload = try sema.arena.create(Value.Payload.Function);8038 const fn_payload = try sema.arena.create(Value.Payload.Function);
8036 new_func.* = .{8039 new_func.* = .{
8037 .state = anal_state,8040 .state = anal_state,
8038 .zir_body_inst = func_inst,8041 .zir_body_inst = func_inst,
8039 .owner_decl = sema.owner_decl_index,8042 .owner_decl = sema.owner_decl_index,
8043 .generic_owner_decl = generic_owner_decl,
8040 .comptime_args = comptime_args,8044 .comptime_args = comptime_args,
8041 .hash = hash,8045 .hash = hash,
8042 .lbrace_line = src_locs.lbrace_line,8046 .lbrace_line = src_locs.lbrace_line,