Skip to content

fix(code-unwrap): cfg(not(test)) polarity; add missing negative-case tests#80

Merged
brettdavies merged 3 commits into
devfrom
fix/code-unwrap-cfg-not-test-polarity
Jun 4, 2026
Merged

fix(code-unwrap): cfg(not(test)) polarity; add missing negative-case tests#80
brettdavies merged 3 commits into
devfrom
fix/code-unwrap-cfg-not-test-polarity

Conversation

@brettdavies

@brettdavies brettdavies commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

Closes a P0 correctness regression in code-unwrap surfaced by the adversarial review of merged PR #77.

PR #77 (commit 817d6fa) introduced a tree-sitter walk that exempts .unwrap() calls inside #[cfg(test)]-gated items. The cfg-args parser, however, treats not(...) identically to any(...) and all(...): it descends into the inner argument list without polarity tracking and returns true whenever the bare identifier test appears at any depth. The effect was that #[cfg(not(test))] on production-only code silently exempted that code from the audit, a regression confirmed both by reading the parser and by an injected adversarial test that panicked under the buggy logic.

This PR threads a negated: bool parameter through cfg_args_contain_test. The bare-test branch returns true only at even parity (!negated); at odd parity (under a not(...)) the predicate means production code and the walker keeps scanning siblings in case another predicate resolves to test-gated. The recursive call for not(...) flips polarity; any(...) and all(...) preserve it. Double-negation (not(not(test))) correctly restores even parity.

Also corrects the inner-attribute comment block (which claimed sticky propagation across all siblings while the code actually does one-shot reset) and pins the intentional asymmetry with a named test.

Per plan #4, the diff is surgical: only the parser function, the inner-attribute comment, and the test module are touched.

Changelog

Fixed

  • code-unwrap: #[cfg(not(test))] no longer silently exempts production-only code from the audit. The cfg-args parser now tracks polarity through not(...) wrappers; not(test), any(not(test), unix), and all(not(test), feature = "x") correctly classify as production. Polarity-pinning tests cover the double-negation case (not(not(test))), the not(any(test)) shape, cfg_attr(test, …) non-gating, and the inner-attribute one-shot reset asymmetry.

Type of Change

  • fix: Bug fix (non-breaking change which fixes an issue)

Related Issues/Stories

Testing

  • Unit tests added/updated
  • All tests passing

Test Summary:

  • cargo test: 846 passed, 2 ignored (8 suites). 9 new unit tests in src/audits/source/rust/unwrap.rs: cfg_not_test_does_not_exempt_production_unwrap, cfg_any_not_test_unix_does_not_exempt, cfg_all_not_test_feature_does_not_exempt, cfg_any_test_or_not_feature_still_exempts, cfg_attr_test_does_not_exempt, inner_attribute_one_shot_consumes_on_use_not_fn, mod_tests_without_gate_does_not_exempt, cfg_not_not_test_does_exempt, cfg_not_any_test_does_not_exempt.
  • cargo clippy --all-targets -- -Dwarnings: clean.
  • Dogfood (anc audit .): code-unwrap flags one production unwrap at src/audits/behavioral/json_errors.rs:192 (trimmed.chars().next().unwrap()). The unwrap has no cfg gate; it surfaces because PR feat(audits): role-based JSON error envelope validation #79 added it and the audit wasn't re-dogfooded after that merge. Verified identical behavior on pre-fix dev via git stash + rebuild, ruling out a regression from this PR. Owned by plan ci: add workflows and rulesets to main #5 (PR feat(audits): role-based JSON error envelope validation #79 follow-up) or addressable as a one-line fix in a separate PR.
  • Dogfood (anc audit ~/dev/xurl-rs): code-unwrap continues to pass. xurl-rs has zero #[cfg(not(test))] items, so the polarity fix is a no-op against that target.

Files Modified

Modified:

  • src/audits/source/rust/unwrap.rs: thread negated: bool through cfg_args_contain_test; flip polarity on not(...) recursion (preserve on any/all); rewrite the inner-attribute comment block to match one-shot reset semantics and name the pinning test; add 9 negative-case unit tests.

Created:

  • None.

Renamed:

  • None.

