bun:sqlite: bind strict-mode numeric named parameters by their literal key - #37240
bun:sqlite: bind strict-mode numeric named parameters by their literal key#37240robobun wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan includes up to 5 reviews per rolling hour; 0 remain after this review. WalkthroughChangesSQLite parameter binding
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Good catch on the |
|
CI status: 195 of 196 jobs passed. The one red lane (debian 13 x64-asan) fails only on test/js/web/workers/worker-terminate-funnels.test.ts, a leak that is unrelated to this change and reproduces on main; it is being tracked separately. All sqlite tests pass on every lane, so this is ready for review. |
0fff885 to
1eeef8b
Compare
|
Updated 9:33 PM PT - Aug 17th, 2026
✅ @robobun, your commit 77ec31d1f550977eae2968b46b6dc398887cf36a passed in 🧪 To try this PR locally: bunx bun-pr 37240That installs a local version of the PR into your bun-37240 --bun |
|
Pushed 2093ec1 addressing the comment lint: removed the unreachable |
…l key In strict mode, $N / :N / @n names were treated like the ?N positional form: the digit name was parsed and looked up at object key N-1, so $1 bound from key 0 and $0 underflowed to key 4294967295. Named parameters now bind from the key matching the name without its prefix, while ?N keeps the array-like 0-based mapping.
SQLite rejects ?0 at prepare time, so the ?N branch is back to the original code.
2093ec1 to
77ec31d
Compare
|
Rebased onto current main (77ec31d, no source changes). The previous build's binary-size failure was a baseline mismatch: the comparison canary was built from main two commits ahead of this branch's base, and those two commits (#39484, #39486) shrink the binary by roughly the reported delta. This change adds one bool and an inlined parseIndex call. sqlite suite passes on the new base (140 tests). |
There was a problem hiding this comment.
LGTM — the pre-existing strlen(nullptr) I flagged earlier is now guarded on main via #37211, and the comment-cop lint was addressed in 2093ec1.
What was reviewed:
isPositionalis read fromname[0]before the+= 1trim and after the nullptr check, so the dereference is safe and the classification is correct for all four prefix chars.JSC::parseIndex(*str.impl())—stris non-null here (branch is gated onname[0]being a digit), and non-canonical forms (01,4294967295,1abc) correctly fall through to the named-slot lookup.- The
?Nbranch is byte-for-byte the original code; existing?1, ?2 → {0:…, 1:…}tests and the new "?N still binds from 0-based" test cover it. - Default (non-strict) mode is unaffected: the whole block is gated on
trimLeadingPrefix.
Extended reasoning...
Overview
Eight lines changed in rebindObject's hasOutOfOrderNames branch in JSSQLStatement.cpp, plus a 44-line test block in sqlite.test.js. The change captures whether the untrimmed parameter name starts with ? (positional) vs $/:/@ (named), and only applies the N-1 array-index mapping to the positional form. Named numeric parameters now use JSC::parseIndex so canonical numeric keys resolve via getDirectIndex(N) and non-canonical digit strings fall through to the named-property lookup.
Security risks
None. This is a lookup-key selection change inside the strict-mode object-binding path. No untrusted-length arithmetic, no allocation, no new user-JS re-entry (getDirectIndex was already called here). The old $0 → getDirectIndex(-1 as unsigned) wraparound was not memory-unsafe (just a property miss), and the new path cannot underflow.
Level of scrutiny
Medium — native C++ binding code, but a very small localized change to an existing lambda with no new control flow beyond one if/else if. The ?N branch is identical to the original code. name[0] is dereferenced only after the existing name == nullptr guard, and str.impl() is non-null because the branch is gated on the trimmed name starting with a digit. JSC::parseIndex is the correct primitive: it applies the exact canonical-array-index rules JS uses to distinguish index properties from named properties, so getDirectIndex and getOwnNonIndexPropertySlot are chosen consistently with how JSC actually stores the key.
Other factors
All prior feedback is resolved: my earlier pre-existing-segfault note is now fixed on main via #37211 (visible at line 1041 in the current tree), and the comment-cop lint was addressed by trimming to a one-line comment and dropping the unreachable ?0 guard. Seven new tests cover the full variant matrix ($/:/@, out-of-order, $0, non-canonical digits, mixed word/numeric, ?N preservation, default-mode preservation); the author verified 5/7 fail without the fix and the full sqlite suite (140 tests) passes. CI on the previous revision was 195/196 with an unrelated worker-leak flake.
Problem
In
bun:sqlitewithstrict: true, numeric named parameters ($1,:1,@1) are looked up at object keyN-1instead of keyN, and$0underflows to key"4294967295":The documented strict-mode contract is that object keys are the parameter name without its prefix (
$namebinds from{ name }), so$1should bind from key1. When both key0and key1exist, the off-by-one silently binds the wrong value.Cause
rebindObjectinJSSQLStatement.cpptreats every digit-leading trimmed name as SQLite's?NNNpositional form: it parses the digits and readsgetDirectIndex(value - 1), applying 1-based-position-to-0-based-index arithmetic to what is actually an object key. For$0the subtraction wraps the unsigned index. There is no memory-safety impact: the wrapped index degrades to an ordinary missing property.Fix
Distinguish the two forms by the parameter's prefix character:
?NNNis a 1-based position and keeps the array-like 0-based mapping (?1, ?2still binds from{ 0: ..., 1: ... }, matching arrays and the existing tests). This branch is unchanged from before; SQLite rejects?0at prepare time, soN - 1cannot underflow here.$N/:N/@Nare names; they now bind from the literal key viaJSC::parseIndex, which applies canonical array-index semantics. Canonical numeric keys (1,0) are index properties that the named-slot lookup cannot see, so they go throughgetDirectIndex(N); non-canonical digit names ($01,$4294967295,$1abc) fall through to the regular named lookup and bind from their literal key.Arrays, non-numeric names, and default (non-strict) mode are unchanged.
Verification
New tests in
test/js/bun/sqlite/sqlite.test.jscover$N/:N/@Nbinding from keyN, out-of-order numeric names, the$0wraparound, non-canonical digit names, mixed word/numeric names, and that?Nobject binding and default-mode prefixed keys behave as before. 5 of the 7 fail on the previous build; all pass with the fix, and the full sqlite suite (133 tests) passes.no test proof · iteration 3 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/sqlite/sqlite.test.js