Skip to content

fix(source): stop fabricating parse positions; --lang is a fallback in corpus walks (#90) - #100

Merged
copyleftdev merged 1 commit into
mainfrom
fix/90-corpus-lang
Jul 30, 2026
Merged

fix(source): stop fabricating parse positions; --lang is a fallback in corpus walks (#90)#100
copyleftdev merged 1 commit into
mainfrom
fix/90-corpus-lang

Conversation

@copyleftdev

Copy link
Copy Markdown
Owner

Two defects from #90 — plus a stack overflow that fixing the first one exposed.

1. Parse positions that cannot exist

vajra-source serialised the CST to a JSON string and re-parsed it to build the Document. When that re-parse hit serde_json's recursion limit, parse_str reported e.column() as byte_offset — an offset into the 374,597-byte intermediate string, attributed to the user's 50,646-byte file:

parse error at byte 202923: recursion limit exceeded at line 1 column 202923

152,277 bytes past EOF, at "line 1 column 202923", in a 968-line file whose longest line is 142 characters. line 1 because the intermediate JSON has no newlines.

vajra-core gains parse_value, which builds a Document from an in-memory value; vajra-source uses it. No round trip, no intermediate offsets, and one fewer full copy of the CST. raw_size_bytes now reports the source length instead of the serialisation length — which is what the field's own doc comment ("size in bytes of the raw input") always claimed.

2. Unbounded recursion, previously masked by the bug

Removing the round trip removed an accidental guard. serde_json's recursion limit was the only thing stopping node_to_json from overflowing the stack:

thread 'a_reported_byte_offset_never_exceeds_the_input' has overflowed its stack
fatal runtime error: stack overflow, aborting

I could not ship a change that turns an error into a crash. node_to_json now carries a depth and stops at MAX_CONVERT_DEPTH, marking the node truncated with its child count rather than emitting something indistinguishable from a genuine leaf.

The cap is 120, not 256. Each CST level becomes two JSON levels (a children array wrapping an object), so a higher cap converts a deep file successfully only for walk to reject the entire document at depth 257 — strictly worse than analysing it with a marked cut. The first value I tried was 256, and the test caught exactly that.

3. --lang overrode every file in a corpus walk

$ vajra fingerprint . --corpus --input-format source --lang javascript   # 42 .js, 6 .py
{"documents_indexed":50, "errors":[{"file":"./benchmarks/agentic/tasks.py", ...}]}   # before
{"documents_indexed":51, "errors":[]}                                                # after

In a directory walk --lang is now a fallback for unrecognised extensions; a recognised extension wins. It still selects the grammar for a single named file, where the user is pointing at one input and the flag is an instruction rather than a default — there is a test for both halves.

Tests

vajra-cli/tests/corpus_lang.rs:

  • an_explicit_lang_does_not_override_a_recognised_extension — zero errors, all three files indexed, and the result is identical to indexing with no --lang at all
  • an_explicit_lang_still_applies_to_a_single_named_file — the same .py fingerprints differently under --lang python and --lang javascript
  • raw_size_bytes_is_the_source_file_size — compared against std::fs::metadata

vajra-source:

  • a_reported_byte_offset_never_exceeds_the_input — the invariant the issue asked for; this is the test that found the stack overflow
  • deep_nesting_is_truncated_visibly_rather_than_overflowing
  • raw_size_bytes_is_the_source_length

Deliberately not changed

--corpus still requires --input-format source to index source files. Making it implicit would silently stop indexing .json, because the walk selects one or the other and never both. The existing error message already names the flag to pass.

Gate

cargo test --workspace clean, cargo clippy --workspace --all-features -- -D warnings clean, cargo fmt --check clean.

Found by dogfooding vajra against a trending repo.

Closes #90

…n corpus walks

Two defects from #90, plus a stack overflow the first fix exposed.

--- Parse positions past EOF

vajra-source serialised the CST to a JSON string and re-parsed it to build the
Document. When that re-parse hit serde_json's recursion limit, parse_str
reported e.column() as byte_offset — an offset into the 374,597-byte
intermediate string, attributed to the 50,646-byte source file:

    parse error at byte 202923: recursion limit exceeded at line 1 column 202923

in a 968-line file whose longest line is 142 characters. An error position that
cannot exist is worse than none: it sends the reader somewhere unverifiable.

vajra-core gains parse_value, building a Document from an in-memory value, and
vajra-source uses it. No round trip, no intermediate offsets, and no wasted
copy of the CST. raw_size_bytes now reports the source length rather than the
serialisation length, which is what the field is documented to mean.

--- Unbounded recursion, previously masked

Removing the round trip removed an accidental guard: serde_json's recursion
limit was the only thing stopping node_to_json from overflowing the stack. A
400-level Python file aborted the test process outright.

node_to_json now carries a depth and stops at MAX_CONVERT_DEPTH, marking the
node `truncated` with its child count rather than emitting something
indistinguishable from a leaf. The cap is 120, not 256: each CST level becomes
two JSON levels, so a higher cap converts a deep file successfully only for
`walk` to reject the whole document at 257 — strictly worse than analysing it
with a marked cut.

--- --lang overrode every file in a corpus walk

A corpus crosses languages, and forcing one grammar onto all of it mis-parsed
the rest: `--corpus --lang javascript` over a tree of 42 .js and 6 .py parsed
the Python as JavaScript, errored, and dropped it. In a directory walk --lang
is now a fallback for unrecognised extensions; a recognised extension wins. It
still selects the grammar for a single named file, where it is an instruction
rather than a default.

On the tree that surfaced this: 1 error and 50 documents -> 0 errors and 51.

Not changed: --corpus still requires --input-format source to index source
files. Making it implicit would silently stop indexing .json, since the walk
selects one or the other, never both.

Closes #90
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@copyleftdev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e0144f0-0da4-4772-a75e-1c3c52a13510

📥 Commits

Reviewing files that changed from the base of the PR and between b6d94ae and c420632.

📒 Files selected for processing (5)
  • vajra-cli/src/main.rs
  • vajra-cli/tests/corpus_lang.rs
  • vajra-core/src/parse.rs
  • vajra-source/src/convert.rs
  • vajra-source/src/lib.rs

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.

@copyleftdev
copyleftdev merged commit f609060 into main Jul 30, 2026
5 checks passed
@copyleftdev
copyleftdev deleted the fix/90-corpus-lang branch July 30, 2026 16:26
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.

fingerprint --corpus: --lang overrides per-file detection, and parse errors report positions past EOF

1 participant