Handle optionals in bun.memory.deinit (#23027)

Currently, if you try to deinit an optional, `bun.memory.deinit` will
silently do nothing, even if the optional's payload is a struct with a
`deinit` method.

This commit makes sure the payload is deinitialized.

(For internal tracking: fixes STAB-1293)
This commit is contained in:
taylor.fish
2025-09-26 23:02:44 -07:00
committed by GitHub
parent 5179dad481
commit 73feb108d9

View File

@@ -65,15 +65,17 @@ fn deinitIsVoid(comptime T: type) bool {
};
}
/// Calls `deinit` on `ptr_or_slice`, or on every element of `ptr_or_slice`, if such a `deinit`
/// method exists.
/// Calls `deinit` on `ptr_or_slice`, or on every element of `ptr_or_slice`, if the pointer points
/// to a struct or tagged union.
///
/// This function first does the following:
///
/// * If `ptr_or_slice` is a single-item pointer, calls `ptr_or_slice.deinit()`, if that method
/// exists.
/// * If `ptr_or_slice` is a slice, calls `deinit` on every element of the slice, if the slice
/// elements have a `deinit` method.
/// * If `ptr_or_slice` is a single-item pointer of type `*T`:
/// - If `T` is a struct or tagged union, calls `ptr_or_slice.deinit()`
/// - If `T` is an optional, checks if `ptr_or_slice` points to a non-null value, and if so,
/// calls `bun.memory.deinit` with a pointer to the payload.
/// * If `ptr_or_slice` is a slice, for each element of the slice, calls `bun.memory.deinit` with
/// a pointer to the element.
///
/// Then, if `ptr_or_slice` is non-const, this function also sets all memory referenced by the
/// pointer to `undefined`.
@@ -81,32 +83,43 @@ fn deinitIsVoid(comptime T: type) bool {
/// This method does not free `ptr_or_slice` itself.
pub fn deinit(ptr_or_slice: anytype) void {
const ptr_info = @typeInfo(@TypeOf(ptr_or_slice));
switch (comptime ptr_info.pointer.size) {
.slice => {
for (ptr_or_slice) |*elem| {
deinit(elem);
}
return;
},
.one => {},
else => @compileError("unsupported pointer type"),
}
const Child = ptr_info.pointer.child;
const mutable = !ptr_info.pointer.is_const;
defer {
if (comptime mutable) {
ptr_or_slice.* = undefined;
}
}
const needs_deinit = comptime switch (@typeInfo(Child)) {
.@"struct" => true,
.@"union" => |u| u.tag_type != null,
.optional => {
if (ptr_or_slice.*) |*payload| {
deinit(payload);
}
return;
},
else => false,
};
const should_call_deinit = comptime needs_deinit and
!exemptedFromDeinit(Child) and
!deinitIsVoid(Child);
switch (comptime ptr_info.pointer.size) {
.one => {
if (comptime should_call_deinit) {
ptr_or_slice.deinit();
}
if (comptime mutable) ptr_or_slice.* = undefined;
},
.slice => for (ptr_or_slice) |*elem| {
if (comptime should_call_deinit) {
elem.deinit();
}
if (comptime mutable) elem.* = undefined;
},
else => @compileError("unsupported pointer type"),
if (comptime should_call_deinit) {
ptr_or_slice.deinit();
}
}