diff options
| author | kcbanner <kcbanner@gmail.com> | 2023-04-23 19:09:12 -0400 |
|---|---|---|
| committer | kcbanner <kcbanner@gmail.com> | 2023-04-27 01:11:57 -0400 |
| commit | 2b592d7e3cf328deb1b8ffa7ea88389d785837ff (patch) | |
| tree | 111892a8c4ef5301207a497382fe4fc6f51a3f4d /src/Module.zig | |
| parent | 396bd51c4818752f6309ff10f2d316f41598d5cd (diff) | |
| download | zig-2b592d7e3cf328deb1b8ffa7ea88389d785837ff.tar.gz zig-2b592d7e3cf328deb1b8ffa7ea88389d785837ff.zip | |
sema: Rework Decl.value_arena to fix another memory corruption issue
This fixes a bug where resolveStructLayout to was promoting from stale
value_arena state which was then overwrriten when another ArenaAllocator
higher in the call stack saved its state back. This resulted in the memory
for struct_obj.optmized_order overlapping existing allocations.
My initial fix in c7067ef wasn't sufficient, as it only checked if the struct being
resolved had the same owner as the current sema instance. However, it's
possible for resolveStructLayout to be called when the sema instance
has a different owner, but the struct decl's value_arena is currently in
use higher up in the callstack.
This change introduces ValueArena, which holds the arena state as well as tracks
if an arena has already been promoted from it. This allows callers to use the
value_arena storage without needing to be aware of another user of this same storage
higher up in the call stack.
Diffstat (limited to 'src/Module.zig')
| -rw-r--r-- | src/Module.zig | 65 |
1 files changed, 52 insertions, 13 deletions
diff --git a/src/Module.zig b/src/Module.zig index 6738322d01..c148ee44e3 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -411,6 +411,43 @@ pub const WipCaptureScope = struct { } }; +const ValueArena = struct { + state: std.heap.ArenaAllocator.State, + state_acquired: ?*std.heap.ArenaAllocator.State = null, + + /// If non-zero, then an ArenaAllocator has been promoted from `state`, + /// and `state_acquired` points to its state field. + ref_count: usize = 0, + + /// Returns an allocator backed by either promoting `state`, or by the existing ArenaAllocator + /// that has already promoted `state`. `out_arena_allocator` provides storage for the initial promotion, + /// and must live until the matching call to release() + pub fn acquire(self: *ValueArena, child_allocator: Allocator, out_arena_allocator: *std.heap.ArenaAllocator) Allocator { + defer self.ref_count += 1; + + if (self.state_acquired) |state_acquired| { + const arena_allocator = @fieldParentPtr( + std.heap.ArenaAllocator, + "state", + state_acquired, + ); + return arena_allocator.allocator(); + } + + out_arena_allocator.* = self.state.promote(child_allocator); + self.state_acquired = &out_arena_allocator.state; + return out_arena_allocator.allocator(); + } + + pub fn release(self: *ValueArena) void { + self.ref_count -= 1; + if (self.ref_count == 0) { + self.state = self.state_acquired.?.*; + self.state_acquired = null; + } + } +}; + pub const Decl = struct { /// Allocated with Module's allocator; outlives the ZIR code. name: [*:0]const u8, @@ -429,7 +466,7 @@ pub const Decl = struct { @"addrspace": std.builtin.AddressSpace, /// The memory for ty, val, align, linksection, and captures. /// If this is `null` then there is no memory management needed. - value_arena: ?*std.heap.ArenaAllocator.State = null, + value_arena: ?*ValueArena = null, /// The direct parent namespace of the Decl. /// Reference to externally owned memory. /// In the case of the Decl corresponding to a file, this is @@ -607,7 +644,7 @@ pub const Decl = struct { variable.deinit(gpa); gpa.destroy(variable); } - if (decl.value_arena) |arena_state| { + if (decl.value_arena) |value_arena| { if (decl.owns_tv) { if (decl.val.castTag(.str_lit)) |str_lit| { mod.string_literal_table.getPtrContext(str_lit.data, .{ @@ -615,7 +652,8 @@ pub const Decl = struct { }).?.* = .none; } } - arena_state.promote(gpa).deinit(); + assert(value_arena.ref_count == 0); + value_arena.state.promote(gpa).deinit(); decl.value_arena = null; decl.has_tv = false; decl.owns_tv = false; @@ -624,9 +662,9 @@ pub const Decl = struct { pub fn finalizeNewArena(decl: *Decl, arena: *std.heap.ArenaAllocator) !void { assert(decl.value_arena == null); - const arena_state = try arena.allocator().create(std.heap.ArenaAllocator.State); - arena_state.* = arena.state; - decl.value_arena = arena_state; + const value_arena = try arena.allocator().create(ValueArena); + value_arena.* = .{ .state = arena.state }; + decl.value_arena = value_arena; } /// This name is relative to the containing namespace of the decl. @@ -4538,14 +4576,15 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { var decl_arena = std.heap.ArenaAllocator.init(gpa); const decl_arena_allocator = decl_arena.allocator(); - const decl_arena_state = blk: { + const decl_value_arena = blk: { errdefer decl_arena.deinit(); - const s = try decl_arena_allocator.create(std.heap.ArenaAllocator.State); + const s = try decl_arena_allocator.create(ValueArena); + s.* = .{ .state = undefined }; break :blk s; }; defer { - decl_arena_state.* = decl_arena.state; - decl.value_arena = decl_arena_state; + decl_value_arena.state = decl_arena.state; + decl.value_arena = decl_value_arena; } var analysis_arena = std.heap.ArenaAllocator.init(gpa); @@ -5493,9 +5532,9 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air { const decl = mod.declPtr(decl_index); // Use the Decl's arena for captured values. - var decl_arena = decl.value_arena.?.promote(gpa); - defer decl.value_arena.?.* = decl_arena.state; - const decl_arena_allocator = decl_arena.allocator(); + var decl_arena: std.heap.ArenaAllocator = undefined; + const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena); + defer decl.value_arena.?.release(); var sema: Sema = .{ .mod = mod, |