Deleted:

  • None.

Breaking Changes

  • No breaking changes

The audit becomes strictly more accurate. Callers that previously relied on #[cfg(not(test))] exempting production code from code-unwrap were doing so accidentally, since that path was an unintended consequence of PR #77's missing polarity tracking.

Deployment Notes

  • No special deployment steps required

Checklist

  • Code follows project conventions and style guidelines
  • Commit messages follow Conventional Commits
  • Self-review of code completed
  • Tests added/updated and passing
  • No new warnings or errors introduced
  • Changes are backward compatible

…tests

PR #77 introduced a tree-sitter walk that exempts .unwrap() calls inside #[cfg(test)]-gated items. The cfg-args parser, however, treats not(...) identically to any(...) and all(...): it descends without polarity tracking and returns true whenever the bare identifier `test` appears at any depth. The effect was that #[cfg(not(test))] on production-only code silently exempted that code from the code-unwrap audit.

The fix threads a `negated: bool` parameter through cfg_args_contain_test. The bare-test branch returns true only at even parity (!negated); at odd parity the predicate means production code and continues scanning sibling predicates. The recursive call for not(...) flips polarity; any(...) / all(...) preserve it.

Also corrects the inner-attribute comment block (which claimed sticky propagation while the code does one-shot reset) and adds 9 negative-case unit tests covering cfg(not(test)), cfg(any(not(test), unix)), cfg(all(not(test), feature = "x")), cfg(any(test, not(feature = "real"))) still-exempts regression, cfg_attr, mod-tests-without-gate, inner-attribute one-shot consumption, cfg(not(not(test))) double-negation, and cfg(not(any(test))) polarity-pin.

