Skip to content

fix: replace Math.max/min spreads with iterative helpers to avoid stack overflow#50

Merged
Mythie merged 1 commit intomainfrom
fix/stack-overflow-large-iterables
Mar 11, 2026
Merged

fix: replace Math.max/min spreads with iterative helpers to avoid stack overflow#50
Mythie merged 1 commit intomainfrom
fix/stack-overflow-large-iterables

Conversation

@Mythie
Copy link
Contributor

@Mythie Mythie commented Mar 11, 2026

Math.max(...iterable) and Math.min(...iterable) spread into function
arguments, which overflows the call stack on large collections (~200k+).
PDFs with many xref/object-stream entries hit this during PDF.load().

Adds min/max helpers in src/helpers/math.ts that iterate without
spreading, and uses them in all affected call sites.

Closes #48

…ck overflow

Math.max(...iterable) and Math.min(...iterable) spread into function
arguments, which overflows the call stack on large collections (~200k+).
PDFs with many xref/object-stream entries hit this during PDF.load().

Adds min/max helpers in src/helpers/math.ts that iterate without
spreading, and uses them in all affected call sites.

Closes #48
@vercel
Copy link
Contributor

vercel bot commented Mar 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
core Ready Ready Preview, Comment Mar 11, 2026 2:39am

@github-actions
Copy link
Contributor

Benchmark Results

Comparison

Load PDF

Benchmark Mean p99 RME Samples
libpdf 2.29ms 3.20ms ±1.3% 219
pdf-lib 41.69ms 46.86ms ±4.5% 12

Create blank PDF

Benchmark Mean p99 RME Samples
libpdf 61μs 119μs ±1.7% 8215
pdf-lib 456μs 1.77ms ±3.0% 1096

Add 10 pages

Benchmark Mean p99 RME Samples
libpdf 106μs 182μs ±1.2% 4704
pdf-lib 537μs 1.99ms ±2.9% 932

Draw 50 rectangles

Benchmark Mean p99 RME Samples
libpdf 335μs 810μs ±1.7% 1494
pdf-lib 1.62ms 5.69ms ±6.1% 308

Load and save PDF

Benchmark Mean p99 RME Samples
libpdf 2.33ms 4.09ms ±1.9% 215
pdf-lib 86.98ms 95.69ms ±4.5% 10

Load, modify, and save PDF

Benchmark Mean p99 RME Samples
libpdf 39.45ms 42.53ms ±2.0% 13
pdf-lib 85.92ms 90.67ms ±2.4% 10

Extract single page from 100-page PDF

Benchmark Mean p99 RME Samples
libpdf 3.58ms 6.55ms ±2.0% 140
pdf-lib 9.15ms 10.59ms ±1.6% 55

Split 100-page PDF into single-page PDFs

Benchmark Mean p99 RME Samples
libpdf 32.95ms 34.92ms ±1.5% 16
pdf-lib 92.17ms 107.13ms ±12.9% 6

Split 2000-page PDF into single-page PDFs (0.9MB)

Benchmark Mean p99 RME Samples
libpdf 615.63ms 615.63ms ±0.0% 1
pdf-lib 1.68s 1.68s ±0.0% 1

Copy 10 pages between documents

Benchmark Mean p99 RME Samples
libpdf 4.43ms 5.09ms ±1.0% 113
pdf-lib 11.89ms 13.11ms ±1.4% 43

Merge 2 x 100-page PDFs

Benchmark Mean p99 RME Samples
libpdf 14.20ms 20.56ms ±3.0% 36
pdf-lib 53.76ms 54.88ms ±0.7% 10
Copying

Copy pages between documents

Benchmark Mean p99 RME Samples
copy 1 page 1.01ms 2.54ms ±2.8% 497
copy 10 pages from 100-page PDF 4.42ms 5.00ms ±0.8% 114
copy all 100 pages 7.15ms 7.75ms ±0.9% 70

