authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2022-11-07 01:54:58-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-09 13:58:52-07:00
logb931889c652b4763e9ac674cd01abcd7f3311e83
treeab0828e0d69e84801f507554849d0eac18fa57ac
parent52f8de0194b4648078c2eb8d09bc3329677a80ec

Sema: avoid breaking hash contract when instantiating generic functions

* Add tagName to Value which behaves like @tagName. * Add hashUncoerced to Value as an alternative to hash when we want to produce the same hash for value that can coerce to each other. * Hash owner_decl instead of module_fn in Sema.instantiateGenericCall since Module.Decl.Index is not affected by ASLR like *Module.Fn was, and also because GenericCallAdapter.eql was already doing this. * Use Value.hashUncoerced in Sema.instantiateGenericCall because GenericCallAdapter.eql uses Value.eqlAdvanced to compare args, which ignores coersions. * Add revealed missing cases to Value.eqlAdvanced. Without these changes, we were breaking the hash contract for monomorphed_funcs, and were generating different hashes for values that compared equal. This resulted in a 0.2% chance when compiling self-hosted of producing a different output, which depended on fingerprint collisions of hashes that were affected by ASLR. Normally, the different hashes would have resulted in equal checks being skipped, but in the case of a fingerprint collision, the truth would be revealed and the compiler's behavior would diverge.

2 files changed, 114 insertions(+), 15 deletions(-)