Plan: docs/plans/2026-06-03-004-fix-pr77-code-unwrap-cfg-not-test-polarity-plan.md
brettdavies added a commit that referenced this pull request Jun 4, 2026
PR #80 (#80) ships the work
described in plan #4 with green CI. Flip the plan's frontmatter status to
completed; body unchanged.
Wires the polarity-fix parser to a real on-disk project so the full
parse -> walk -> AuditResult -> scorecard pipeline is exercised, not just
the parser in isolation. The fixture covers four cfg shapes in one
src/lib.rs: production code (no gate, must flag), #[cfg(not(test))]
production-only (PR #80 regression guard, must flag), #[cfg(test)] helper
(must exempt), and items inside #[cfg(test)] mod tests (must exempt).

The integration test pins expected evidence to the fixture's current line
numbers (20, 25 flagged; 30, 37 exempt). A header comment in the fixture
tells future maintainers to update the test if items are reordered.

Closes the "no fixture" gap surfaced during plan #4 execution.
Matches the main crate's edition (Cargo.toml:4). The audit parses the
fixture's source via tree-sitter and does not invoke cargo build, so the
edition does not affect audit behavior; this is a consistency cleanup.

The three pre-existing Rust fixtures (broken-rust, perfect-rust,
source-only) remain on edition 2021 — pre-dating main's bump and out of
scope for this PR.
@brettdavies brettdavies merged commit 7ba92a5 into dev Jun 4, 2026
8 checks passed
@brettdavies brettdavies deleted the fix/code-unwrap-cfg-not-test-polarity branch June 4, 2026 00:28
brettdavies added a commit that referenced this pull request Jun 4, 2026
…le (#81)

## Summary

PR #80 review surfaced that the three pre-existing Rust fixtures lag the
main crate by one edition. This PR aligns them and documents the
convention so future fixtures don't drift again.

`tests/fixtures/broken-rust/`, `tests/fixtures/perfect-rust/`, and
`tests/fixtures/source-only/` were on `edition = "2021"`. Main is
`edition = "2024"`. The audits parse fixture sources via tree-sitter and
never invoke `cargo build`, so the drift is harmless today, but a
fixture lagging the main crate is a silent skew that could mask audit
regressions on edition-specific syntax in future.

### Why not Cargo workspace inheritance?

Cargo's `field.workspace = true` requires the sub-crate to be a
workspace member. The fixtures here are intentionally NOT workspace
members. Making them members would have `cargo build` from the root
compile every fixture, and would apply workspace-level lints,
dependencies, and profile overrides to them, changing what the audits
see. Inheritance was investigated and rejected; documenting the
convention in `AGENTS.md` is the practical alternative.

## Type of Change

- [x] `chore`: Maintenance tasks (dependencies, config, etc.)

## Related Issues/Stories

- Origin: surfaced during PR #80 review.
- See `AGENTS.md > Testing > Test fixtures` for the new convention.

## Testing

- [x] Existing tests
- [x] All tests passing

**Test Summary:**

- `cargo test`: 847 passed, 2 ignored (same baseline as PR #80 merge, no
regression).
- `cargo clippy --all-targets -- -Dwarnings`: clean.
- Integration tests touching the three bumped fixtures
(`test_broken_fixture`, `test_perfect_fixture`,
`test_source_only_fixture`, `test_binary_only_fixture`): all 4 pass.

## Files Modified

**Modified:**

- `tests/fixtures/broken-rust/Cargo.toml`: edition 2021 → 2024.
- `tests/fixtures/perfect-rust/Cargo.toml`: edition 2021 → 2024.
- `tests/fixtures/source-only/Cargo.toml`: edition 2021 → 2024.
- `AGENTS.md`: new "Test fixtures" subsection under "Testing"
documenting the lockstep edition rule and the workspace-inheritance
non-option.

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.

## Breaking Changes

- [x] No breaking changes

## Deployment Notes

- [x] No special deployment steps required

## Checklist

- [x] Code follows project conventions and style guidelines
- [x] Commit messages follow Conventional Commits
- [x] Self-review of code completed
- [x] Tests added/updated and passing
- [x] No new warnings or errors introduced
- [x] Changes are backward compatible
brettdavies added a commit that referenced this pull request Jun 4, 2026
…ue (#82)

## Summary

Addresses the `json_errors.rs:192` finding from PR #80's dogfood run:
the `.unwrap()` on `trimmed.chars().next()` inside `is_type_id_value`
was provably safe (an `is_empty()` guard ten lines earlier guarantees at
least one char), but the `code-unwrap` source audit pattern-matches on
`.unwrap()` call expressions and surfaced it during self-dogfood.

Replaces the `.unwrap()` with a `let-else` that returns `false`,
consistent with the function's "return false on any invalid input"
pattern. Adds a comprehensive unit test
(`is_type_id_value_validates_json_shapes`) covering all five non-string
JSON value kinds (Number, Bool, Null, Array, Object), three valid
identifier shapes (kebab-case, SCREAMING_SNAKE, snake_case), and three
invalid string shapes (whitespace, period-separated, leading digit).

## Changelog

### Fixed

- Fix `code-unwrap` self-dogfood finding at `json_errors.rs:192`:
replace `.unwrap()` with a `let-else` returning `false` in
`is_type_id_value`.

## Type of Change

- [x] `fix`: Bug fix (non-breaking change which fixes an issue)

## Related Issues/Stories

- PR #80 (dogfood surfaced `json_errors.rs:192` as the one remaining
`code-unwrap` finding)
- PR #77 (introduced the `code-unwrap` audit)
- PR #79 (introduced the `.unwrap()` that this PR removes)

## Testing

- [x] Unit tests added/updated
- [x] All tests passing

**Test Summary:**

- `cargo test`: 848 passed, 2 ignored (8 suites). +1 new test
`is_type_id_value_validates_json_shapes` in
`src/audits/behavioral/json_errors.rs`.
- `cargo clippy --all-targets -- -Dwarnings`: clean.
- `cargo fmt --check`: clean.

## Files Modified

**Modified:**

- `src/audits/behavioral/json_errors.rs`: replace `.unwrap()` with
`let-else` at line 192; add `is_type_id_value_validates_json_shapes`
unit test.

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.

## Breaking Changes

- [x] No breaking changes

## Deployment Notes

- [x] No special deployment steps required

## Checklist

- [x] Code follows project conventions and style guidelines
- [x] Commit messages follow Conventional Commits
- [x] Self-review of code completed
- [x] Tests added/updated and passing
- [x] No new warnings or errors introduced
- [x] Changes are backward compatible
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