Skip to content

fix(parser): harden Rust cfg(test) exclusion and remove file size cap - #29

Closed
tuannx wants to merge 1 commit into
codex/rust-parserfrom
fix/rust-parser-red-team-findings
Closed

fix(parser): harden Rust cfg(test) exclusion and remove file size cap#29
tuannx wants to merge 1 commit into
codex/rust-parserfrom
fix/rust-parser-red-team-findings

Conversation

@tuannx

@tuannx tuannx commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Red-team review findings against the Rust parser in #18. Three fixes:

1. Comment between #[cfg(test)] and item broke test exclusion

Tree-sitter emits line_comment/block_comment as named children. A comment between the attribute and its item cleared pending_attributes before the mod_item was reached, so #[cfg(test)] mod tests was NOT skipped.

#[cfg(test)]
// unit tests for this module
mod tests { struct Fixture; }  // ← was leaking into graph

Fix: Skip comment nodes without clearing pending_attributes.

2. #[cfg(test)] on non-mod items was silently ignored

is_cfg_test was computed for ALL node types but only checked in the mod_item branch. Functions, structs, and impls annotated with #[cfg(test)] were extracted as production entities.

#[cfg(test)]
fn test_only_helper() {}  // ← was leaking into graph

#[cfg(test)]
struct TestFixture { x: u64 }  // ← was leaking into graph

Fix: Skip ALL items when is_cfg_test is true (early continue before the type dispatch).

3. Removed _MAX_FILE_BYTES (1MB) cap

  • No other parser has a file size cap
  • Silently drops real code (generated protobuf, diesel schemas often exceed 1MB)
  • Never mitigated the recursion crashes it appeared to guard against
  • The per-file except Exception boundary is the actual backstop

Regression tests

  • test_rust_parser_skips_cfg_test_with_comment_between_attribute_and_item
  • test_rust_parser_skips_cfg_test_on_non_mod_items
  • test_rust_parser_handles_large_files_without_cap

Validation

  • 21 Rust parser tests pass
  • Ruff clean
  • No behavioral change for production code (only test-annotated items are now correctly excluded)

Copilot AI review requested due to automatic review settings July 23, 2026 14:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Red-team review findings against the Rust parser:

1. Comment/doc-comment between #[cfg(test)] and its item broke test
   exclusion — tree-sitter emits comments as named children which
   cleared pending_attributes before the mod_item was reached.
   Fix: skip line_comment/block_comment nodes without clearing.

2. #[cfg(test)] on non-mod items (functions, structs, impls) was
   silently ignored — is_cfg_test was computed but only checked for
   mod_item. Fix: skip ALL items annotated with #[cfg(test)].

3. Removed _MAX_FILE_BYTES (1MB) cap — no other parser has it, it
   silently drops real code (generated protobuf/diesel schemas), and
   the per-file except boundary is the actual backstop.

Regression tests cover all three fixes: comment-between-attribute,
cfg(test) on fn/struct/impl, and >1MB file survival.
@tuannx
tuannx force-pushed the fix/rust-parser-red-team-findings branch from 8b7e248 to 9dd08b3 Compare July 23, 2026 14:48
@github-actions

Copy link
Copy Markdown
Contributor

Architecture Drift Report

Algorithm: PKG | Entities: 722 | Components: 7

Drift from Baseline

Metric Baseline Current Delta
Components 7 7 +0
Similarity 0.98
BalancedArchitectureScore 0.68
PrincipleAlignmentScore 0.84
RCI 0.99
TurboMQ 0.24
BasicMQ 0.24
IntraConnectivity 0.00
InterConnectivity 0.01
TwoWayPairRatio 0.00
DependencyHealth 0.99
ComponentBalance 0.22
HubBalance 0.67
BoundaryClarity 1.00
DependencyDistribution 0.57
SmellDiscipline 0.91

Changes

  • 52 entity movement(s) between components

Smells (1)

  • Concern Overload: Tests

Generated by arcade-agent

@lemduc lemduc closed this in #39 Aug 10, 2026
lemduc added a commit that referenced this pull request Aug 10, 2026
)

