Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion rust/ql/.generated.list

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion rust/ql/.gitattributes

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 46 additions & 40 deletions rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll
Original file line number Diff line number Diff line change
Expand Up @@ -117,51 +117,57 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
override string toString() { result = "boolean(" + value + ")" }
}

/** Holds if `pat` is guaranteed to match at the point in the AST where it occurs. */
pragma[nomagic]
private predicate isExhaustiveMatch(Pat pat) {
(
pat instanceof WildcardPat
or
pat = any(IdentPat ip | not ip.hasPat() and ip = any(Variable v).getPat())
or
pat instanceof RestPat
or
// `let` statements without an `else` branch must be exhaustive
pat = any(LetStmt let | not let.hasLetElse()).getPat()
or
// `match` expressions must be exhaustive, so last arm cannot fail
pat = any(MatchExpr me).getLastArm().getPat()
or
// macro invocations are exhaustive if their expansion is
pat = any(MacroPat mp | isExhaustiveMatch(mp.getMacroCall().getExpanded()))
or
// parameter patterns must be exhaustive
pat = any(Param p).getPat()
) and
not pat = any(ForExpr for).getPat() // workaround until `for` loops are desugared
/**
* Holds if `pat` can not _itself_ be the cause of a pattern match failure. This
* does not mean that `pat` is irrefutable, as its children might be the cause
* of a failure.
Comment on lines +121 to +123
Copy link
Contributor

Choose a reason for hiding this comment

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

This QL doc seems to suggest the dual of what the predicate actually computes? I wonder if it is actually better to implement what the QL doc says, e.g.

private predicate cannotCauseMatchFailure(Pat pat) {
  pat = any(IdentPat p | p.hasPat())
  or
  pat instanceof BoxPat
  or
  pat instanceof RestPat
  or
  pat instanceof MacroPat
  or
  pat instanceof WildcardPat
  or
  pat instanceof RangePat
  or
  pat instanceof RefPat
  or
  pat instanceof SlicePat
  or
  pat instanceof TuplePat
  or
  pat instanceof ConstBlockPat
}

and then replace not canCauseMatchFailure(pat) with cannotCauseMatchFailure(pat) further down.

*/
private predicate cannotCauseMatchFailure(Pat pat) {
pat instanceof RangePat or
// Identifier patterns that are in fact path patterns can cause failures. For
// instance `None`. Only if an `@ ...` part is present can we be sure that
// it's an actual identifier pattern.
pat = any(IdentPat p | p.hasPat()) or
pat instanceof WildcardPat or
pat instanceof RestPat or
pat instanceof RefPat or
pat instanceof TuplePat or
pat instanceof MacroPat
}

/**
* Holds if `pat` is guaranteed to match at the point in the AST where it occurs
* due to Rust's exhaustiveness checks.
*/
private predicate guaranteedMatchPosition(Pat pat) {
// `let` statements without an `else` branch must match
pat = any(LetStmt let | not let.hasLetElse()).getPat()
or
exists(Pat parent | isExhaustiveMatch(parent) |
pat = parent.(BoxPat).getPat()
or
pat = parent.(IdentPat).getPat()
or
pat = parent.(MacroPat).getMacroCall().getExpanded()
or
pat = parent.(ParenPat).getPat()
or
pat = parent.(RecordPat).getRecordPatFieldList().getField(_).getPat()
or
pat = parent.(RefPat).getPat()
or
pat = parent.(TuplePat).getAField()
// `match` expressions must be exhaustive, so last arm must match
pat = any(MatchExpr me).getLastArm().getPat()
or
// parameter patterns must match
pat = any(Param p).getPat()
or
exists(Pat parent | guaranteedMatchPosition(parent) |
// propagate to all children except for or patterns
parent = pat.getParentPat() and not parent instanceof OrPat
or
pat = parent.(TupleStructPat).getAField()
// for or patterns only the last child inherits the property
parent.(OrPat).getLastPat() = pat
or
pat = parent.(OrPat).getLastPat()
// for macro patterns we propagate to the expanded pattern
parent.(MacroPat).getMacroCall().getExpanded() = pat
)
}

private predicate guaranteedMatch(Pat pat) {
(cannotCauseMatchFailure(pat) or guaranteedMatchPosition(pat)) and
// In `for` loops we use a no-match edge from the pattern to terminate the
// loop, hence we special case and always allow the no-match edge.
not pat = any(ForExpr for).getPat()
}

/**
* A completion that represents the result of a pattern match.
*/
Expand All @@ -170,7 +176,7 @@ class MatchCompletion extends TMatchCompletion, ConditionalCompletion {

override predicate isValidForSpecific(AstNode e) {
e instanceof Pat and
if isExhaustiveMatch(e) then value = true else any()
if guaranteedMatch(e) then value = true else any()
or
e instanceof TryExpr and value = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ module PatternTrees {
super.last(node, c)
or
c.(MatchCompletion).failed() and
completionIsValidFor(c, this) and
completionIsValidFor(c, node) and
(node = this or last(this.getPatRanked(_), node, c))
}
}
Expand Down
14 changes: 12 additions & 2 deletions rust/ql/lib/codeql/rust/elements/internal/PatImpl.qll
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
// generated by codegen, remove this comment if you wish to edit this file
/**
* This module provides a hand-modifiable wrapper around the generated class `Pat`.
*
* INTERNAL: Do not use.
*/

private import codeql.rust.elements.internal.generated.Pat
private import codeql.rust.elements.internal.generated.ParentChild

/**
* INTERNAL: This module contains the customizable definition of `Pat` and should not
* be referenced directly.
*/
module Impl {
// the following QLdoc is generated: if you need to edit it, do it in the schema file
/**
* The base class for patterns.
*/
class Pat extends Generated::Pat { }
class Pat extends Generated::Pat {
/**
* Gets the pattern under which this pattern is immediately nested, if any.
*/
Pat getParentPat() {
result = getImmediateParent(this)
or
result.(RecordPat).getRecordPatFieldList().getAField().getPat() = this
}
}
}
10 changes: 2 additions & 8 deletions rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,9 @@ module Impl {
ClosureBodyScope() { this = any(ClosureExpr ce).getBody() }
}

private Pat getImmediatePatParent(AstNode n) {
result = getImmediateParent(n)
or
result.(RecordPat).getRecordPatFieldList().getAField().getPat() = n
}

private Pat getAPatAncestor(Pat p) {
(p instanceof IdentPat or p instanceof OrPat) and
exists(Pat p0 | result = getImmediatePatParent(p0) |
exists(Pat p0 | result = p0.getParentPat() |
p0 = p
or
p0 = getAPatAncestor(p) and
Expand Down Expand Up @@ -222,7 +216,7 @@ module Impl {
or
exists(Pat mid |
mid = getAVariablePatAncestor(v) and
result = getImmediatePatParent(mid)
result = mid.getParentPat()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ edges
| test.rs:6:17:6:31 | let ... = b | test.rs:6:31:6:31 | b | |
| test.rs:6:21:6:27 | Some(...) | test.rs:6:12:6:31 | [boolean(false)] ... && ... | no-match |
| test.rs:6:21:6:27 | Some(...) | test.rs:6:26:6:26 | d | match |
| test.rs:6:26:6:26 | d | test.rs:6:12:6:31 | [boolean(false)] ... && ... | no-match |
| test.rs:6:26:6:26 | d | test.rs:6:12:6:31 | [boolean(true)] ... && ... | match |
| test.rs:6:26:6:26 | d | test.rs:6:26:6:26 | d | |
| test.rs:6:31:6:31 | b | test.rs:6:21:6:27 | Some(...) | |
Expand Down Expand Up @@ -46,6 +47,7 @@ edges
| test.rs:14:12:15:16 | [boolean(false)] ... && ... | test.rs:19:13:19:17 | false | false |
| test.rs:14:12:15:16 | [boolean(true)] ... && ... | test.rs:17:13:17:13 | d | true |
| test.rs:14:17:14:25 | let ... = b | test.rs:14:25:14:25 | b | |
| test.rs:14:21:14:21 | d | test.rs:14:12:14:25 | [boolean(false)] ... && ... | no-match |
| test.rs:14:21:14:21 | d | test.rs:14:12:14:25 | [boolean(true)] ... && ... | match |
| test.rs:14:21:14:21 | d | test.rs:14:21:14:21 | d | |
| test.rs:14:25:14:25 | b | test.rs:14:21:14:21 | d | |
Expand Down
Loading