You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add use-after-move rule on a generic worklist dataflow engine (M6)
Phase 2+ backlog item #2: a small, Clang-independent forward "may"
dataflow fixed-point engine (sentinel::dataflow::runForwardWorklist),
plus a fourth rule built on it that flags reading a local/parameter
after std::move(...) with no reassignment in between. This is the
first rule needing a real fixed point across CFG blocks -- a
moved-from fact can flow into a loop body from the previous iteration
via a back edge, which lock-order-inversion's single AST walk can't
see. Extracted FunctionCollector out of LockOrderInversionRule so both
CFG-based rules share it.
Self-scanning the codebase (same dogfooding discipline as M4/M5)
caught a real false-positive class before it shipped: a VarDecl's own
declaration wasn't treated as killing a prior moved-from fact tied to
it, so a variable declared fresh inside a loop body looked moved-from
on the next iteration once the fixed point carried the fact around the
back edge. Fixed, with a regression fixture.
Copy file name to clipboardExpand all lines: README.md
+28-5Lines changed: 28 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,16 +7,19 @@ defects with precise source locations.
7
7
8
8
This is the MVP scoped in [`docs/spec.md`](docs/spec.md) -- three rules,
9
9
text/JSON/SARIF output, YAML config with suppression, and one flagship
10
-
cross-file rule with real graph-algorithm teeth -- plus SARIF output from
11
-
the Phase 2+ backlog. See [`docs/architecture.md`](docs/architecture.md)
12
-
for how it's built internally.
10
+
cross-file rule with real graph-algorithm teeth -- plus two items from the
11
+
Phase 2+ backlog: SARIF output and a fourth rule, `use-after-move`, built
12
+
on a generic worklist dataflow engine. See
13
+
[`docs/architecture.md`](docs/architecture.md) for how it's built
14
+
internally.
13
15
14
16
## Rules
15
17
16
18
| Rule | Kind | What it catches |
17
19
|---|---|---|
18
20
|`missing-override`| AST | A virtual method that overrides a base method without the `override` keyword. |
19
21
|`redundant-move-return`| AST |`return std::move(local);`, which suppresses NRVO for no benefit. |
22
+
|`use-after-move`| CFG + worklist dataflow | A local variable or parameter read after `std::move(...)`, with no reassignment in between. |
20
23
|`lock-order-inversion`| CFG + cross-file graph | A cycle in lock acquisition order across the whole project (a potential deadlock), found via Tarjan's SCC over a project-wide lock graph. |
21
24
22
25
`lock-order-inversion` is the flagship: it tracks which locks are held
@@ -28,6 +31,16 @@ be individually lock-order-safe and still form a cycle once merged --
28
31
that's the case the flagship is built to catch (see
29
32
`tests/projects/lock-cycle/`).
30
33
34
+
`use-after-move` is the other CFG-based rule, and the only one that needs
35
+
a real fixed point: it runs a forward "may" dataflow analysis
36
+
(`sentinel::dataflow::runForwardWorklist`, a small generic worklist engine
37
+
reused by any future rule that needs one) over each function's
38
+
`clang::CFG`, tracking which variables are moved-from and not yet
39
+
reassigned. A moved-from fact can flow into a loop body from the
40
+
*previous* iteration via a back edge -- unlike `lock-order-inversion`'s
41
+
single AST walk, this genuinely needs the fixed point to catch that case
42
+
(see `tests/fixtures/use-after-move/loop.cpp`).
43
+
31
44
## Building
32
45
33
46
Requires CMake 3.20+, a C++20 compiler, and LLVM/Clang dev packages
@@ -159,8 +172,18 @@ scaling studies).
159
172
treated as an immediate acquisition.
160
173
-`redundant-move-return` doesn't verify all of a function's return paths
161
174
agree on the same NRVO candidate.
162
-
-`--header-filter` and the main-file-only default only apply to the two
163
-
per-TU AST rules (enforced in `TranslationUnitContext::report()`);
175
+
-`use-after-move` only recognizes a direct `x = ...` assignment as
176
+
reinitializing `x`; a method call that logically resets it
177
+
(`x.clear()`, `x.reset()`) or passing `&x` to an out-parameter is not,
178
+
and can produce a false positive on a use right after. It also doesn't
179
+
specially model short-circuit (`&&`/`||`) or `?:` operators, which
180
+
`clang::CFG` splits across blocks while the underlying AST subtree is
181
+
still shared -- plain statements (the common shape of this bug) are
182
+
unaffected. Diagnostics don't point back to the specific move site,
183
+
since a "may" analysis with multiple incoming paths doesn't always have
184
+
one unambiguous site to blame.
185
+
-`--header-filter` and the main-file-only default only apply to the
186
+
per-TU AST/CFG rules (enforced in `TranslationUnitContext::report()`);
164
187
`lock-order-inversion`'s `finalize()`-time diagnostics don't currently
0 commit comments