Skip to content

fix(wire): hoist read_cstring/parse_error_fields' nested recursive fn to top-level - #2

Merged
Ch4s3 merged 2 commits into
mainfrom
fix/wire-decode-nested-closure-corruption
Jul 29, 2026
Merged

fix(wire): hoist read_cstring/parse_error_fields' nested recursive fn to top-level#2
Ch4s3 merged 2 commits into
mainfrom
fix/wire-decode-nested-closure-corruption

Conversation

@Ch4s3

@Ch4s3 Ch4s3 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

read_cstring and parse_error_fields each defined a local fn go(...) do ... end closing over the buffer b, called immediately as go(pos, ...). On the compiled backend this corrupts the closure's own first parameter — traced with debug prints, go's i read back as a garbage ~4.3 billion value (looks like a raw pointer) on its very first call, where the caller passed pos = 0. The very next Bytes.get(b, i) then panics with "index out of bounds". The interpreter always evaluated these correctly — this is compiled-backend only, same class of miscompile already worked around in conduit's cron_parser.march.

Symptom (found via forgepm's test suite): any multi-field ErrorResponse/NoticeResponse or any C-string field beyond the first (ParameterStatus, RowDescription, CommandComplete, SASL mechanism lists, error-field parsing) intermittently panicked in a compiled Postgres client. 10 of forgepm's DB tests crashed with Bytes.get: index out of bounds, and duplicate-email/username classification silently fell back to a generic "db_error" because the unique-violation message text never survived parse_error_fields intact.

Fix

Hoist both go functions to top-level pfns (read_cstring_go, parse_error_fields_go) taking b as an explicit parameter instead of a closure capture. Purely mechanical, no behavior change.

Test plan

  • forge check — clean
  • Verified against forgepm's full 544-test suite: 10 crashing + 1 misclassified test → 0 failures

Ch4s3 added 2 commits July 28, 2026 17:19
`Inner`/`Left`/`Right` (JoinKind) and `Asc`/`Desc` (SortDir) are declared
by both `Ast` and `DataFrame`, so under current march they are ambiguous
wherever both modules are in scope — 17 errors across build.march and
compile.march that any consumer inherits (found compiling bastion).

Every site here means the SQL AST's own constructors, so all qualify to
`Ast.*`. Same treatment as f11be60 did for the `Expr.*` collisions, and
the doctests in build.march are qualified along with the code they
document so they keep running.

`forge check`, `forge build` and `forge lint --strict` are clean against
march main-a5c3dc70. `forge test` does not yet compile on that toolchain,
for two reasons that are not this change: six `Module Depot.* not found`
resolution failures, and test/eq_impls.march's universal `impl Eq(a)` now
colliding with a built-in implementation (coherence). Both are unchanged
by this commit and want separate fixes.
… to top-level

Both had a local `fn go(...) do ... end` defined and immediately called
inside their outer function, closing over the buffer `b`. On the compiled
backend this corrupts the recursive closure's own first parameter: traced
with debug prints, `go`'s `i` parameter read back as a garbage ~4.3 billion
value (looks like a raw pointer) on its very first call, `go(pos, Nil)`,
where `pos` was 0 — never reaching the intended base case, so the very
next `Bytes.get(b, i)` call panics with "index out of bounds". The
interpreter always evaluated these correctly; this is compiled-backend
only, same class of bug already worked around in conduit's cron_parser.march
(nested recursive closure, not this specific shape).

Symptom: any multi-field ErrorResponse/NoticeResponse (parse_error_fields)
or any C-string field beyond the first (read_cstring, used by
ParameterStatus, RowDescription, CommandComplete, SASL mechanism lists,
and error-field parsing) intermittently panicked in a compiled Postgres
client — reproduced via forgepm's test suite as a segfault-adjacent crash
in `Bytes.get: index out of bounds` on every DB test that triggered a
Postgres error response, and (before this) an incorrect duplicate-email
error classification because the unique-violation error's message text
never survived parse_error_fields intact.

Fix: hoist both `go` functions to top-level `pfn`s (read_cstring_go,
parse_error_fields_go) taking `b` as an explicit parameter instead of a
closure capture. Purely mechanical — no behavior change, verified against
forgepm's full 544-test suite (was 10 failures from this bug + 1 from the
parse_error_fields half specifically, now 0).
@Ch4s3
Ch4s3 merged commit d1ae164 into main Jul 29, 2026
1 check failed
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.

1 participant