Skip to content

Commit d2d97f6

Browse files
committed
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.
1 parent c75e11b commit d2d97f6

File tree

3 files changed

+91
-33
lines changed

3 files changed

+91
-33
lines changed

src/Module.zig

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,43 @@ pub const WipCaptureScope = struct {
411411
}
412412
};
413413

414+
const ValueArena = struct {
415+
state: std.heap.ArenaAllocator.State,
416+
state_acquired: ?*std.heap.ArenaAllocator.State = null,
417+
418+
/// If non-zero, then an ArenaAllocator has been promoted from `state`,
419+
/// and `state_acquired` points to its state field.
420+
ref_count: usize = 0,
421+
422+
/// Returns an allocator backed by either promoting `state`, or by the existing ArenaAllocator
423+
/// that has already promoted `state`. `out_arena_allocator` provides storage for the initial promotion,
424+
/// and must live until the matching call to release()
425+
pub fn acquire(self: *ValueArena, child_allocator: Allocator, out_arena_allocator: *std.heap.ArenaAllocator) Allocator {
426+
defer self.ref_count += 1;
427+
428+
if (self.state_acquired) |state_acquired| {
429+
const arena_allocator = @fieldParentPtr(
430+
std.heap.ArenaAllocator,
431+
"state",
432+
state_acquired,
433+
);
434+
return arena_allocator.allocator();
435+
}
436+
437+
out_arena_allocator.* = self.state.promote(child_allocator);
438+
self.state_acquired = &out_arena_allocator.state;
439+
return out_arena_allocator.allocator();
440+
}
441+
442+
pub fn release(self: *ValueArena) void {
443+
self.ref_count -= 1;
444+
if (self.ref_count == 0) {
445+
self.state = self.state_acquired.?.*;
446+
self.state_acquired = null;
447+
}
448+
}
449+
};
450+
414451
pub const Decl = struct {
415452
/// Allocated with Module's allocator; outlives the ZIR code.
416453
name: [*:0]const u8,
@@ -429,7 +466,7 @@ pub const Decl = struct {
429466
@"addrspace": std.builtin.AddressSpace,
430467
/// The memory for ty, val, align, linksection, and captures.
431468
/// If this is `null` then there is no memory management needed.
432-
value_arena: ?*std.heap.ArenaAllocator.State = null,
469+
value_arena: ?*ValueArena = null,
433470
/// The direct parent namespace of the Decl.
434471
/// Reference to externally owned memory.
435472
/// In the case of the Decl corresponding to a file, this is
@@ -607,15 +644,16 @@ pub const Decl = struct {
607644
variable.deinit(gpa);
608645
gpa.destroy(variable);
609646
}
610-
if (decl.value_arena) |arena_state| {
647+
if (decl.value_arena) |value_arena| {
611648
if (decl.owns_tv) {
612649
if (decl.val.castTag(.str_lit)) |str_lit| {
613650
mod.string_literal_table.getPtrContext(str_lit.data, .{
614651
.bytes = &mod.string_literal_bytes,
615652
}).?.* = .none;
616653
}
617654
}
618-
arena_state.promote(gpa).deinit();
655+
assert(value_arena.ref_count == 0);
656+
value_arena.state.promote(gpa).deinit();
619657
decl.value_arena = null;
620658
decl.has_tv = false;
621659
decl.owns_tv = false;
@@ -624,9 +662,9 @@ pub const Decl = struct {
624662

625663
pub fn finalizeNewArena(decl: *Decl, arena: *std.heap.ArenaAllocator) !void {
626664
assert(decl.value_arena == null);
627-
const arena_state = try arena.allocator().create(std.heap.ArenaAllocator.State);
628-
arena_state.* = arena.state;
629-
decl.value_arena = arena_state;
665+
const value_arena = try arena.allocator().create(ValueArena);
666+
value_arena.* = .{ .state = arena.state };
667+
decl.value_arena = value_arena;
630668
}
631669

632670
/// This name is relative to the containing namespace of the decl.
@@ -4538,14 +4576,15 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
45384576
var decl_arena = std.heap.ArenaAllocator.init(gpa);
45394577
const decl_arena_allocator = decl_arena.allocator();
45404578

4541-
const decl_arena_state = blk: {
4579+
const decl_value_arena = blk: {
45424580
errdefer decl_arena.deinit();
4543-
const s = try decl_arena_allocator.create(std.heap.ArenaAllocator.State);
4581+
const s = try decl_arena_allocator.create(ValueArena);
4582+
s.* = .{ .state = undefined };
45444583
break :blk s;
45454584
};
45464585
defer {
4547-
decl_arena_state.* = decl_arena.state;
4548-
decl.value_arena = decl_arena_state;
4586+
decl_value_arena.state = decl_arena.state;
4587+
decl.value_arena = decl_value_arena;
45494588
}
45504589

45514590
var analysis_arena = std.heap.ArenaAllocator.init(gpa);
@@ -5493,9 +5532,9 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
54935532
const decl = mod.declPtr(decl_index);
54945533

54955534
// Use the Decl's arena for captured values.
5496-
var decl_arena = decl.value_arena.?.promote(gpa);
5497-
defer decl.value_arena.?.* = decl_arena.state;
5498-
const decl_arena_allocator = decl_arena.allocator();
5535+
var decl_arena: std.heap.ArenaAllocator = undefined;
5536+
const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
5537+
defer decl.value_arena.?.release();
54995538

55005539
var sema: Sema = .{
55015540
.mod = mod,

src/Sema.zig

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2855,9 +2855,9 @@ fn zirEnumDecl(
28552855
const decl_val = try sema.analyzeDeclVal(block, src, new_decl_index);
28562856
done = true;
28572857

2858-
var decl_arena = new_decl.value_arena.?.promote(gpa);
2859-
defer new_decl.value_arena.?.* = decl_arena.state;
2860-
const decl_arena_allocator = decl_arena.allocator();
2858+
var decl_arena: std.heap.ArenaAllocator = undefined;
2859+
const decl_arena_allocator = new_decl.value_arena.?.acquire(gpa, &decl_arena);
2860+
defer new_decl.value_arena.?.release();
28612861

28622862
extra_index = try mod.scanNamespace(&enum_obj.namespace, extra_index, decls_len, new_decl);
28632863

@@ -26719,13 +26719,12 @@ const ComptimePtrMutationKit = struct {
2671926719

2672026720
fn beginArena(self: *ComptimePtrMutationKit, mod: *Module) Allocator {
2672126721
const decl = mod.declPtr(self.decl_ref_mut.decl_index);
26722-
self.decl_arena = decl.value_arena.?.promote(mod.gpa);
26723-
return self.decl_arena.allocator();
26722+
return decl.value_arena.?.acquire(mod.gpa, &self.decl_arena);
2672426723
}
2672526724

2672626725
fn finishArena(self: *ComptimePtrMutationKit, mod: *Module) void {
2672726726
const decl = mod.declPtr(self.decl_ref_mut.decl_index);
26728-
decl.value_arena.?.* = self.decl_arena.state;
26727+
decl.value_arena.?.release();
2672926728
self.decl_arena = undefined;
2673026729
}
2673126730
};
@@ -26756,6 +26755,7 @@ fn beginComptimePtrMutation(
2675626755
.elem_ptr => {
2675726756
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
2675826757
var parent = try sema.beginComptimePtrMutation(block, src, elem_ptr.array_ptr, elem_ptr.elem_ty);
26758+
2675926759
switch (parent.pointee) {
2676026760
.direct => |val_ptr| switch (parent.ty.zigTypeTag()) {
2676126761
.Array, .Vector => {
@@ -30373,10 +30373,9 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
3037330373
try sema.perm_arena.alloc(u32, struct_obj.fields.count())
3037430374
else blk: {
3037530375
const decl = sema.mod.declPtr(struct_obj.owner_decl);
30376-
var decl_arena = decl.value_arena.?.promote(sema.mod.gpa);
30377-
defer decl.value_arena.?.* = decl_arena.state;
30378-
const decl_arena_allocator = decl_arena.allocator();
30379-
30376+
var decl_arena: std.heap.ArenaAllocator = undefined;
30377+
const decl_arena_allocator = decl.value_arena.?.acquire(sema.mod.gpa, &decl_arena);
30378+
defer decl.value_arena.?.release();
3038030379
break :blk try decl_arena_allocator.alloc(u32, struct_obj.fields.count());
3038130380
};
3038230381

@@ -30420,9 +30419,9 @@ fn semaBackingIntType(mod: *Module, struct_obj: *Module.Struct) CompileError!voi
3042030419

3042130420
const decl_index = struct_obj.owner_decl;
3042230421
const decl = mod.declPtr(decl_index);
30423-
var decl_arena = decl.value_arena.?.promote(gpa);
30424-
defer decl.value_arena.?.* = decl_arena.state;
30425-
const decl_arena_allocator = decl_arena.allocator();
30422+
var decl_arena: std.heap.ArenaAllocator = undefined;
30423+
const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
30424+
defer decl.value_arena.?.release();
3042630425

3042730426
const zir = struct_obj.namespace.file_scope.zir;
3042830427
const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
@@ -31114,9 +31113,9 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
3111431113
}
3111531114

3111631115
const decl = mod.declPtr(decl_index);
31117-
var decl_arena = decl.value_arena.?.promote(gpa);
31118-
defer decl.value_arena.?.* = decl_arena.state;
31119-
const decl_arena_allocator = decl_arena.allocator();
31116+
var decl_arena: std.heap.ArenaAllocator = undefined;
31117+
const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
31118+
defer decl.value_arena.?.release();
3112031119

3112131120
var analysis_arena = std.heap.ArenaAllocator.init(gpa);
3112231121
defer analysis_arena.deinit();
@@ -31454,10 +31453,9 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
3145431453
extra_index += body.len;
3145531454

3145631455
const decl = mod.declPtr(decl_index);
31457-
31458-
var decl_arena = decl.value_arena.?.promote(gpa);
31459-
defer decl.value_arena.?.* = decl_arena.state;
31460-
const decl_arena_allocator = decl_arena.allocator();
31456+
var decl_arena: std.heap.ArenaAllocator = undefined;
31457+
const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
31458+
defer decl.value_arena.?.release();
3146131459

3146231460
var analysis_arena = std.heap.ArenaAllocator.init(gpa);
3146331461
defer analysis_arena.deinit();

test/cases/decl_value_arena.zig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
pub const Protocols: struct {
2+
list: *const fn(*Connection) void = undefined,
3+
handShake: type = struct {
4+
const stepStart: u8 = 0;
5+
},
6+
} = .{};
7+
8+
pub const Connection = struct {
9+
streamBuffer: [0]u8 = undefined,
10+
__lastReceivedPackets: [0]u8 = undefined,
11+
12+
handShakeState: u8 = Protocols.handShake.stepStart,
13+
};
14+
15+
pub fn main() void {
16+
var conn: Connection = undefined;
17+
_ = conn;
18+
}
19+
20+
// run
21+
//

0 commit comments

Comments
 (0)