Commit 8f9d857932

Andrew Kelley <andrew@ziglang.org>
2021-12-01 07:56:57
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.
1 parent 7355a20
Changed files (2)
src/Sema.zig
@@ -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,
src/type.zig
@@ -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;
                         },