* feat(parser): add Rust support (roadmap #16b)

Tree-sitter parser for Rust modules, types, traits, functions, methods,
imports, qualified references, trait inheritance/implementations, and Cargo
workspaces. All AST traversal is iterative and each file is extracted
transactionally, so one adversarial file cannot erase healthy siblings.

Two defects found while relanding the original contribution are fixed here.

Linear entity indexing. `add_entity` guarded the per-package entity list with
`if fqn not in package_entities` — a linear scan of a list that grows to tens
of thousands of entries — and the cross-file merge repeated the same test in a
generator expression. A companion `set` per package makes both O(1). On a
5.2 MB generated single-package file this is 70.2s -> 3.0s (23x) with
identical entity (79,500) and edge (63,600) counts. The membership set is
reset alongside the per-file `packages` dict so no state leaks between files.

Complete `#[cfg(test)]` exclusion. Rust unit tests live inline, so path-based
exclusion never sees them, and matching only the literal `cfg(test)` text on
an outer attribute of an item with a body left four shapes leaking. A probe
crate with 2 production structs and 10 test-only entities yielded 10 entities
(8 test-only) before and yields exactly the 2 production structs now:

  * compound predicates — the cfg predicate tree is evaluated rather than
    string-compared, so `cfg(all(test, ...))` and `cfg(any(test, ...))` match
    while `cfg(not(test))` correctly stays production;
  * inner `#![cfg(test)]` on a file or module body, previously invisible
    because only `attribute_item` was inspected;
  * out-of-line `#[cfg(test)] mod helpers;`, whose backing `helpers.rs` was
    later parsed as an independent production file — files are now visited
    in declaring-module-first order so the module path can be excluded;
  * `#[cfg(test)] use ...`, which gave every production entity in the file a
    phantom import of mockall/proptest/rstest and inflated its fan-out.

No input size cap is added. `go.py` and `typescript.py` both keep a 1 MB
`_MAX_FILE_BYTES` for minified and vendored bundles; Rust does not need one
now that the quadratic index is gone.

Co-Authored-By: Tony Nguyen <tuannx87@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(source): honor exclude_tests in parsers, cache keys, and Cargo workspaces

Three wiring gaps kept the Rust parser's inline test exclusion from ever
taking effect through the public tools.

`exclude_tests` never reached a parser. It only drove *path* filtering during
discovery, so `_parse_one` left every parser on its default and inline
`#[cfg(test)]` exclusion was dead on arrival. `get_parser` returns a fresh
instance per call, so setting the flag there cannot leak between calls.
`analyze` now forwards its own `exclude_tests` to `parse` as well.

`cache_key` ignored `exclude_tests`. Inline exclusion changes the graph for an
*identical* file list, so the explicit `files=` path could return a cached
graph of the wrong shape. The flag now takes part in the key, `.rs` joins the
tracked suffixes, and Cargo manifests are hashed for Rust parses — crate names
and module layout come from `Cargo.toml`, which no `.rs` mtime reflects. The
manifest probe matches composite language keys such as `rust|<exclusions>`,
which is what `source.parse` actually passes.

`_detect_source_root` narrowed Cargo workspaces to the root crate's `src`.
On the new three-crate fixture that ingested 1 of 3 files; the `[workspace]`
probe now runs before the generic source-root candidates and returns the
workspace root, ingesting all 3.

Co-Authored-By: Tony Nguyen <tuannx87@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: document Rust support and the parser hardening workflow

Adds Rust to the supported-language lists across the README, ROADMAP, the
reusable analysis workflow, the analyze action, the MCP tool docstrings, and
the self-analysis CLI help.

Ports the contributor process files from the original Rust contribution, with
one claim corrected: `docs/BUG_CATALOG.md` asserted that per-parser input caps
"diverge from the other parsers", but `parsers/go.py` and
`parsers/typescript.py` both define `_MAX_FILE_BYTES = 1_000_000`. The rule now
says what it means — a cap is a legitimate performance tool for input that is
not human-authored, but never a substitute for fixing the underlying
algorithm.

Records two new reusable failure classes: quadratic membership tests on
de-duplicated ordered collections, and annotation-gated test exclusion leaking
through its less common syntactic shapes.

Co-Authored-By: Tony Nguyen <tuannx87@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Tony Nguyen <tuannx87@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lemduc

lemduc commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Merged into the reland, #39 (squash commit d2bb44b) — closing this one.

Everything real from this branch is in: the cfg(test)/comment hunk (a comment
between #[cfg(test)] and its item used to clear pending_attributes), the move
of the is_cfg_test check out of the mod_item branch so it applies to
functions, structs and impls too, and all three tests.

Two notes on the rest:

  • The "remove file size cap" part was already a no-op — no cap existed at
    cbea67f, the base of this branch. The reland keeps Rust cap-free but says so
    honestly: parsers/go.py:20 and parsers/typescript.py:26 both define
    _MAX_FILE_BYTES = 1_000_000, so this is not a divergence from the other
    parsers, and it is only defensible because the reland also fixes an O(n²) in
    add_entity (5.2 MB generated file: 70.2 s → 3.0 s, identical entity and edge
    counts).
  • Four more cfg(test) leak classes were still open after this branch —
    cfg(all(test, …))/cfg(any(test, …)), inner #![cfg(test)], out-of-line
    #[cfg(test)] mod x;, and #[cfg(test)] use … — and are fixed in feat(parser): add Rust support (roadmap #16b) — reland of #18 + #29 #39 with a
    test each. A probe crate with 2 production structs and 10 test-only entities
    went from 10 entities (8 leaked) to exactly 2.

Thanks — the hardening work here carried over intact, with co-author credit on
every commit.

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.

3 participants