Skip to content

fix(runtime): Buffer write/indexOf accept an SSO string; parameterised pg queries no longer segfault (#11430) - #11468

Merged
proggeramlug merged 3 commits into
mainfrom
fix-11430-sso-buffer-write
Sep 27, 2026
Merged

proggeramlug merged 3 commits into
mainfrom
fix-11430-sso-buffer-write

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11430

What was wrong

On current main, every parameterised node-postgres query segfaulted on the main thread in js_buffer_write_len. The cause is an interaction between two changes, not the Buffer.write argument handling itself:

node-postgres binds every parameter as writer.addInt32PrefixedString(String(value)) → buffer.write(value, offset, 'utf-8'), so client.query("… $1", [7]) crashed. This is the backtrace from the issue:

#0 js_buffer_write_len
#1 perry_runtime::object::buffer_dispatch::dispatch_buffer_method
…
#6 …pg_protocol_dist_buffer_writer_js__Writer__addInt32PrefixedString$pshape () at buffer-writer.js:107

Package-free reduction: Buffer.alloc(8).write(String(7), 1, "utf-8"). It segfaults on main, and Node prints 1.

The same family had a quieter sibling. indexOf / lastIndexOf / includes with an SSO needle masked it the same way. The value was then not recognised as a string, so the call returned -1 instead of the index (for example, Buffer.from("xx7yy").indexOf(String(7)) returned -1; Node returns 2).

The fix

  • buffer_dispatch.rs: the write and <encoding>Write arms get the string through a new buffer_dispatch_string_ptr, which calls js_get_string_pointer_unified. That function materializes an SSO value onto the heap and returns a heap string's header unchanged.
  • buffer/cmp.rs: the needle resolvers for indexOf, lastIndexOf and includes handle an SSO needle first, through the same materialization and the same per-encoding decode.

Evidence

Arm A is main at 2c90f29 and arm B is this branch. Both were built the same way (cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static, codegen-units 16, PERRY_NO_AUTO_OPTIMIZE=1) on Linux x86_64. The database was a live PostgreSQL 16.15 with pg 8.23.0 compiled from source. The oracle is Node 26.5.1.

20 runs each, compared with Node's stdout and exit code A (main) B (this PR)
new gap test test_gap_11430_buffer_write_short_string SIGSEGV matches Node
pg: SELECT $1::int * 3 after a parameterless query 0/20 (SIGSEGV every run) 20/20
pg probe on the main thread (connect, query, params, rolled-back and committed transactions, error code, Pool with max 3, pool-client transaction), scram-sha-256 user 0/20 20/20
same probe, trust user 0/20 20/20
scripts/turnloop/apps/pg_parity.ts (parameters only via SQL text) 20/20 20/20

The gap test covers:

  • write with and without offset, length and encoding.
  • utf8Write, latin1Write and hexWrite.
  • indexOf and lastIndexOf with and without an encoding, and includes.
  • SSO values from String(n), (-42).toString() and a template string.
  • pg-protocol's Writer.addInt32PrefixedString shape, checked byte for byte.

The same probe with its worker_threads section still fails on this branch. That is #11340, fixed separately by #11444.

Gap subset through run_parity_tests.sh (66 tests matching buffer / string / write / indexOf): A → B shows 63 PASS → PASS and 1 CRASH → PASS (the new test). Two tests each reported NORESULT (a harness stall at host load 50+) in one arm. Both passed when re-run on B. There are no regressions. Each arm ran against its own workspace clone at its own commit.

Checks run

  • RUST_TEST_THREADS=1 cargo test --release -p perry-runtime --lib buffer: 178 passed, 0 failed.
  • RUSTFLAGS=-D warnings cargo check -p perry-runtime --all-targets: clean. cargo fmt --check: clean.
  • SKIP_COMPILE_GATES=1 scripts/run_lint_gates.sh: 90 of 92 script gates pass. The two failures are Public benchmark evidence freshness (red on main) and cargo xwin (not installed on the host). The compile tier was not run. The string-payload ratchet first flagged an open-coded StringHeader offset in the new SSO helper; it now reads through OwnedStringBytes, and the gate passes.

Not run

  • A full gap sweep.
  • macOS.
  • An instruction-count A/B. Only SSO arguments take the new path, and they could not work before.

Noticed, not changed

A few option-object readers in the same area only accept a heap string (the == 0x7FFF checks in buffer_dispatch.rs's key-export format and hasOwnProperty key handling, and u8_codec.rs). An SSO value there is treated as "not a string", which gives a wrong answer rather than a crash. The hasOwnProperty cases in the gap probe already match Node. These are not touched here.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Buffer.write and encoding-specific writes for short strings, preventing crashes in parameterized database queries.
    • Corrected indexOf, lastIndexOf, and includes when searching for short strings.
    • Improved handling of short strings in buffer conversion, byte-length, and fill operations.

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 0cdb9c5b-239e-4c4d-a1da-7e1f27625e7a

📥 Commits

Reviewing files that changed from the base of the PR and between fb8b9fc and 911089c.

📒 Files selected for processing (4)
  • changelog.d/11468-sso-buffer-write.md
  • crates/perry-runtime/src/buffer/cmp.rs
  • crates/perry-runtime/src/object/buffer_dispatch.rs
  • test-files/test_gap_11430_buffer_write_short_string.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Buffer write methods now handle short-string values through a unified string-pointer accessor. Buffer search methods convert short-string needles using the requested encoding. A regression test covers writes, searches, conversion, byte length, fill, and a length-prefixed string writer.

Changes

Short-string Buffer handling

Layer / File(s) Summary
Materialize short strings for writes
crates/perry-runtime/src/object/buffer_dispatch.rs, test-files/test_gap_11430_buffer_write_short_string.ts
Fixed-encoding write methods and Buffer.write use buffer_dispatch_string_ptr to obtain string headers. The regression test includes a length-prefixed string writer.
Convert short-string search needles
crates/perry-runtime/src/buffer/cmp.rs, test-files/test_gap_11430_buffer_write_short_string.ts, changelog.d/11468-sso-buffer-write.md
Search helpers convert short-string needles using the requested encoding. The regression test covers Buffer searches and other operations with short strings. The changelog describes the fixes and regression test.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 91108

The reviewed Buffer changes have no established merge-blocking issue; proceed with normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 91108

The change repairs short-string handling in existing Buffer operations without showing a new external entry point or weakened control. Its effect across all production callers is not fully established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The affected scope is processes that call the existing runtime Buffer methods; the available changes do not establish a new service entry point or a tenant-specific exposure.

Trust Boundaries and Controls

  • observed — The changed write path crosses the inline-string-to-native-pointer boundary through the unified accessor rather than treating inline bytes as an address; the receiving copy remains length-bounded.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the SSO-related Buffer write and index search fixes and the resolved parameterized PostgreSQL segfault. It is concise and directly related to the main changes.
Description check ✅ Passed The description provides detailed context, affected components, linked issue #11430, implementation changes, test evidence, limitations, and known unrelated failures. It does not use the exact templat…
Linked Issues check ✅ Passed The change meets #11430. buffer_dispatch_string_ptr uses js_get_string_pointer_unified before js_buffer_write_len for write and fixed-encoding write methods. This materializes SSO strings inst…
Out of Scope Changes check ✅ Passed The changes stay within the SSO buffer-string defect scope of #11430. The search-method changes address the same invalid SSO representation handling, and the added test and changelog document and veri…
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

pg: parameterised query segfaults in js_buffer_write_len (Writer.addInt32PrefixedString $pshape clone) on current main

1 participant