Duplicate pages within same document

Benchmark Mean p99 RME Samples
duplicate page 0 930μs 1.56ms ±1.5% 538
duplicate all pages (double the document) 875μs 1.35ms ±0.9% 572

Merge PDFs

Benchmark Mean p99 RME Samples
merge 2 small PDFs 1.44ms 2.11ms ±1.1% 348
merge 10 small PDFs 7.27ms 9.30ms ±1.1% 69
merge 2 x 100-page PDFs 13.36ms 13.95ms ±0.7% 38
Drawing

benchmarks/drawing.bench.ts

Benchmark Mean p99 RME Samples
draw 100 rectangles 563μs 1.45ms ±2.5% 889
draw 100 circles 1.29ms 2.92ms ±2.9% 389
draw 100 lines 489μs 1.20ms ±1.5% 1023
draw 100 text lines (standard font) 1.57ms 2.39ms ±1.6% 320
create 10 pages with mixed content 1.36ms 2.66ms ±2.3% 369
Forms

benchmarks/forms.bench.ts

Benchmark Mean p99 RME Samples
get form fields 3.34ms 6.93ms ±4.1% 150
fill text fields 11.65ms 15.60ms ±4.3% 43
read field values 2.98ms 5.34ms ±2.0% 168
flatten form 8.51ms 12.54ms ±3.5% 59
Loading

benchmarks/loading.bench.ts

Benchmark Mean p99 RME Samples
load small PDF (888B) 60μs 150μs ±0.8% 8308
load medium PDF (19KB) 91μs 178μs ±0.6% 5517
load form PDF (116KB) 1.34ms 2.40ms ±1.5% 374
load heavy PDF (9.9MB) 2.19ms 3.01ms ±1.1% 228
Saving

benchmarks/saving.bench.ts

Benchmark Mean p99 RME Samples
save unmodified (19KB) 103μs 233μs ±0.9% 4839
save with modifications (19KB) 722μs 1.37ms ±1.3% 693
incremental save (19KB) 160μs 343μs ±1.0% 3125
save heavy PDF (9.9MB) 2.22ms 2.68ms ±0.6% 226
incremental save heavy PDF (9.9MB) 4.79ms 11.59ms ±5.6% 105
Splitting

Extract single page

Benchmark Mean p99 RME Samples
extractPages (1 page from small PDF) 990μs 1.92ms ±2.3% 506
extractPages (1 page from 100-page PDF) 3.57ms 6.11ms ±1.9% 141
extractPages (1 page from 2000-page PDF) 56.67ms 57.80ms ±0.9% 10

Split into single-page PDFs

Benchmark Mean p99 RME Samples
split 100-page PDF (0.1MB) 30.76ms 34.99ms ±2.6% 17
split 2000-page PDF (0.9MB) 596.52ms 596.52ms ±0.0% 1

Batch page extraction

Benchmark Mean p99 RME Samples
extract first 10 pages from 2000-page PDF 58.02ms 60.00ms ±1.2% 9
extract first 100 pages from 2000-page PDF 60.64ms 62.37ms ±1.4% 9
extract every 10th page from 2000-page PDF (200 pages) 64.77ms 66.18ms ±1.6% 8
Environment
  • Runner: Linux (X64)
  • Runtime: Bun 1.3.10

Results are machine-dependent.

@Mythie Mythie merged commit 6db670a into main Mar 11, 2026
6 checks passed
Mythie added a commit that referenced this pull request Mar 11, 2026
Same class of bug fixed in v0.3.0 (PR #50) — spreading thousands of
xref entries as arguments to Math.max() overflows the call stack on
large documents. Missed in pdf-writer.ts (both xref stream and xref
table paths) and pdf-page.ts (drawText line widths).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDF.load() stack overflows on large xref/object-stream sets due to Math.max(...xref.keys()) in ObjectRegistry

1 participant