Skip to content

fix: make buffer growth overflow-safe - #3944

Merged
peter-jerry-ye merged 3 commits into
mainfrom
codex/fix-growth-overflow
Jul 28, 2026
Merged

fix: make buffer growth overflow-safe#3944
peter-jerry-ye merged 3 commits into
mainfrom
codex/fix-growth-overflow

Conversation

@peter-jerry-ye

@peter-jerry-ye peter-jerry-ye commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace every grow_if_necessary call in Buffer and the non-JS StringBuilder implementation with an inline capacity fast path and a private slow growth path
  • reject wrapped required sizes with the required >= len invariant, without imposing a backend-specific maximum
  • fall back to the exact requested capacity when geometric doubling overflows
  • add allocation-free boundary tests for normal doubling, doubling overflow, and wrapped required sizes

Why

The old doubling loops could overflow before reaching a representable requested size. Depending on the backend, that could loop indefinitely or pass an invalid capacity to allocation. The common append path also paid for a function call whose capacity check could be kept directly at the write site.

Correctness

Append sizes are non-negative, so a computed required smaller than the current logical length proves that the size calculation wrapped. Those cases enter the slow path and panic before allocation. For a representable required, growth doubles while the next capacity remains representable; if doubling wraps, it uses required exactly and leaves backend allocation limits to the runtime.

The boundary tests exercise the capacity calculation without attempting huge allocations. No public API changes.

This supersedes #3317, #3822, and #3837.

Performance

Initial native release comparison against main, before the final fixed-size fast-path refinement:

Case Before After
Buffer::write_byte, preallocated, n=1,000,000 2.26 ms 690.72 µs
Buffer::write_uint64_le, preallocated, n=125,000 550.15 µs 482.96 µs
Buffer::write_leb128, preallocated, n=100,000 292.53 µs 284.22 µs
StringBuilder::write_string, n=4,096 19.50 µs 19.45 µs
StringBuilder::write_view, n=4,096 29.95 µs 27.49 µs
StringBuilder::write_stringview, n=4,096 29.97 µs 27.51 µs
StringBuilder::write_char ASCII, n=4,096 8.61 µs 2.99 µs
StringBuilder::write_char non-BMP, n=4,096 12.95 µs 10.27 µs
StringBuilder::reset with capacity reuse, n=4,096 112.86 µs 107.74 µs

The final revision uses the invariant 0 <= len <= capacity to compare fixed-size appends against remaining capacity without evaluating len + N on the common path. Compared with the preceding PR revision, using the same runtime for each pair:

Case Native Wasm Wasm-GC JS
StringBuilder::write_char ASCII, n=4,096 4.4% faster 16.7% faster 12.9% faster unchanged implementation
Buffer::write_byte, preallocated, n=65,536 ~3% faster 12.0% faster 10.1% faster ~3% faster
Buffer::write_uint64_le, preallocated, n=8,192 7.3% faster 3.2% faster 4.0% faster neutral within noise

An earlier JS comparison accidentally mixed runtimes; those numbers were discarded. The generated JavaScript for the StringBuilder case was byte-identical.

The full PR measured about 3.2% higher throughput than main in the moonbitlang/async HTTP server benchmark. The incremental effect of the final refinement could not be isolated reliably because the unchanged baseline varied from 54k to 86k requests/second under system load, so no additional HTTP percentage is claimed.

@coveralls

coveralls commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 5639

Coverage decreased (-0.03%) to 90.434%

Details

  • Coverage decreased (-0.03%) from the base build.
  • Patch coverage: 12 uncovered changes across 1 file (71 of 83 lines covered, 85.54%).
  • 2 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
buffer/buffer.mbt 56 44 78.57%
Total (4 files) 83 71 85.54%

Coverage Regressions

2 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
buffer/buffer.mbt 2 88.89%

Coverage Stats

Coverage Status
Relevant Lines: 17457
Covered Lines: 15787
Line Coverage: 90.43%
Coverage Strength: 173609.91 hits per line

💛 - Coveralls

@peter-jerry-ye
peter-jerry-ye marked this pull request as ready for review July 28, 2026 07:11
@peter-jerry-ye
peter-jerry-ye force-pushed the codex/fix-growth-overflow branch from 7e10027 to af2d7d8 Compare July 28, 2026 07:59
@peter-jerry-ye
peter-jerry-ye enabled auto-merge (rebase) July 28, 2026 07:59
@peter-jerry-ye
peter-jerry-ye merged commit f526ab2 into main Jul 28, 2026
15 checks passed
@peter-jerry-ye
peter-jerry-ye deleted the codex/fix-growth-overflow branch July 28, 2026 08:06
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.

2 participants