From a03711b1e3921b2e088e4ee05277497e6143355c Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Thu, 9 Jul 2026 10:21:06 +0100 Subject: [PATCH] MappedFile: fix pathological case causing extreme file sizes The old logic here meant that if you had a parent node with a large number of small child nodes being added to it, the parent node would grow in small steps instead of exponentially. If there was insufficient space for the parent node to grow, it would jump over its siblings, leaving a vacant space. If the siblings were also hitting this case, the nodes would end up "leapfrogging" over one another constantly, never re-using the vacant region before them (due to the node allocation logic in `MappedFile` currently being quite simplistic). In these conditions, because the nodes were adding wasted space every time they wanted to grow just *slightly*, the file size could get truly ridiculous---at worst, binaries which should be on the order of a few hundred megabytes could potentially reach the order of 100 *gigabytes*. The `MappedFile.growth_factor` constant solves exactly this class of problem by using exponential growth to bound the number of wasted bytes in the file, so the fix is simply to actually use it when expanding a parent node to make space for a new child. --- src/link/MappedFile.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index a6000e0e620e6d3f97214732c1e3b869d837b145..60d5a4315214fcaa68c022bdab96bb10c1b3dc32 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -572,7 +572,7 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { .none => { _, const parent_size = opts.parent.location(mf).resolve(mf); if (new_end > parent_size) - try opts.parent.resize(mf, gpa, new_end); + try opts.parent.resize(mf, gpa, new_end +| new_end / growth_factor); }, else => |next_ni| { const next_offset, _ = next_ni.location(mf).resolve(mf); -- 2.54.0