From 73feb108d9fdcacaaf048e7d55400ded72c65f24 Mon Sep 17 00:00:00 2001 From: "taylor.fish" Date: Fri, 26 Sep 2025 23:02:44 -0700 Subject: [PATCH] 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) --- src/memory.zig | 53 +++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/memory.zig b/src/memory.zig index 6f41f9fed9..cd3d076bd9 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -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(); } }