From a7355a57625a4cf4ec7b9b79fc6f91f2a26a25ca Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 2 Jun 2026 13:00:17 +0800 Subject: [PATCH] Less arena-reuse (retain) in Debug Arena reuse/retain can hide UAF issues, often resulting in a crash that is more symptom than cause (far from where the error actually is). Removing this, lets us better utilize the DebugAllocator's UAF-detection. Also, when running WPT tests (-Dwpt_extensions) limit console logging to 100 values (a few tests writer millions of values, which is annoying and just destroys the terminal). --- src/ArenaPool.zig | 12 +++++++++--- src/browser/webapi/Console.zig | 15 ++++++++++++--- src/cdp/domains/page.zig | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/ArenaPool.zig b/src/ArenaPool.zig index 36f7c71d..29d2a0da 100644 --- a/src/ArenaPool.zig +++ b/src/ArenaPool.zig @@ -28,6 +28,9 @@ const ArenaPool = @This(); const IS_DEBUG = builtin.mode == .Debug; +// In Debug, disable pooling to better catch UAF. +const SAFETY = IS_DEBUG == true and builtin.is_test == false; + pub const BucketSize = enum { tiny, small, medium, large }; const Bucket = struct { @@ -187,7 +190,8 @@ pub fn release(self: *ArenaPool, allocator: Allocator) void { } } - if (bucket.free_list_len >= bucket.free_list_max) { + if ((comptime SAFETY) or bucket.free_list_len >= bucket.free_list_max) { + // In Debug, we never pool. It can mask UAF bugs. arena.deinit(); self.entry_pool.destroy(entry); return; @@ -200,12 +204,14 @@ pub fn release(self: *ArenaPool, allocator: Allocator) void { pub fn reset(_: *const ArenaPool, allocator: Allocator, retain: usize) void { const arena: *ArenaAllocator = @ptrCast(@alignCast(allocator.ptr)); - _ = arena.reset(.{ .retain_with_limit = retain }); + // In Debug, free_all, it's less likely to hide things + _ = arena.reset(if (comptime SAFETY) .free_all else .{ .retain_with_limit = retain }); } pub fn resetRetain(_: *const ArenaPool, allocator: Allocator) void { const arena: *ArenaAllocator = @ptrCast(@alignCast(allocator.ptr)); - _ = arena.reset(.retain_capacity); + // In Debug, free_all, it's less likely to hide things + _ = arena.reset(if (comptime SAFETY) .free_all else .retain_capacity); } const testing = std.testing; diff --git a/src/browser/webapi/Console.zig b/src/browser/webapi/Console.zig index eea5e700..3d97325a 100644 --- a/src/browser/webapi/Console.zig +++ b/src/browser/webapi/Console.zig @@ -175,7 +175,7 @@ const ValueWriter = struct { stack: ?[]const u8 = null, pub fn format(self: ValueWriter, writer: *std.io.Writer) !void { - for (self.values, 1..) |value, i| { + for (self.valuesToLog(), 1..) |value, i| { try writer.print("\n arg({d}): {f}", .{ i, value }); } if (self.stack) |s| { @@ -185,7 +185,7 @@ const ValueWriter = struct { pub fn logFmt(self: ValueWriter, _: []const u8, writer: anytype) !void { var buf: [32]u8 = undefined; - for (self.values, 0..) |value, i| { + for (self.valuesToLog(), 0..) |value, i| { const name = try std.fmt.bufPrint(&buf, "param.{d}", .{i}); try writer.write(name, value); } @@ -193,11 +193,20 @@ const ValueWriter = struct { pub fn jsonStringify(self: ValueWriter, writer: *std.json.Stringify) !void { try writer.beginArray(); - for (self.values) |value| { + for (self.valuesToLog()) |value| { try writer.write(value); } return writer.endArray(); } + + fn valuesToLog(self: ValueWriter) []js.Value { + if (lp.build_config.wpt_extensions) { + // A few WPT tests print HUGE arrays, it's at best, annoying when + // running it locally + return self.values[0..@min(self.values.len, 100)]; + } + return self.values; + } }; pub const JsApi = struct { diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 71aaeafb..ec8ad40a 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -535,7 +535,7 @@ pub fn frameCreated(bc: *CDP.BrowserContext, frame: *Frame) !void { const in_commit = bc.session.pendingPage() != null; if (!in_commit) { - _ = bc.cdp.frame_arena.reset(.{ .retain_with_limit = 1024 * 512 }); + bc.cdp.browser.arena_pool.reset(bc.frame_arena, 1024 * 512); } for (bc.isolated_worlds.items) |isolated_world| {