From 45bf609dcc6fa72d319794bfbedcb5c9cf49365d Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Mon, 3 Nov 2025 19:28:03 +0000 Subject: [PATCH] Phase 3, Step 3.2: Add ResponseMetadataHolder ownership wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ResponseMetadataHolder wrapper struct to make metadata ownership transfer explicit and ensure metadata is only transferred once to the Response object. This prevents use-after-free and double-free bugs. Added: - ResponseMetadataHolder struct with private metadata and certificate fields - takeMetadata() method for one-time ownership transfer - takeCertificate() method for one-time ownership transfer - setMetadata() method that frees old metadata before storing new - setCertificate() method that frees old certificate before storing new - Single deinit() method with null-safe cleanup The take semantics ensure metadata can only be consumed once, with subsequent calls returning null. This makes the ownership contract explicit and prevents common memory safety bugs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/bun.js/webcore/fetch/FetchTasklet.zig | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/bun.js/webcore/fetch/FetchTasklet.zig b/src/bun.js/webcore/fetch/FetchTasklet.zig index 7cbe27a8f2..d37ec2591c 100644 --- a/src/bun.js/webcore/fetch/FetchTasklet.zig +++ b/src/bun.js/webcore/fetch/FetchTasklet.zig @@ -397,6 +397,66 @@ const RequestHeaders = struct { } }; +/// Response metadata with explicit take semantics. +/// Ensures metadata is only transferred once to Response object. +/// +/// OWNERSHIP MODEL: +/// - Metadata and certificate info are owned until taken +/// - Take methods transfer ownership to caller +/// - Set methods take ownership of new values +/// - deinit() frees any remaining owned data +const ResponseMetadataHolder = struct { + #metadata: ?http.HTTPResponseMetadata = null, + #certificate_info: ?http.CertificateInfo = null, + allocator: std.mem.Allocator, + + fn init(allocator: std.mem.Allocator) ResponseMetadataHolder { + return .{ .allocator = allocator }; + } + + /// Take metadata, transferring ownership to caller. + /// Can only be called once - subsequent calls return null. + fn takeMetadata(self: *ResponseMetadataHolder) ?http.HTTPResponseMetadata { + const metadata = self.#metadata; + self.#metadata = null; // Clear to prevent double-take + return metadata; + } + + /// Take certificate info, transferring ownership to caller. + fn takeCertificate(self: *ResponseMetadataHolder) ?http.CertificateInfo { + const cert = self.#certificate_info; + self.#certificate_info = null; + return cert; + } + + /// Set metadata from HTTP result (takes ownership). + /// Frees old metadata if present. + fn setMetadata(self: *ResponseMetadataHolder, metadata: http.HTTPResponseMetadata) void { + if (self.#metadata) |old| { + old.deinit(self.allocator); + } + self.#metadata = metadata; + } + + /// Set certificate info from HTTP result (takes ownership). + fn setCertificate(self: *ResponseMetadataHolder, cert: http.CertificateInfo) void { + if (self.#certificate_info) |old| { + old.deinit(self.allocator); + } + self.#certificate_info = cert; + } + + /// Single cleanup path + fn deinit(self: *ResponseMetadataHolder) void { + if (self.#metadata) |metadata| { + metadata.deinit(self.allocator); + } + if (self.#certificate_info) |cert| { + cert.deinit(self.allocator); + } + } +}; + pub const FetchTasklet = struct { pub const ResumableSink = jsc.WebCore.ResumableFetchSink;