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> 2022-11-10 14:35:57-05:00
loge40c38d258800cd555a4b53af8c711886ca0d38d
treeb86544249ad5a7ed4ee405defe126da9edd1172c
parent59af6417bbb93a2cca453d930320217a970040bd

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(
68286828 // don't find out until after generating a monomorphed function whether the parameter
68296829 // type ended up being a "must-be-comptime-known" type.
68306830 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
68336833 const generic_args = try sema.arena.alloc(GenericCallAdapter.Arg, func_ty_info.param_types.len);
68346834 {
......@@ -6871,7 +6871,7 @@ fn instantiateGenericCall(
68716871 },
68726872 else => |e| return e,
68736873 };
6874 arg_val.hash(arg_ty, &hasher, mod);
6874 arg_val.hashUncoerced(arg_ty, &hasher, mod);
68756875 if (is_anytype) {
68766876 arg_ty.hashWithHasher(&hasher, mod);
68776877 generic_args[i] = .{
src/value.zig+112-13
......@@ -1055,6 +1055,40 @@ pub const Value = extern union {
10551055 }
10561056 }
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
10581092 /// Asserts the value is an integer.
10591093 pub fn toBigInt(val: Value, space: *BigIntSpace, target: Target) BigIntConst {
10601094 return val.toBigIntAdvanced(space, target, null) catch unreachable;
......@@ -2211,7 +2245,7 @@ pub const Value = extern union {
22112245 return eqlAdvanced(a_union.val, active_field_ty, b_union.val, active_field_ty, mod, sema_kit);
22122246 },
22132247 else => {},
2214 } else if (a_tag == .null_value or b_tag == .null_value) {
2248 } else if (b_tag == .null_value or b_tag == .@"error") {
22152249 return false;
22162250 } else if (a_tag == .undef or b_tag == .undef) {
22172251 return false;
......@@ -2335,18 +2369,25 @@ pub const Value = extern union {
23352369 if (a_nan) return true;
23362370 return a_float == b_float;
23372371 },
2338 .Optional => {
2339 if (a.tag() != .opt_payload and b.tag() == .opt_payload) {
2340 var buffer: Payload.SubValue = .{
2341 .base = .{ .tag = .opt_payload },
2342 .data = a,
2343 };
2344 const opt_val = Value.initPayload(&buffer.base);
2345 return eqlAdvanced(opt_val, ty, b, ty, mod, sema_kit);
2346 }
2372 .Optional => if (a_tag != .null_value and b_tag == .opt_payload) {
2373 var sub_pl: Payload.SubValue = .{
2374 .base = .{ .tag = b.tag() },
2375 .data = a,
2376 };
2377 const sub_val = Value.initPayload(&sub_pl.base);
2378 return eqlAdvanced(sub_val, ty, b, ty, mod, sema_kit);
2379 },
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);
23472387 },
23482388 else => {},
23492389 }
2390 if (a_tag == .null_value or a_tag == .@"error") return false;
23502391 return (try orderAdvanced(a, b, target, sema_kit)).compare(.eq);
23512392 }
23522393
......@@ -2436,7 +2477,7 @@ pub const Value = extern union {
24362477 const sub_ty = ty.optionalChild(&buffer);
24372478 sub_val.hash(sub_ty, hasher, mod);
24382479 } else {
2439 std.hash.autoHash(hasher, false); // non-null
2480 std.hash.autoHash(hasher, false); // null
24402481 }
24412482 },
24422483 .ErrorUnion => {
......@@ -2474,8 +2515,8 @@ pub const Value = extern union {
24742515 union_obj.val.hash(active_field_ty, hasher, mod);
24752516 },
24762517 .Fn => {
2477 // Note that his hashes the *Fn/*ExternFn rather than the *Decl. This is
2478 // to differentiate function bodies from function pointers.
2518 // Note that this hashes the *Fn/*ExternFn rather than the *Decl.
2519 // This is to differentiate function bodies from function pointers.
24792520 // This is currently redundant since we already hash the zig type tag
24802521 // at the top of this function.
24812522 if (val.castTag(.function)) |func| {
......@@ -2497,6 +2538,64 @@ pub const Value = extern union {
24972538 }
24982539 }
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
25002599 pub const ArrayHashContext = struct {
25012600 ty: Type,
25022601 mod: *Module,