From 4e991a39658f8df34082a5b90bbb96fb5ff5e609 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Wed, 11 Mar 2026 19:08:44 +1100 Subject: [PATCH] perf(writer): replace recursive reachable-ref walk with iterative stack Avoids potential stack overflow on deeply nested object graphs, consistent with the fix in 6db670a. --- src/writer/pdf-writer.ts | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/writer/pdf-writer.ts b/src/writer/pdf-writer.ts index f415efd..3153fb4 100644 --- a/src/writer/pdf-writer.ts +++ b/src/writer/pdf-writer.ts @@ -284,44 +284,45 @@ function collectReachableRefs( encrypt?: PdfRef, ): Set { const visited = new Set(); + const stack: PdfObject[] = [root]; - const walk = (obj: PdfObject | null): void => { - if (obj === null) { - return; - } + if (info) { + stack.push(info); + } + + if (encrypt) { + stack.push(encrypt); + } + + while (stack.length > 0) { + const obj = stack.pop()!; if (obj instanceof PdfRef) { const key = `${obj.objectNumber} ${obj.generation}`; if (visited.has(key)) { - return; + continue; } visited.add(key); const resolved = registry.resolve(obj); - walk(resolved); + if (resolved !== null) { + stack.push(resolved); + } } else if (obj instanceof PdfDict) { // PdfStream extends PdfDict, so this handles both for (const [, value] of obj) { - walk(value); + if (value != null) { + stack.push(value); + } } } else if (obj instanceof PdfArray) { for (const item of obj) { - walk(item); + stack.push(item); } } - }; - - walk(root); - - if (info) { - walk(info); - } - - if (encrypt) { - walk(encrypt); } return visited;