Skip to content

Latest commit

 

History

History
220 lines (142 loc) · 10.1 KB

File metadata and controls

220 lines (142 loc) · 10.1 KB

cpp-sentinel — MVP Spec

Trimmed version of cpp-static-analyzer-project-spec.md, scoped for a solo portfolio project. Everything cut here is not abandoned — it's demoted to a "Phase 2" list at the bottom, so the full spec still serves as the long-term roadmap.

Target: a finished, demoable, well-tested tool in roughly 6–10 weeks of part-time work, not a permanently-in-progress framework.


1. Project Summary

Same as full spec: cpp-sentinel, C++20, Clang LibTooling + AST Matchers + CFG APIs. Command-line analyzer that loads a compilation database and reports defects with source locations.

What changes for the MVP: single-threaded, no cache, two output formats instead of three, three rules instead of seven-plus.


2. MVP Goals

  1. Analyze real C++17/20 projects via compile_commands.json.
  2. Ship three rules, including one that requires genuine cross-file/graph reasoning.
  3. Precise diagnostics: file, line, column, message, one note, one remediation hint.
  4. Text and JSON output.
  5. A real test suite (unit + fixture + one project-level test) — not an afterthought.
  6. One honest benchmark number (throughput on a real repo) — not a full benchmarking program.

Non-Goals (MVP)

  • Parallel or incremental analysis
  • SARIF, HTML, JUnit output
  • Automatic fixes
  • Fuzzing, mutation testing, differential testing, property-based testing
  • Baseline comparison, CI sanitizer matrix, multi-LLVM-version CI
  • A generic pluggable data-flow engine (build the one thing you need, not the framework)

3. MVP Rule Set

Three rules: two cheap, reliable AST rules to prove the pipeline works, plus one flagship that's actually hard and differentiates the project.

3.1 missing-override (AST)

Virtual function that overrides a base method but lacks the override keyword. Pure AST Matchers, no CFG. Low risk, good first rule to validate the whole pipeline end to end.

3.2 redundant-move-return (AST)

return std::move(local); where NRVO would already apply and the move suppresses it. Also pure AST Matchers.

3.3 lock-order-inversion (flagship — project-level, CFG + graph)

Build a lock-order graph across translation units and detect cycles (potential deadlock) via SCC.

Why this is the flagship instead of use-after-move: it doesn't require a general fixed-point data-flow lattice with branch/loop merging to be trustworthy — the core logic (walk each function, track locks currently held, emit an edge when a new lock is acquired while others are held) is comparably simple to implement correctly, while the result (cross-file deadlock detection with a real graph algorithm) is the most distinctive, resume-worthy piece of the project. It exercises CFG traversal, a project-wide fact store, and Tarjan's SCC — enough breadth to demonstrate "control-flow and cross-translation-unit analysis" without needing the hardest part of the original spec (a general worklist dataflow engine with alias-safe merging).

Required analysis (trimmed from full spec 15.1–15.2):

  • Detect explicit (lock()/unlock()) and RAII (lock_guard, unique_lock) acquisition.
  • Track locks held per function via straight-line + simple branch walk (skip full fixed-point iteration for loops in MVP — flag loop-carried lock state as a known limitation).
  • Emit an edge A -> B when lock B is acquired while A is held.
  • Serialize edges per translation unit to a simple JSON fact file; merge after all TUs are processed.
  • Run Tarjan's SCC over the merged graph; any SCC with more than one node is a candidate cycle.
  • Reconstruct and report the shortest explanatory cycle with acquisition source locations.

Cut from full spec for MVP: Graphviz --emit-lock-graph visualization, suppression-by-config for grouped locks (keep inline suppression only).


4. Architecture (simplified)

compile_commands.json
        |
Compilation Database Loader
        |
Clang Tooling Driver  (sequential, one TU at a time)
        |
   +----+----+
   |         |
AST Rules   CFG walk (lock rule)
   |         |
   +----+----+
        |
 Lock Fact Store (JSON, in-memory + optional dump)
        |
 Project Analysis (SCC after all TUs processed)
        |
 Diagnostic Pipeline
        |
   +----+----+
 Terminal      JSON

No parallel scheduler, no cache layer, no SARIF branch.


5. Components to build

Keep from the full spec largely as-is — these are small and worth doing right the first time:

  • CompilationDatabaseLoader (7.1)
  • Rule interface (7.2) — you can drop analysis_kind enum values you're not using yet, but keep the interface generic so Phase 2 rules slot in without a rewrite.
  • TranslationUnitContext (7.3)
  • Diagnostic / SourceLocation / FixEdit model (7.4) — build the full struct even though fix stays unused in MVP; it costs nothing now and avoids a breaking change later.
  • A minimal FactStore holding only LockOrderFact (7.5) — skip the variant<...> with four fact types; add the others when you add the rules that need them.

