sql: drop redundant clamps and a zero-fill of a zero-initialized buffer - #34792
Conversation
All four are porting artifacts from the Zig rewrite (#30412) where the original code initialized with 'undefined' or had looser slice contracts. In Rust they are provably dead: - PreparedStatement.rs: [0u8; N] is already zero-initialized, so .fill(0) on a sub-slice is a no-op. NewWriter::write_null_bitmap already omits it. - HandshakeV10.rs: after flooring auth_plugin_data_len to 21, subtracting 8 gives at least 13, so .max(13) is a no-op. Folded into one .max(21). - CommandTag.rs: strings::index_of_char returns an index strictly within the slice (highway asserts haystack[result] == needle), so idx+1 <= len and .min(len) on idx+1 is a no-op. - NewReader.rs (postgres): StackReader::read(count) returns exactly count bytes on Ok (src/sql/shared/StackReader.rs:94), so the length re-check is unreachable. from_be_slice already bounds-checks via [..SIZE].
|
Warning Review limit reached
Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
Comment |
There was a problem hiding this comment.
LGTM — each removed guard is provably dead.
What was reviewed:
HandshakeV10:.max(21)before- 8guarantees>= 13, so the dropped.max(13)was a no-op;u8arithmetic can't underflow here.CommandTag: verifiedstrings::index_of_char→highway::index_of_charreturns an in-bounds index, soidx + 1 <= lenand the.min(len)clamps were unreachable.NewReader::int: confirmed the onlyReaderContext::readimpl isStackReader::read(src/sql/shared/StackReader.rs:94), which returns exactlycountbytes orShortRead;from_be_slicestill slices[..SIZE]so a future short read would panic, not silently truncate.PreparedStatement:[0u8; N]is zero-initialized by definition;.fill(0)was a no-op.
Extended reasoning...
Overview
Removes four dead defensive guards in src/sql/ that are Zig-to-Rust porting artifacts: a .fill(0) on an already-zeroed stack array, a redundant .max(13) clamp after a .max(21) floor, three .min(len) clamps on indices that are already in-bounds, and a length re-check on a slice whose only producer guarantees exact length. Net -8/+4 lines across four files.
Verification of each claim
I traced each removed guard to source:
strings::index_of_char(src/bun_core/string/immutable.rs:1682) delegates tohighway::index_of_char, and the siblingindex_of_char_posdebug_assert!s the returned index is strictly in-bounds. Soidx + 1 <= lenholds and&tag[idx+1..]is always valid (possibly empty).StackReader::read(src/sql/shared/StackReader.rs:94) either errors withSHORT_READor returnsData::Temporaryover&buffer[offset..offset+count]— exactlycountbytes. Grep confirms it's the sole concreteReaderContextimpl for the postgres reader (plus the blanket&mut Cforwarder). Thefrom_be_slicemacro body still does&bytes[..Self::SIZE]beforecopy_from_slice, so a hypothetical undersized slice would panic on the index rather than read garbage.u8::max(21) - 8ranges [13, 247], so both the oldif < 21branch and the.max(13)were equivalent to the new single.max(21).[0u8; N]is zero-initialized by the language; the.fill(0)was a Zigundefined-buffer artifact.
Security risks
None. This is protocol parsing over server-supplied bytes, but every removed check was provably unreachable given the invariants of the callee it guards. No validation of untrusted input is weakened — the HandshakeV10 floor and the StackReader short-read error remain in place.
Level of scrutiny
Low-to-medium. Pure dead-code removal with no observable behavior change on any input; each claim is mechanically verifiable against the callee source. Existing fault-injection tests still exercise these paths.
Other factors
No prior reviewer comments. The PR description is precise and each claim held up on inspection. The "no test can distinguish before/after" justification is accurate for a pure no-op removal.
…erContext::read int<Int>() relies on it; state it on the trait method so future implementors (there are two today, StackReader and the sql_jsc live-socket Reader) do not silently violate it.
|
This PR removes code that is provably unreachable on every input, so there is no test that can distinguish the patched binary from the released one: any assertion that passes here also passes on This is the same class of src-only |
There was a problem hiding this comment.
LGTM — each removed guard is provably dead and the reasoning in the description checks out.
What was reviewed:
highway::index_of_char(src/highway/lib.rs:126) returnsNoneatlenand otherwise an in-bounds index, soidx + 1 <= lenalways holds — the three.min(len)clamps in CommandTag were no-ops.- Both concrete
ReaderContext::readimpls (StackReader.rs:94, PostgresSQLConnection.rs:1743) return exactlycountbytes orShortRead;from_be_slicestill slices[..SIZE]internally so a future non-conforming impl panics rather than misreads. The new trait doc comment records the contract. - HandshakeV10:
.max(21)on au8guarantees the subsequent- 8 >= 13, so the dropped.max(13)was unreachable and no underflow is possible. [0u8; N]is zero-initialized by construction; the siblingNewWriter::write_null_bitmapalready omits the fill.
Extended reasoning...
Overview
Four small deletions of provably-dead defensive code in src/sql/, all identified as Zig→Rust porting artifacts: a .fill(0) on an already-zero-initialized stack array in the MySQL COM_STMT_EXECUTE null-bitmap writer; a redundant .max(13) clamp in MySQL HandshakeV10 decoding (collapsed with the preceding if < 21 into a single .max(21)); three .min(len) clamps on index_of_char results in the Postgres CommandTag parser; and a length re-check in the Postgres NewReaderWrap::int<Int> helper. A doc comment is added to ReaderContext::read stating the exact-count-or-ShortRead contract that int<Int> now relies on directly.
Security risks
None. All removed checks operate on server-provided wire bytes, but each is unreachable given the invariants established immediately before it. The one case where the guard was structural rather than arithmetic (int<Int>'s length re-check) still fails loudly if a future ReaderContext impl violates the newly-documented contract: from_be_slice slices &bytes[..SIZE] before copy_from_slice, so an undersized slice panics on the bounds check rather than reading garbage. No buffer arithmetic on adversarial input was loosened.
Level of scrutiny
Low-to-moderate. This is protocol-parsing code on the client side of MySQL/Postgres connections, so I traced each claim to source rather than taking the description at face value. I confirmed highway::index_of_char returns None when the needle is absent and otherwise a strictly in-bounds index (src/highway/lib.rs:126-140, with a debug_assert! on haystack[result]); confirmed both live ReaderContext::read implementations return exactly count bytes on Ok (src/sql/shared/StackReader.rs:94-103 and src/sql_jsc/postgres/PostgresSQLConnection.rs:1743-1754); confirmed from_be_slice internally re-slices to [..SIZE]; and confirmed the sibling NewWriter::write_null_bitmap (src/sql/mysql/protocol/NewWriter.rs:104) already omits the redundant fill. The HandshakeV10 arithmetic is closed-form on u8 and cannot underflow.
Other factors
The PR follows the review guideline to delete defensive code only when the condition is shown to be unreachable, and it explains the Zig-era origin of each guard. The description notes existing fault-injection tests (sql-mysql-auth-short-nonce.test.ts, postgres-invalid-message-length.test.ts) still pass, and correctly observes that a pure no-behavior-change refactor cannot produce a test that fails on the released binary. The added trait doc comment is the right way to record the contract now that a caller depends on it without a local re-check. No prior reviews on the PR; the bug-hunting system found nothing.
What
Removes four dead guards in
src/sql/that are porting artifacts from the Zig rewrite (#30412). In Zig the original code initialized buffers withundefinedand had different slice-length contracts; in Rust these checks are provably unreachable.No behaviour change.
Why each one is dead
src/sql/mysql/protocol/PreparedStatement.rswrite_null_bitmap:[0u8; MYSQL_MAX_PARAMS]is zero-initialized by the language, so the subsequent.fill(0)on a sub-slice writes zeros over zeros. The siblingNewWriter::write_null_bitmap(src/sql/mysql/protocol/NewWriter.rs:104) already omits the fill.src/sql/mysql/protocol/HandshakeV10.rs:auth_plugin_data_lenis floored to 21 before the subtraction, soauth_plugin_data_len - 8 >= 13and.max(13)is a no-op. Collapsed theifclamp and the later.max(13)into a single.max(21), which also lets the binding losemut.src/sql/postgres/CommandTag.rs:strings::index_of_charwrapshighway::index_of_char, which returnsNonewhen the byte is absent and otherwise an index strictly inside the slice (the implementationdebug_assert!shaystack[result] == needle). Soidx + 1 <= lenand.min(len)onidx + 1is a no-op. Three call sites.src/sql/postgres/protocol/NewReader.rsint<Int>: both concrete postgresReaderContext::readimplementations, the sharedStackReader::read(src/sql/shared/StackReader.rs:94) and the live-socketReader::read(src/sql_jsc/postgres/PostgresSQLConnection.rs:1743), either returnErr(ShortRead)or a slice of exactlycountbytes. Theslice.len() < Int::SIZEre-check is unreachable under every extant impl, andfrom_be_slicealready slices[..SIZE]internally so an undersized input from a future implementation would still fail loudly rather than silently. Added a doc comment on the trait method stating the contract.Verification
cargo check -p bun_sqlandcargo clippy -p bun_sqlare clean. The existing fault-injection protocol tests that exerciseHandshakeV10decoding andNewReaderWrap::intstill pass underbun bd:This is a pure refactor with no observable difference on any input, so there is no test that can fail on the released binary and pass on this branch; the justification is the analysis above plus the existing coverage.