src/Sema.zig+2-2
...@@ -6828,7 +6828,7 @@ fn instantiateGenericCall(...@@ -6828,7 +6828,7 @@ fn instantiateGenericCall(
6828 // don't find out until after generating a monomorphed function whether the parameter6828 // don't find out until after generating a monomorphed function whether the parameter
6829 // type ended up being a "must-be-comptime-known" type.6829 // type ended up being a "must-be-comptime-known" type.
6830 var hasher = std.hash.Wyhash.init(0);6830 var hasher = std.hash.Wyhash.init(0);
6831 std.hash.autoHash(&hasher, @ptrToInt(module_fn));6831 std.hash.autoHash(&hasher, module_fn.owner_decl);
68326832
6833 const generic_args = try sema.arena.alloc(GenericCallAdapter.Arg, func_ty_info.param_types.len);6833 const generic_args = try sema.arena.alloc(GenericCallAdapter.Arg, func_ty_info.param_types.len);
6834 {6834 {
...@@ -6871,7 +6871,7 @@ fn instantiateGenericCall(...@@ -6871,7 +6871,7 @@ fn instantiateGenericCall(
6871 },6871 },
6872 else => |e| return e,6872 else => |e| return e,
6873 };6873 };
6874 arg_val.hash(arg_ty, &hasher, mod);6874 arg_val.hashUncoerced(arg_ty, &hasher, mod);
6875 if (is_anytype) {6875 if (is_anytype) {
6876 arg_ty.hashWithHasher(&hasher, mod);6876 arg_ty.hashWithHasher(&hasher, mod);
6877 generic_args[i] = .{6877 generic_args[i] = .{
src/value.zig+112-13
...@@ -1055,6 +1055,40 @@ pub const Value = extern union {...@@ -1055,6 +1055,40 @@ pub const Value = extern union {
1055 }1055 }
1056 }1056 }
10571057
1058 pub fn tagName(val: Value, ty: Type, mod: *Module) []const u8 {
1059 if (ty.zigTypeTag() == .Union) return val.unionTag().tagName(ty.unionTagTypeHypothetical(), mod);
1060
1061 const field_index = switch (val.tag()) {
1062 .enum_field_index => val.castTag(.enum_field_index).?.data,
1063 .the_only_possible_value => blk: {
1064 assert(ty.enumFieldCount() == 1);
1065 break :blk 0;
1066 },
1067 .enum_literal => return val.castTag(.enum_literal).?.data,
1068 else => field_index: {
1069 const values = switch (ty.tag()) {
1070 .enum_full, .enum_nonexhaustive => ty.cast(Type.Payload.EnumFull).?.data.values,
1071 .enum_numbered => ty.castTag(.enum_numbered).?.data.values,
1072 .enum_simple => Module.EnumFull.ValueMap{},
1073 else => unreachable,
1074 };
1075 break :field_index if (values.entries.len == 0)
1076 // auto-numbered enum
1077 @intCast(u32, val.toUnsignedInt(mod.getTarget()))
1078 else
1079 @intCast(u32, values.getIndexContext(val, .{ .ty = ty, .mod = mod }).?);
1080 },
1081 };
1082
1083 const fields = switch (ty.tag()) {
1084 .enum_full, .enum_nonexhaustive => ty.cast(Type.Payload.EnumFull).?.data.fields,
1085 .enum_numbered => ty.castTag(.enum_numbered).?.data.fields,
1086 .enum_simple => ty.castTag(.enum_simple).?.data.fields,
1087 else => unreachable,
1088 };
1089 return fields.keys()[field_index];
1090 }
1091
1058 /// Asserts the value is an integer.1092 /// Asserts the value is an integer.
1059 pub fn toBigInt(val: Value, space: *BigIntSpace, target: Target) BigIntConst {1093 pub fn toBigInt(val: Value, space: *BigIntSpace, target: Target) BigIntConst {
1060 return val.toBigIntAdvanced(space, target, null) catch unreachable;1094 return val.toBigIntAdvanced(space, target, null) catch unreachable;
...@@ -2211,7 +2245,7 @@ pub const Value = extern union {...@@ -2211,7 +2245,7 @@ pub const Value = extern union {
2211 return eqlAdvanced(a_union.val, active_field_ty, b_union.val, active_field_ty, mod, sema_kit);2245 return eqlAdvanced(a_union.val, active_field_ty, b_union.val, active_field_ty, mod, sema_kit);
2212 },2246 },
2213 else => {},2247 else => {},
2214 } else if (a_tag == .null_value or b_tag == .null_value) {2248 } else if (b_tag == .null_value or b_tag == .@"error") {
2215 return false;2249 return false;
2216 } else if (a_tag == .undef or b_tag == .undef) {2250 } else if (a_tag == .undef or b_tag == .undef) {
2217 return false;2251 return false;
...@@ -2335,18 +2369,25 @@ pub const Value = extern union {...@@ -2335,18 +2369,25 @@ pub const Value = extern union {
2335 if (a_nan) return true;2369 if (a_nan) return true;
2336 return a_float == b_float;2370 return a_float == b_float;
2337 },2371 },
2338 .Optional => {2372 .Optional => if (a_tag != .null_value and b_tag == .opt_payload) {
2339 if (a.tag() != .opt_payload and b.tag() == .opt_payload) {2373 var sub_pl: Payload.SubValue = .{
2340 var buffer: Payload.SubValue = .{2374 .base = .{ .tag = b.tag() },
2341 .base = .{ .tag = .opt_payload },2375 .data = a,
2342 .data = a,2376 };
2343 };2377 const sub_val = Value.initPayload(&sub_pl.base);
2344 const opt_val = Value.initPayload(&buffer.base);2378 return eqlAdvanced(sub_val, ty, b, ty, mod, sema_kit);
2345 return eqlAdvanced(opt_val, ty, b, ty, mod, sema_kit);2379 },
2346 }2380 .ErrorUnion => if (a_tag != .@"error" and b_tag == .eu_payload) {
2381 var sub_pl: Payload.SubValue = .{
2382 .base = .{ .tag = b.tag() },
2383 .data = a,
2384 };
2385 const sub_val = Value.initPayload(&sub_pl.base);
2386 return eqlAdvanced(sub_val, ty, b, ty, mod, sema_kit);
2347 },2387 },
2348 else => {},2388 else => {},
2349 }2389 }
2390 if (a_tag == .null_value or a_tag == .@"error") return false;
2350 return (try orderAdvanced(a, b, target, sema_kit)).compare(.eq);2391 return (try orderAdvanced(a, b, target, sema_kit)).compare(.eq);
2351 }2392 }
23522393
...@@ -2436,7 +2477,7 @@ pub const Value = extern union {...@@ -2436,7 +2477,7 @@ pub const Value = extern union {
2436 const sub_ty = ty.optionalChild(&buffer);2477 const sub_ty = ty.optionalChild(&buffer);
2437 sub_val.hash(sub_ty, hasher, mod);2478 sub_val.hash(sub_ty, hasher, mod);
2438 } else {2479 } else {
2439 std.hash.autoHash(hasher, false); // non-null2480 std.hash.autoHash(hasher, false); // null
2440 }2481 }
2441 },2482 },
2442 .ErrorUnion => {2483 .ErrorUnion => {
...@@ -2474,8 +2515,8 @@ pub const Value = extern union {...@@ -2474,8 +2515,8 @@ pub const Value = extern union {
2474 union_obj.val.hash(active_field_ty, hasher, mod);2515 union_obj.val.hash(active_field_ty, hasher, mod);
2475 },2516 },
2476 .Fn => {2517 .Fn => {
2477 // Note that his hashes the *Fn/*ExternFn rather than the *Decl. This is2518 // Note that this hashes the *Fn/*ExternFn rather than the *Decl.
2478 // to differentiate function bodies from function pointers.2519 // This is to differentiate function bodies from function pointers.
2479 // This is currently redundant since we already hash the zig type tag2520 // This is currently redundant since we already hash the zig type tag
2480 // at the top of this function.2521 // at the top of this function.
2481 if (val.castTag(.function)) |func| {2522 if (val.castTag(.function)) |func| {
...@@ -2497,6 +2538,64 @@ pub const Value = extern union {...@@ -2497,6 +2538,64 @@ pub const Value = extern union {
2497 }2538 }
2498 }2539 }
24992540
2541 /// This is a more conservative hash function that produces equal hashes for values
2542 /// that can coerce into each other.
2543 /// This function is used by hash maps and so treats floating-point NaNs as equal
2544 /// to each other, and not equal to other floating-point values.
2545 pub fn hashUncoerced(val: Value, ty: Type, hasher: *std.hash.Wyhash, mod: *Module) void {
2546 if (val.isUndef()) return;
2547 // The value is runtime-known and shouldn't affect the hash.
2548 if (val.tag() == .runtime_value) return;
2549
2550 switch (ty.zigTypeTag()) {
2551 .BoundFn => unreachable, // TODO remove this from the language
2552 .Opaque => unreachable, // Cannot hash opaque types
2553 .Void,
2554 .NoReturn,
2555 .Undefined,
2556 .Null,
2557 .Struct, // It sure would be nice to do something clever with structs.
2558 => |zig_type_tag| std.hash.autoHash(hasher, zig_type_tag),
2559 .Type => {
2560 var buf: ToTypeBuffer = undefined;
2561 val.toType(&buf).hashWithHasher(hasher, mod);
2562 },
2563 .Float, .ComptimeFloat => std.hash.autoHash(hasher, @bitCast(u128, val.toFloat(f128))),
2564 .Bool, .Int, .ComptimeInt, .Pointer, .Fn => switch (val.tag()) {
2565 .slice => val.castTag(.slice).?.data.ptr.hashPtr(hasher, mod.getTarget()),
2566 else => val.hashPtr(hasher, mod.getTarget()),
2567 },
2568 .Array, .Vector => {
2569 const len = ty.arrayLen();
2570 const elem_ty = ty.childType();
2571 var index: usize = 0;
2572 var elem_value_buf: ElemValueBuffer = undefined;
2573 while (index < len) : (index += 1) {
2574 const elem_val = val.elemValueBuffer(mod, index, &elem_value_buf);
2575 elem_val.hashUncoerced(elem_ty, hasher, mod);
2576 }
2577 },
2578 .Optional => if (val.castTag(.opt_payload)) |payload| {
2579 var buf: Type.Payload.ElemType = undefined;
2580 const child_ty = ty.optionalChild(&buf);
2581 payload.data.hashUncoerced(child_ty, hasher, mod);
2582 } else std.hash.autoHash(hasher, std.builtin.TypeId.Null),
2583 .ErrorSet, .ErrorUnion => if (val.getError()) |err| hasher.update(err) else {
2584 const pl_ty = ty.errorUnionPayload();
2585 val.castTag(.eu_payload).?.data.hashUncoerced(pl_ty, hasher, mod);
2586 },
2587 .Enum, .EnumLiteral, .Union => {
2588 hasher.update(val.tagName(ty, mod));
2589 if (val.cast(Payload.Union)) |union_obj| {
2590 const active_field_ty = ty.unionFieldType(union_obj.data.tag, mod);
2591 union_obj.data.val.hashUncoerced(active_field_ty, hasher, mod);
2592 } else std.hash.autoHash(hasher, std.builtin.TypeId.Void);
2593 },
2594 .Frame => @panic("TODO implement hashing frame values"),
2595 .AnyFrame => @panic("TODO implement hashing anyframe values"),
2596 }
2597 }
2598
2500 pub const ArrayHashContext = struct {2599 pub const ArrayHashContext = struct {
2501 ty: Type,2600 ty: Type,
2502 mod: *Module,2601 mod: *Module,