Skip for MVP: incremental cache keys, parallel worker pool, HTML/JUnit backends.


6. CLI

cpp-sentinel analyze \
    --compile-commands build/compile_commands.json \
    --format json \
    --output reports/results.json

Commands: analyze, list-rules, explain <rule-id>.

Options: --compile-commands, --enable, --disable, --format <text|json>, --output, --include, --exclude, --fail-on.

Cut for MVP: --jobs, --baseline, --changed-files-only, benchmark/baseline subcommands, config validate.

Exit codes: keep the same 0–4 scheme from the full spec (§8) — it's cheap and makes CI integration trivial later.


7. Configuration

Keep YAML config and the two simplest suppression mechanisms:

  • Inline: // cpp-sentinel-ignore <rule>
  • Region: // cpp-sentinel-disable <rule> / // cpp-sentinel-enable <rule>

Cut: config-file path-based suppressions and suppression-debt reporting (§10 last paragraph) — nice governance feature, not needed to prove the concept.


8. Output

Text and JSON only. Structure JSON output close enough to what SARIF would need (stable fingerprints, source ranges, severity, rule id) that adding a SARIF backend later is a serialization change, not a redesign.


9. Testing Plan (MVP)

This is the one area not to cut aggressively — a static analyzer with no visible test discipline undercuts the whole portfolio pitch. Keep:

  • Unit tests: config parsing, path filtering, diagnostic formatting, fingerprint generation, graph construction, SCC detection.
  • Rule fixture tests: for all three rules — one positive, one negative, one boundary case minimum. Full spec's template/macro/suppression fixture variants are good stretch additions, not MVP-blocking.
  • One project-level test: a miniature multi-file fixture (tests/projects/lock-cycle/) with two translation units that only produce a cycle when merged — this is the test that proves the flagship feature actually works cross-file.
  • One golden JSON test: lock in output schema/shape so you notice regressions.

Cut for MVP: differential testing against clang-tidy/CSA, property-based tests, libFuzzer harnesses, mutation testing with a numeric detection target, sanitizer-clean CI requirement (still fine to build with ASan locally, just don't gate CI on it yet).

Coverage target: aim for reasonable coverage on the lock-graph/SCC code specifically (it's the part a reviewer will actually poke at), rather than committing to a blanket 80/90% line/branch number.


10. Benchmarking (MVP)

Skip the full benchmarking program (§20 in the full spec). Do one thing:

  1. Run cpp-sentinel against one real, mid-sized open-source C++ project.
  2. Report wall-clock time, LOC/s, and peak memory for that single run.
  3. Put the numbers and the exact commit hash of the target repo in the README.

That's enough to back up a performance claim honestly without building a corpus generator, scaling study, or regression-gate infrastructure.


11. Continuous Integration (MVP)

One GitHub Actions job: Linux Release build, run the full test suite, run clang-format --dry-run. That's it.

Cut: Debug/ASan/UBSan matrix, multiple LLVM versions, fuzz smoke tests, benchmark jobs.


12. Milestones

M1 — Skeleton (week 1–2): CMake + Clang Tooling integration, compilation database loader, rule registration, text output, unit-test scaffolding. Success: analyze a small CMake project and emit one correctly-located diagnostic.

M2 — AST rules (week 2–3): missing-override, redundant-move-return, JSON output, config, inline/region suppression, fixtures.

M3 — Flagship (week 4–7): Lock fact collection, CFG walk, lock-order graph, Tarjan's SCC, cycle reconstruction, multi-file project test. This is the bulk of the effort — budget the most time here.

M4 — Polish and release (week 7–9): Golden output test, one real-repo benchmark run, CI job, README with actual measured numbers, architecture doc, short demo recording, v0.1.0 tag.


13. Demonstration Scenario (MVP)

  1. Build a small sample multithreaded project with an intentional lock-order bug spanning two files.
  2. Run cpp-sentinel analyze.
  3. Show it catching the missing-override and redundant-move cases too.
  4. Show the cross-file lock cycle diagnostic with both acquisition sites.
  5. Show the JSON output.

That's a complete, honest 5-minute demo.


14. Phase 2+ (deferred, not deleted)

Once the MVP is finished, polished, and has a real README with real numbers, pull from the full spec in roughly this order:

  1. SARIF output + GitHub code-scanning integration
  2. use-after-move + the generic worklist data-flow engine (§14) — now justified because you have a second rule that needs it
  3. unchecked-null-result, unbalanced-lock, expensive-copy
  4. Parallel translation-unit analysis
  5. Incremental caching
  6. Baseline comparison
  7. Fuzzing, mutation testing, differential testing, property-based tests
  8. Full benchmark suite (synthetic corpus, cold/warm, scaling, memory)
  9. CI sanitizer/multi-LLVM matrix

Treat each of these as its own mini-milestone with its own "ship it and write it up" endpoint, rather than building them all before showing anyone the project.