Sema: fix error set merging creating references to invalid memory

Trying to use std.heap.page_allocator with stage2 now results in
(incorrect) compile errors rather than UAF.
This commit is contained in:
Andrew Kelley 2021-11-30 23:56:57 -07:00
parent 7355a20133
commit 8f9d857932
2 changed files with 77 additions and 15 deletions

View File

@ -4554,9 +4554,7 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
if (lhs_ty.tag() == .anyerror or rhs_ty.tag() == .anyerror) {
return Air.Inst.Ref.anyerror_type;
}
// When we support inferred error sets, we'll want to use a data structure that can
// represent a merged set of errors without forcing them to be resolved here. Until then
// we re-use the same data structure that is used for explicit error set declarations.
// Resolve both error sets now.
var set: std.StringHashMapUnmanaged(void) = .{};
defer set.deinit(sema.gpa);
@ -4565,6 +4563,12 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
const name = lhs_ty.castTag(.error_set_single).?.data;
try set.put(sema.gpa, name, {});
},
.error_set_merged => {
const names = lhs_ty.castTag(.error_set_merged).?.data;
for (names) |name| {
try set.put(sema.gpa, name, {});
}
},
.error_set => {
const lhs_set = lhs_ty.castTag(.error_set).?.data;
try set.ensureUnusedCapacity(sema.gpa, lhs_set.names_len);
@ -4579,6 +4583,12 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
const name = rhs_ty.castTag(.error_set_single).?.data;
try set.put(sema.gpa, name, {});
},
.error_set_merged => {
const names = rhs_ty.castTag(.error_set_merged).?.data;
for (names) |name| {
try set.put(sema.gpa, name, {});
}
},
.error_set => {
const rhs_set = rhs_ty.castTag(.error_set).?.data;
try set.ensureUnusedCapacity(sema.gpa, rhs_set.names_len);
@ -4589,22 +4599,25 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
else => unreachable,
}
const new_names = try sema.arena.alloc([]const u8, set.count());
// TODO do we really want to create a Decl for this?
// The reason we do it right now is for memory management.
var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit();
const new_names = try anon_decl.arena().alloc([]const u8, set.count());
var it = set.keyIterator();
var i: usize = 0;
while (it.next()) |key| : (i += 1) {
new_names[i] = key.*;
}
const new_error_set = try sema.arena.create(Module.ErrorSet);
new_error_set.* = .{
.owner_decl = sema.owner_decl,
.node_offset = inst_data.src_node,
.names_ptr = new_names.ptr,
.names_len = @intCast(u32, new_names.len),
};
const error_set_ty = try Type.Tag.error_set.create(sema.arena, new_error_set);
return sema.addConstant(Type.type, try Value.Tag.ty.create(sema.arena, error_set_ty));
const err_set_ty = try Type.Tag.error_set_merged.create(anon_decl.arena(), new_names);
const err_set_decl = try anon_decl.finish(
Type.type,
try Value.Tag.ty.create(anon_decl.arena(), err_set_ty),
);
try sema.mod.declareDeclDependency(sema.owner_decl, err_set_decl);
return sema.addType(err_set_ty);
}
fn zirEnumLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -14788,6 +14801,7 @@ fn typeHasOnePossibleValue(
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.var_args_param,
.manyptr_u8,

View File

@ -61,11 +61,17 @@ pub const Type = extern union {
.c_longdouble,
=> return .Float,
.error_set,
.error_set_single,
.anyerror,
.error_set_inferred,
.error_set_merged,
=> return .ErrorSet,
.c_void, .@"opaque" => return .Opaque,
.bool => return .Bool,
.void => return .Void,
.type => return .Type,
.error_set, .error_set_single, .anyerror, .error_set_inferred => return .ErrorSet,
.comptime_int => return .ComptimeInt,
.comptime_float => return .ComptimeFloat,
.noreturn => return .NoReturn,
@ -608,6 +614,9 @@ pub const Type = extern union {
return true;
},
.ErrorSet => {
// TODO: revisit the language specification for how to evaluate equality
// for error set types.
if (a.tag() == .anyerror and b.tag() == .anyerror) {
return true;
}
@ -892,6 +901,14 @@ pub const Type = extern union {
.payload = try payload.payload.copy(allocator),
});
},
.error_set_merged => {
const names = self.castTag(.error_set_merged).?.data;
const duped_names = try allocator.alloc([]const u8, names.len);
for (duped_names) |*name, i| {
name.* = try allocator.dupe(u8, names[i]);
}
return Tag.error_set_merged.create(allocator, duped_names);
},
.error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
.error_set_inferred => return self.copyPayloadShallow(allocator, Payload.ErrorSetInferred),
.error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
@ -1185,6 +1202,16 @@ pub const Type = extern union {
const func = ty.castTag(.error_set_inferred).?.data.func;
return writer.print("(inferred error set of {s})", .{func.owner_decl.name});
},
.error_set_merged => {
const names = ty.castTag(.error_set_merged).?.data;
try writer.writeAll("error{");
for (names) |name, i| {
if (i != 0) try writer.writeByte(',');
try writer.writeAll(name);
}
try writer.writeAll("}");
return;
},
.error_set_single => {
const name = ty.castTag(.error_set_single).?.data;
return writer.print("error{{{s}}}", .{name});
@ -1365,6 +1392,7 @@ pub const Type = extern union {
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.generic_poison,
.array_u8,
@ -1525,6 +1553,7 @@ pub const Type = extern union {
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.manyptr_u8,
.manyptr_const_u8,
.atomic_order,
@ -1783,6 +1812,7 @@ pub const Type = extern union {
.anyerror_void_error_union,
.anyerror,
.error_set_inferred,
.error_set_merged,
=> return 2, // TODO revisit this when we have the concept of the error tag type
.array, .array_sentinel => return self.elemType().abiAlignment(target),
@ -2021,6 +2051,7 @@ pub const Type = extern union {
.anyerror_void_error_union,
.anyerror,
.error_set_inferred,
.error_set_merged,
=> return 2, // TODO revisit this when we have the concept of the error tag type
.int_signed, .int_unsigned => {
@ -2199,6 +2230,7 @@ pub const Type = extern union {
.anyerror_void_error_union,
.anyerror,
.error_set_inferred,
.error_set_merged,
=> return 16, // TODO revisit this when we have the concept of the error tag type
.int_signed, .int_unsigned => self.cast(Payload.Bits).?.data,
@ -2961,7 +2993,7 @@ pub const Type = extern union {
return .{ .signedness = .unsigned, .bits = smallestUnsignedBits(field_count - 1) };
},
.error_set, .error_set_single, .anyerror, .error_set_inferred => {
.error_set, .error_set_single, .anyerror, .error_set_inferred, .error_set_merged => {
// TODO revisit this when error sets support custom int types
return .{ .signedness = .unsigned, .bits = 16 };
},
@ -3250,6 +3282,7 @@ pub const Type = extern union {
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.var_args_param,
.manyptr_u8,
@ -3882,6 +3915,7 @@ pub const Type = extern union {
error_set_single,
/// The type is the inferred error set of a specific function.
error_set_inferred,
error_set_merged,
empty_struct,
@"opaque",
@"struct",
@ -3986,6 +4020,7 @@ pub const Type = extern union {
.error_set => Payload.ErrorSet,
.error_set_inferred => Payload.ErrorSetInferred,
.error_set_merged => Payload.ErrorSetMerged,
.array, .vector => Payload.Array,
.array_sentinel => Payload.ArraySentinel,
@ -4090,6 +4125,13 @@ pub const Type = extern union {
data: *Module.ErrorSet,
};
pub const ErrorSetMerged = struct {
pub const base_tag = Tag.error_set_merged;
base: Payload = Payload{ .tag = base_tag },
data: []const []const u8,
};
pub const ErrorSetInferred = struct {
pub const base_tag = Tag.error_set_inferred;
@ -4125,6 +4167,12 @@ pub const Type = extern union {
try self.map.put(gpa, entry.key_ptr.*, {});
}
},
.error_set_merged => {
const names = err_set_ty.castTag(.error_set_merged).?.data;
for (names) |name| {
try self.map.put(gpa, name, {});
}
},
.anyerror => {
self.is_anyerror = true;
},