Skip to content

Commit 4a01a4d

Browse files
committed
Rust: Remove nonsensical no-match CFG edges
1 parent 52515dd commit 4a01a4d

File tree

10 files changed

+256
-242
lines changed

10 files changed

+256
-242
lines changed

rust/ql/.generated.list

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/ql/.gitattributes

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -117,51 +117,63 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
117117
override string toString() { result = "boolean(" + value + ")" }
118118
}
119119

120-
/** Holds if `pat` is guaranteed to match at the point in the AST where it occurs. */
121-
pragma[nomagic]
122-
private predicate isExhaustiveMatch(Pat pat) {
123-
(
124-
pat instanceof WildcardPat
125-
or
126-
pat = any(IdentPat ip | not ip.hasPat() and ip = any(Variable v).getPat())
127-
or
128-
pat instanceof RestPat
129-
or
130-
// `let` statements without an `else` branch must be exhaustive
131-
pat = any(LetStmt let | not let.hasLetElse()).getPat()
132-
or
133-
// `match` expressions must be exhaustive, so last arm cannot fail
134-
pat = any(MatchExpr me).getLastArm().getPat()
135-
or
136-
// macro invocations are exhaustive if their expansion is
137-
pat = any(MacroPat mp | isExhaustiveMatch(mp.getMacroCall().getExpanded()))
138-
or
139-
// parameter patterns must be exhaustive
140-
pat = any(Param p).getPat()
141-
) and
142-
not pat = any(ForExpr for).getPat() // workaround until `for` loops are desugared
120+
/**
121+
* Holds if `pat` can not _itself_ be the cause of a pattern match failure. This
122+
* does not mean that `pat` is irrefutable, as its children might be the cause
123+
* of a failure.
124+
*/
125+
private predicate canCauseMatchFailure(Pat pat) {
126+
pat instanceof LiteralPat
143127
or
144-
exists(Pat parent | isExhaustiveMatch(parent) |
145-
pat = parent.(BoxPat).getPat()
146-
or
147-
pat = parent.(IdentPat).getPat()
148-
or
149-
pat = parent.(MacroPat).getMacroCall().getExpanded()
150-
or
151-
pat = parent.(ParenPat).getPat()
152-
or
153-
pat = parent.(RecordPat).getRecordPatFieldList().getField(_).getPat()
154-
or
155-
pat = parent.(RefPat).getPat()
156-
or
157-
pat = parent.(TuplePat).getAField()
128+
// NOTE: a `TupleStructPat` can cause a failure if it resolves to a an enum
129+
// variant but not when it resolves to a tuple struct.
130+
pat instanceof TupleStructPat
131+
or
132+
pat instanceof SlicePat
133+
or
134+
pat instanceof PathPat
135+
or
136+
pat instanceof OrPat
137+
or
138+
// Identifier patterns that are in fact path patterns can cause failures. For
139+
// instance `None`. Only if a `@ ...` part is present can we be sure that it's
140+
// an actual identifier pattern.
141+
pat = any(IdentPat p | not p.hasPat())
142+
}
143+
144+
/**
145+
* Holds if `pat` is guaranteed to match at the point in the AST where it occurs
146+
* due to Rust's exhaustiveness checks.
147+
*/
148+
private predicate guaranteedMatchPosition(Pat pat) {
149+
// `let` statements without an `else` branch must match
150+
pat = any(LetStmt let | not let.hasLetElse()).getPat()
151+
or
152+
// `match` expressions must be exhaustive, so last arm must match
153+
pat = any(MatchExpr me).getLastArm().getPat()
154+
or
155+
// parameter patterns must match
156+
pat = any(Param p).getPat()
157+
or
158+
exists(Pat parent | guaranteedMatchPosition(parent) |
159+
// propagate to all children except for or patterns
160+
parent = pat.getParentPat() and not parent instanceof OrPat
158161
or
159-
pat = parent.(TupleStructPat).getAField()
162+
// for or patterns only the last child inherits the property
163+
parent.(OrPat).getLastPat() = pat
160164
or
161-
pat = parent.(OrPat).getLastPat()
165+
// for macro patterns we propagate to the expanded pattern
166+
parent.(MacroPat).getMacroCall().getExpanded() = pat
162167
)
163168
}
164169

170+
private predicate guaranteedMatch(Pat pat) {
171+
(not canCauseMatchFailure(pat) or guaranteedMatchPosition(pat)) and
172+
// In `for` loops we use a no-match edge from the pattern to terminate the
173+
// loop, hence we special case and always allow the no-match edge.
174+
not pat = any(ForExpr for).getPat()
175+
}
176+
165177
/**
166178
* A completion that represents the result of a pattern match.
167179
*/
@@ -170,7 +182,7 @@ class MatchCompletion extends TMatchCompletion, ConditionalCompletion {
170182

171183
override predicate isValidForSpecific(AstNode e) {
172184
e instanceof Pat and
173-
if isExhaustiveMatch(e) then value = true else any()
185+
if guaranteedMatch(e) then value = true else any()
174186
or
175187
e instanceof TryExpr and value = true
176188
}

rust/ql/lib/codeql/rust/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ module PatternTrees {
641641
super.last(node, c)
642642
or
643643
c.(MatchCompletion).failed() and
644-
completionIsValidFor(c, this) and
644+
completionIsValidFor(c, node) and
645645
(node = this or last(this.getPatRanked(_), node, c))
646646
}
647647
}
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1-
// generated by codegen, remove this comment if you wish to edit this file
21
/**
32
* This module provides a hand-modifiable wrapper around the generated class `Pat`.
43
*
54
* INTERNAL: Do not use.
65
*/
76

87
private import codeql.rust.elements.internal.generated.Pat
8+
private import codeql.rust.elements.internal.generated.ParentChild
99

1010
/**
1111
* INTERNAL: This module contains the customizable definition of `Pat` and should not
1212
* be referenced directly.
1313
*/
1414
module Impl {
15+
// the following QLdoc is generated: if you need to edit it, do it in the schema file
1516
/**
1617
* The base class for patterns.
1718
*/
18-
class Pat extends Generated::Pat { }
19+
class Pat extends Generated::Pat {
20+
/**
21+
* If this pattern is immediately nested within another pattern, then get the
22+
* parent pattern.
23+
*/
24+
Pat getParentPat() {
25+
result = getImmediateParent(this)
26+
or
27+
result.(RecordPat).getRecordPatFieldList().getAField().getPat() = this
28+
}
29+
}
1930
}

rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,9 @@ module Impl {
3636
ClosureBodyScope() { this = any(ClosureExpr ce).getBody() }
3737
}
3838

39-
private Pat getImmediatePatParent(AstNode n) {
40-
result = getImmediateParent(n)
41-
or
42-
result.(RecordPat).getRecordPatFieldList().getAField().getPat() = n
43-
}
44-
4539
private Pat getAPatAncestor(Pat p) {
4640
(p instanceof IdentPat or p instanceof OrPat) and
47-
exists(Pat p0 | result = getImmediatePatParent(p0) |
41+
exists(Pat p0 | result = p0.getParentPat() |
4842
p0 = p
4943
or
5044
p0 = getAPatAncestor(p) and
@@ -222,7 +216,7 @@ module Impl {
222216
or
223217
exists(Pat mid |
224218
mid = getAVariablePatAncestor(v) and
225-
result = getImmediatePatParent(mid)
219+
result = mid.getParentPat()
226220
)
227221
}
228222

rust/ql/test/library-tests/controlflow-unstable/Cfg.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ edges
1919
| test.rs:6:17:6:31 | let ... = b | test.rs:6:31:6:31 | b | |
2020
| test.rs:6:21:6:27 | Some(...) | test.rs:6:12:6:31 | [boolean(false)] ... && ... | no-match |
2121
| test.rs:6:21:6:27 | Some(...) | test.rs:6:26:6:26 | d | match |
22+
| test.rs:6:26:6:26 | d | test.rs:6:12:6:31 | [boolean(false)] ... && ... | no-match |
2223
| test.rs:6:26:6:26 | d | test.rs:6:12:6:31 | [boolean(true)] ... && ... | match |
2324
| test.rs:6:26:6:26 | d | test.rs:6:26:6:26 | d | |
2425
| test.rs:6:31:6:31 | b | test.rs:6:21:6:27 | Some(...) | |
@@ -46,6 +47,7 @@ edges
4647
| test.rs:14:12:15:16 | [boolean(false)] ... && ... | test.rs:19:13:19:17 | false | false |
4748
| test.rs:14:12:15:16 | [boolean(true)] ... && ... | test.rs:17:13:17:13 | d | true |
4849
| test.rs:14:17:14:25 | let ... = b | test.rs:14:25:14:25 | b | |
50+
| test.rs:14:21:14:21 | d | test.rs:14:12:14:25 | [boolean(false)] ... && ... | no-match |
4951
| test.rs:14:21:14:21 | d | test.rs:14:12:14:25 | [boolean(true)] ... && ... | match |
5052
| test.rs:14:21:14:21 | d | test.rs:14:21:14:21 | d | |
5153
| test.rs:14:25:14:25 | b | test.rs:14:21:14:21 | d | |

0 commit comments

Comments
 (0)