diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig
index b928bb7e8d..f29c3508aa 100644
--- a/src/bun.js/api/html_rewriter.zig
+++ b/src/bun.js/api/html_rewriter.zig
@@ -197,7 +197,7 @@ pub const HTMLRewriter = struct {
if (kind != .other) {
{
- const body_value = try jsc.WebCore.Body.extract(global, response_value);
+ const body_value = try jsc.WebCore.Body.extract(global, response_value, null);
const resp = bun.new(Response, Response{
.init = .{
.status_code = 200,
diff --git a/src/bun.js/webcore/BakeResponse.zig b/src/bun.js/webcore/BakeResponse.zig
index 4c03c755c0..bc88feee79 100644
--- a/src/bun.js/webcore/BakeResponse.zig
+++ b/src/bun.js/webcore/BakeResponse.zig
@@ -44,7 +44,7 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, b
}
}
- return Response.constructor(globalThis, callframe);
+ return Response.constructor(globalThis, callframe, .zero);
}
pub export fn BakeResponseClass__constructRedirect(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) jsc.JSValue {
diff --git a/src/bun.js/webcore/Body.zig b/src/bun.js/webcore/Body.zig
index ac9c0b00e8..a8cf436090 100644
--- a/src/bun.js/webcore/Body.zig
+++ b/src/bun.js/webcore/Body.zig
@@ -16,9 +16,9 @@ pub fn use(this: *Body) Blob {
return this.value.use();
}
-pub fn clone(this: *Body, globalThis: *JSGlobalObject) bun.JSError!Body {
+pub fn clone(this: *Body, owner: jsc.WebCore.ReadableStream.Ref.Owner, globalThis: *JSGlobalObject, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Body {
return Body{
- .value = try this.value.clone(globalThis),
+ .value = try this.value.clone(owner, globalThis, readable_stream_tee),
};
}
@@ -504,6 +504,10 @@ pub const Value = union(Tag) {
}
pub fn fromJS(globalThis: *JSGlobalObject, value: JSValue) bun.JSError!Value {
+ return fromJSWithReadableStreamValue(globalThis, value, null);
+ }
+
+ pub fn fromJSWithReadableStreamValue(globalThis: *JSGlobalObject, value: JSValue, readable_stream_value: ?*JSValue) bun.JSError!Value {
value.ensureStillAlive();
if (value.isEmptyOrUndefinedOrNull()) {
@@ -598,6 +602,11 @@ pub const Value = union(Tag) {
else => {},
}
+ if (readable_stream_value) |readable_stream_ptr| {
+ readable_stream_ptr.* = readable.value;
+ return .{ .Locked = .{ .global = globalThis, .readable = .empty } };
+ }
+
return Body.Value.fromReadableStreamWithoutLockCheck(readable, globalThis);
}
@@ -941,23 +950,32 @@ pub const Value = union(Tag) {
}
}
- pub fn tee(this: *Value, globalThis: *jsc.JSGlobalObject) bun.JSError!Value {
+ pub fn tee(this: *Value, owner: jsc.WebCore.ReadableStream.Ref.Owner, globalThis: *jsc.JSGlobalObject, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Value {
var locked = &this.Locked;
- if (locked.readable.isDisturbed(.strong, globalThis)) {
- return Value{ .Used = {} };
+ if (locked.readable.isDisturbed(owner, globalThis)) {
+ return .Used;
}
- if (try locked.readable.tee(.{ .strong = {} }, globalThis)) |readable| {
- return Value{
+ if (try locked.readable.tee(owner, globalThis, readable_stream_tee)) |result| {
+ if (readable_stream_tee != null) {
+ return .{
+ .Locked = .{
+ .global = globalThis,
+ },
+ };
+ }
+
+ return .{
.Locked = .{
- .readable = .{ .strong = jsc.WebCore.ReadableStream.Strong.init(readable.@"0", globalThis) },
+ .readable = .{ .strong = .init(result.@"1", globalThis) },
.global = globalThis,
},
};
}
- if (locked.promise != null or locked.action != .none or locked.readable.has(.strong, globalThis)) {
- return Value{ .Used = {} };
+
+ if (locked.promise != null or locked.action != .none or locked.readable.has(owner, globalThis)) {
+ return .Used;
}
var drain_result: jsc.WebCore.DrainResult = .{
@@ -970,8 +988,8 @@ pub const Value = union(Tag) {
}
if (drain_result == .empty or drain_result == .aborted) {
- this.* = .{ .Null = {} };
- return Value{ .Null = {} };
+ this.* = .Null;
+ return .Null;
}
var reader = jsc.WebCore.ByteStream.Source.new(.{
@@ -994,28 +1012,35 @@ pub const Value = union(Tag) {
.ptr = .{ .Bytes = &reader.context },
.value = stream_value,
};
- locked.readable = .{ .strong = jsc.WebCore.ReadableStream.Strong.init(stream, globalThis) };
+ locked.readable.set(.strong, stream, globalThis);
if (locked.onReadableStreamAvailable) |onReadableStreamAvailable| {
- onReadableStreamAvailable(locked.task.?, globalThis, locked.readable.get(.{ .strong = {} }, globalThis).?);
+ onReadableStreamAvailable(locked.task.?, globalThis, locked.readable.get(owner, globalThis).?);
}
- const first, const second = (try locked.readable.tee(.strong, globalThis)) orelse return Value{ .Used = {} };
- locked.readable = .{ .strong = .init(first, globalThis) };
+ const tee_result = (try locked.readable.tee(owner, globalThis, readable_stream_tee)) orelse return Value{ .Used = {} };
+
+ if (readable_stream_tee != null) {
+ return .{
+ .Locked = .{
+ .global = globalThis,
+ },
+ };
+ }
return Value{
.Locked = .{
- .readable = .{ .strong = .init(second, globalThis) },
+ .readable = .{ .strong = .init(tee_result.@"1", globalThis) },
.global = globalThis,
},
};
}
- pub fn clone(this: *Value, globalThis: *jsc.JSGlobalObject) bun.JSError!Value {
- this.toBlobIfPossible(.empty);
+ pub fn clone(this: *Value, owner: jsc.WebCore.ReadableStream.Ref.Owner, globalThis: *jsc.JSGlobalObject, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Value {
+ this.toBlobIfPossible(owner);
if (this.* == .Locked) {
- return this.tee(globalThis);
+ return try this.tee(owner, globalThis, readable_stream_tee);
}
if (this.* == .InternalBlob) {
@@ -1054,10 +1079,11 @@ pub const Value = union(Tag) {
pub fn extract(
globalThis: *JSGlobalObject,
value: JSValue,
+ readable_stream_value: ?*JSValue,
) bun.JSError!Body {
var body = Body{ .value = Value{ .Null = {} } };
- body.value = try Value.fromJS(globalThis, value);
+ body.value = try Value.fromJSWithReadableStreamValue(globalThis, value, readable_stream_value);
if (body.value == .Blob) {
assert(!body.value.Blob.isHeapAllocated()); // owned by Body
}
@@ -1328,7 +1354,7 @@ pub const ValueBufferer = struct {
js_sink: ?*ArrayBufferSink.JSSink = null,
byte_stream: ?*jsc.WebCore.ByteStream = null,
// readable stream strong ref to keep byte stream alive
- readable_stream_ref: jsc.WebCore.ReadableStream.Ref = .{ .empty = {} },
+ readable_stream_ref: jsc.WebCore.ReadableStream.Strong = .{},
stream_buffer: bun.MutableString,
allocator: std.mem.Allocator,
global: *JSGlobalObject,
@@ -1564,14 +1590,15 @@ pub const ValueBufferer = struct {
assert(value.* == .Locked);
const locked = &value.Locked;
if (locked.readable.get(.{ .empty = {} }, sink.global)) |stream| {
- // keep the stream alive until we're done with it
- sink.readable_stream_ref = locked.readable;
- value.* = .{ .Used = {} };
-
if (stream.isLocked(sink.global)) {
return error.StreamAlreadyUsed;
}
+ // keep the stream alive until we're done with it
+ sink.readable_stream_ref = .init(stream, sink.global);
+ value.deinit();
+ value.* = .{ .Used = {} };
+
switch (stream.ptr) {
.Invalid => {
return error.InvalidStream;
diff --git a/src/bun.js/webcore/ReadableStream.zig b/src/bun.js/webcore/ReadableStream.zig
index 94cb4c5720..0a4086b662 100644
--- a/src/bun.js/webcore/ReadableStream.zig
+++ b/src/bun.js/webcore/ReadableStream.zig
@@ -29,7 +29,7 @@ pub const Ref = union(Type) {
.Response => {
if (owner == .Response) {
if (owner.Response.as(jsc.WebCore.Response)) |_| {
- if (jsc.WebCore.Response.js.gc.body.get(owner.Response)) |body_value| {
+ if (Response.js.gc.body.get(owner.Response)) |body_value| {
return ReadableStream.fromJS(body_value, global) catch null;
}
}
@@ -38,7 +38,7 @@ pub const Ref = union(Type) {
.Request => {
if (owner == .Request) {
if (owner.Request.as(jsc.WebCore.Request)) |_| {
- if (jsc.WebCore.Request.js.gc.body.get(owner.Request)) |body_value| {
+ if (Request.js.gc.body.get(owner.Request)) |body_value| {
return ReadableStream.fromJS(body_value, global) catch null;
}
}
@@ -55,9 +55,50 @@ pub const Ref = union(Type) {
return stream.isDisturbed(global);
}
- pub fn tee(this: *const Ref, owner: Owner, global: *jsc.JSGlobalObject) bun.JSError!?struct { ReadableStream, ReadableStream } {
+ pub fn setValue(this: *Ref, owner: Owner, stream_jsvalue: jsc.JSValue, global: *jsc.JSGlobalObject) void {
+ switch (owner) {
+ .Response => |jsvalue| {
+ if (jsvalue != .zero) {
+ Response.js.gc.body.set(jsvalue, global, stream_jsvalue);
+ this.deinit();
+ this.* = .Response;
+ } else {
+ this.deinit();
+ this.* = .empty;
+ }
+ },
+ .Request => |jsvalue| {
+ if (jsvalue != .zero) {
+ Request.js.gc.body.set(jsvalue, global, stream_jsvalue);
+ this.deinit();
+ this.* = .Request;
+ } else {
+ this.deinit();
+ this.* = .empty;
+ }
+ },
+ .strong => {
+ this.deinit();
+ this.* = .{ .strong = .init(stream_jsvalue, global) };
+ },
+ .empty => {},
+ }
+ }
+
+ pub fn set(this: *Ref, owner: Owner, stream: ReadableStream, global: *jsc.JSGlobalObject) void {
+ this.setValue(owner, stream.value, global);
+ }
+
+ pub fn tee(this: *Ref, owner: Owner, global: *jsc.JSGlobalObject, readable_stream_value: ?*[2]jsc.JSValue) bun.JSError!?struct { ReadableStream, ReadableStream } {
const stream = get(this, owner, global) orelse return null;
- return stream.tee(global) catch null;
+
+ const result = try stream.tee(global) orelse return null;
+ if (readable_stream_value) |value| {
+ value.* = .{ result.@"0".value, result.@"1".value };
+ } else {
+ this.set(owner, result.@"0", global);
+ }
+ return result;
}
pub fn has(this: *const Ref, owner: Owner, global: *jsc.JSGlobalObject) bool {
@@ -77,16 +118,16 @@ pub const Ref = union(Type) {
pub fn init(owner: Owner, global: *jsc.JSGlobalObject) Ref {
switch (owner) {
.Response => {
- return .{ .Response = {} };
+ return .Response;
},
.Request => {
- return .{ .Request = {} };
+ return .Request;
},
.strong => {
return .{ .strong = .init(owner.strong, global) };
},
.empty => {
- return .{ .empty = {} };
+ return .empty;
},
}
}
@@ -100,7 +141,7 @@ pub const Ref = union(Type) {
.Request => {},
.empty => {},
}
- this.* = .{ .empty = {} };
+ this.* = .empty;
}
pub fn abort(this: *Ref, owner: Owner, global: *jsc.JSGlobalObject) bool {
@@ -974,3 +1015,5 @@ const Blob = webcore.Blob;
const streams = webcore.streams;
const std = @import("std");
+const Request = jsc.WebCore.Request;
+const Response = jsc.WebCore.Response;
diff --git a/src/bun.js/webcore/Request.zig b/src/bun.js/webcore/Request.zig
index 01b36116c2..8e6279b6d5 100644
--- a/src/bun.js/webcore/Request.zig
+++ b/src/bun.js/webcore/Request.zig
@@ -175,7 +175,8 @@ pub export fn Bun__JSRequest__calculateEstimatedByteSize(this: *Request) void {
pub fn toJS(this: *Request, globalObject: *JSGlobalObject) JSValue {
this.calculateEstimatedByteSize();
- return js.toJSUnchecked(globalObject, this);
+ const value = js.toJSUnchecked(globalObject, this);
+ return value;
}
extern "C" fn Bun__JSRequest__createForBake(globalObject: *jsc.JSGlobalObject, requestPtr: *Request) callconv(jsc.conv) jsc.JSValue;
@@ -522,7 +523,7 @@ const Fields = enum {
url,
};
-pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSValue) bun.JSError!Request {
+pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Request {
var success = false;
const vm = globalThis.bunVM();
const body = try vm.initRequestBodyValue(.{ .Null = {} });
@@ -582,7 +583,7 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
if (value_type == .DOMWrapper) {
if (value.asDirect(Request)) |request| {
if (values_to_try.len == 1) {
- try request.cloneInto(&req, bun.default_allocator, globalThis, fields.contains(.url));
+ try request.cloneInto(&req, bun.default_allocator, globalThis, fields.contains(.url), value, readable_stream_tee);
success = true;
return req;
}
@@ -610,7 +611,7 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
switch (request.body.value) {
.Null, .Empty, .Used => {},
else => {
- req.body.value = try request.body.value.clone(globalThis);
+ req.body.value = try request.body.value.clone(.{ .Request = value }, globalThis, readable_stream_tee);
fields.insert(.body);
},
}
@@ -641,7 +642,11 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
switch (response.body.value) {
.Null, .Empty, .Used => {},
else => {
- req.body.value = try response.body.value.clone(globalThis);
+ req.body.value = try response.body.value.clone(.empty, globalThis, readable_stream_tee);
+ if (readable_stream_tee) |tee_value| {
+ Response.js.gc.body.set(value, globalThis, tee_value[1]);
+ }
+
fields.insert(.body);
},
}
@@ -654,7 +659,7 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
if (!fields.contains(.body)) {
if (try value.fastGet(globalThis, .body)) |body_| {
fields.insert(.body);
- req.body.value = try Body.Value.fromJS(globalThis, body_);
+ req.body.value = try Body.Value.fromJSWithReadableStreamValue(globalThis, body_, if (readable_stream_tee) |tee| &tee[1] else null);
}
if (globalThis.hasException()) return error.JSError;
@@ -775,12 +780,23 @@ pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSV
return req;
}
-pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Request {
+pub fn constructor(
+ globalThis: *jsc.JSGlobalObject,
+ callframe: *jsc.CallFrame,
+ thisValue: JSValue,
+) bun.JSError!*Request {
const arguments_ = callframe.arguments_old(2);
const arguments = arguments_.ptr[0..arguments_.len];
+ var readable_stream_tee: [2]JSValue = .{ .zero, .zero };
- const request = try constructInto(globalThis, arguments);
- return Request.new(request);
+ const request = try constructInto(globalThis, arguments, &readable_stream_tee);
+ const result = Request.new(request);
+
+ if (readable_stream_tee[1] != .zero and result.body.value == .Locked) {
+ js.gc.body.set(thisValue, globalThis, readable_stream_tee[1]);
+ }
+
+ return result;
}
pub fn getBodyValue(
@@ -795,22 +811,16 @@ pub fn doClone(
callframe: *jsc.CallFrame,
) bun.JSError!jsc.JSValue {
const this_value = callframe.this();
- const cloned = try this.clone(bun.default_allocator, globalThis);
-
+ var readable_stream_tee: [2]JSValue = .{ .zero, .zero };
+ const cloned = try this.clone(bun.default_allocator, globalThis, this_value, &readable_stream_tee);
const js_wrapper = cloned.toJS(globalThis);
if (js_wrapper != .zero) {
- if (cloned.body.value == .Locked) {
- if (cloned.body.value.Locked.readable.get(.{ .Request = js_wrapper }, globalThis)) |readable| {
- // If we are teed, then we need to update the cached .body
- // value to point to the new readable stream
- // We must do this on both the original and cloned request
- // but especially the original request since it will have a stale .body value now.
- js.bodySetCached(js_wrapper, globalThis, readable.value);
- const this_js = this.toJS(globalThis);
- if (this.body.value.Locked.readable.get(.{ .Request = this_js }, globalThis)) |other_readable| {
- js.bodySetCached(this_value, globalThis, other_readable.value);
- }
- }
+ if (this.body.value == .Locked and readable_stream_tee[0] != .zero) {
+ js.gc.body.set(js_wrapper, globalThis, readable_stream_tee[0]);
+ }
+
+ if (cloned.body.value == .Locked and readable_stream_tee[1] != .zero) {
+ js.gc.body.set(js_wrapper, globalThis, readable_stream_tee[1]);
}
}
@@ -928,11 +938,13 @@ pub fn cloneInto(
allocator: std.mem.Allocator,
globalThis: *JSGlobalObject,
preserve_url: bool,
+ this_value: jsc.JSValue,
+ readable_stream_tee: ?*[2]jsc.JSValue,
) bun.JSError!void {
_ = allocator;
this.ensureURL() catch {};
const vm = globalThis.bunVM();
- var body_ = try this.body.value.clone(globalThis);
+ var body_ = try this.body.value.clone(.{ .Request = this_value }, globalThis, readable_stream_tee);
errdefer body_.deinit();
const body = try vm.initRequestBodyValue(body_);
const url = if (preserve_url) req.url else this.url.dupeRef();
@@ -953,10 +965,10 @@ pub fn cloneInto(
}
}
-pub fn clone(this: *Request, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) bun.JSError!*Request {
+pub fn clone(this: *Request, allocator: std.mem.Allocator, globalThis: *JSGlobalObject, this_value: jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!*Request {
const req = Request.new(undefined);
errdefer bun.destroy(req);
- try this.cloneInto(req, allocator, globalThis, false);
+ try this.cloneInto(req, allocator, globalThis, false, this_value, readable_stream_tee);
return req;
}
diff --git a/src/bun.js/webcore/Response.zig b/src/bun.js/webcore/Response.zig
index 56db374d97..ecef3ef846 100644
--- a/src/bun.js/webcore/Response.zig
+++ b/src/bun.js/webcore/Response.zig
@@ -298,8 +298,10 @@ pub fn makeMaybePooled(globalObject: *jsc.JSGlobalObject, ptr: *Response) JSValu
pub fn cloneValue(
this: *Response,
globalThis: *JSGlobalObject,
+ this_value: jsc.JSValue,
+ readable_stream_tee: ?*[2]jsc.JSValue,
) bun.JSError!Response {
- var body = try this.body.clone(globalThis);
+ var body = try this.body.clone(.{ .Response = this_value }, globalThis, readable_stream_tee);
errdefer body.deinit(bun.default_allocator);
var init = try this.init.clone(globalThis);
errdefer init.deinit(bun.default_allocator);
@@ -311,8 +313,8 @@ pub fn cloneValue(
};
}
-pub fn clone(this: *Response, globalThis: *JSGlobalObject) bun.JSError!*Response {
- return bun.new(Response, try this.cloneValue(globalThis));
+pub fn clone(this: *Response, globalThis: *JSGlobalObject, this_value: jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!*Response {
+ return bun.new(Response, try this.cloneValue(globalThis, this_value, readable_stream_tee));
}
pub fn getStatus(
@@ -538,7 +540,7 @@ pub fn constructError(
return response.toJS(globalThis);
}
-pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Response {
+pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, thisValue: JSValue) bun.JSError!*Response {
var arguments = callframe.argumentsAsArray(2);
if (!arguments[0].isUndefinedOrNull() and arguments[0].isObject()) {
@@ -566,10 +568,11 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
return s3.throwSignError(sign_err, globalThis);
};
defer result.deinit();
- response.init.headers = try response.getOrCreateHeaders(globalThis);
+ const headers = try response.getOrCreateHeaders(globalThis);
+ errdefer headers.deref();
+ try headers.put(.Location, result.url, globalThis);
response.redirected = true;
- var headers_ref = response.init.headers.?;
- try headers_ref.put(.Location, result.url, globalThis);
+ response.init.headers = headers;
return bun.new(Response, response);
}
}
@@ -595,13 +598,15 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
return error.JSError;
}
+ var readable_stream_value: JSValue = .zero;
+
var body: Body = brk: {
if (arguments[0].isUndefinedOrNull()) {
break :brk Body{
.value = Body.Value{ .Null = {} },
};
}
- break :brk try Body.extract(globalThis, arguments[0]);
+ break :brk try Body.extract(globalThis, arguments[0], &readable_stream_value);
};
errdefer body.deinit(bun.default_allocator);
@@ -609,21 +614,23 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
return error.JSError;
}
+ if (body.value == .Blob and
+ init.headers != null and
+ body.value.Blob.content_type.len > 0 and
+ !init.headers.?.fastHas(.ContentType))
+ {
+ try init.headers.?.put(.ContentType, body.value.Blob.content_type, globalThis);
+ }
+
var response = bun.new(Response, Response{
.body = body,
.init = init,
});
- if (response.body.value == .Blob and
- response.init.headers != null and
- response.body.value.Blob.content_type.len > 0 and
- !response.init.headers.?.fastHas(.ContentType))
- {
- try response.init.headers.?.put(.ContentType, response.body.value.Blob.content_type, globalThis);
- }
-
response.calculateEstimatedByteSize();
-
+ if (thisValue != .zero and readable_stream_value != .zero) {
+ response.body.value.Locked.readable.set(.{ .Response = thisValue }, response.body.value.Locked.global, readable_stream_value);
+ }
return response;
}
diff --git a/src/bun.js/webcore/response.classes.ts b/src/bun.js/webcore/response.classes.ts
index e1900cd059..31fb3c4d18 100644
--- a/src/bun.js/webcore/response.classes.ts
+++ b/src/bun.js/webcore/response.classes.ts
@@ -4,6 +4,7 @@ export default [
define({
name: "Request",
construct: true,
+ constructNeedsThis: true,
finalize: true,
final: false,
klass: {},
@@ -68,6 +69,7 @@ export default [
define({
name: "Response",
construct: true,
+ constructNeedsThis: true,
finalize: true,
final: false,
JSType: "0b11101110",
diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts
index 1215f77d54..45a889ba7e 100644
--- a/src/codegen/generate-classes.ts
+++ b/src/codegen/generate-classes.ts
@@ -1710,7 +1710,7 @@ size_t ${name}::memoryCost(void* ptr) {
size_t ${name}::estimatedSize(JSC::JSCell* cell, JSC::VM& vm) {
auto* thisObject = jsCast<${name}*>(cell);
auto* wrapped = thisObject->wrapped();
- return Base::estimatedSize(cell, vm) + ${name}::memoryCost(wrapped);
+ return Base::estimatedSize(cell, vm) + (wrapped ? ${name}::memoryCost(wrapped) : 0);
}
void ${name}::destroy(JSCell* cell)
diff --git a/src/codegen/generate-jssink.ts b/src/codegen/generate-jssink.ts
index 13d71b6d18..f235591c7e 100644
--- a/src/codegen/generate-jssink.ts
+++ b/src/codegen/generate-jssink.ts
@@ -424,7 +424,8 @@ size_t ${controller}::memoryCost(void* sinkPtr) {
}
size_t ${controller}::estimatedSize(JSCell* cell, JSC::VM& vm) {
- return Base::estimatedSize(cell, vm) + ${controller}::memoryCost(jsCast<${controller}*>(cell)->wrapped());
+ auto* wrapped = jsCast<${controller}*>(cell)->wrapped();
+ return Base::estimatedSize(cell, vm) + (wrapped ? ${controller}::memoryCost(wrapped) : 0);
}
JSC_DECLARE_HOST_FUNCTION(${controller}__close);