Merge pull request #7283 from aibaars/ruby-pattern-matching-cfg

Ruby: pattern matching: CFG
This commit is contained in:
Arthur Baars
2021-12-10 10:24:38 +01:00
committed by GitHub
13 changed files with 1364 additions and 44 deletions

View File

@@ -246,8 +246,6 @@ private module Cached {
explicitAssignmentNode(g, _)
or
casePattern(g)
or
classReferencePattern(g)
)
} or
TScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier i) {
@@ -293,8 +291,6 @@ private module Cached {
explicitAssignmentNode(g, _)
or
casePattern(g)
or
classReferencePattern(g)
} or
TTokenMethodName(MethodName::Token g) { MethodName::range(g) } or
TTokenSuperCall(Ruby::Super g) { vcall(g) } or

View File

@@ -48,13 +48,7 @@ predicate casePattern(Ruby::AstNode node) {
node = any(Ruby::KeywordPattern parent).getValue()
or
node = any(Ruby::ParenthesizedPattern parent).getChild()
}
/**
* Holds if `node` is a class reference used in an
* array, find, or hash pattern.
*/
predicate classReferencePattern(Ruby::AstNode node) {
or
node = any(Ruby::ArrayPattern p).getClass()
or
node = any(Ruby::FindPattern p).getClass()

View File

@@ -6,6 +6,7 @@
private import codeql.ruby.AST
private import codeql.ruby.ast.internal.AST
private import codeql.ruby.ast.internal.Control
private import codeql.ruby.controlflow.ControlFlowGraph
private import ControlFlowGraphImpl
private import NonReturning
@@ -27,6 +28,10 @@ private newtype TCompletion =
outer instanceof NonNestedNormalCompletion and
nestLevel = 0
or
inner instanceof TBooleanCompletion and
outer instanceof TMatchingCompletion and
nestLevel = 0
or
inner instanceof NormalCompletion and
nestedEnsureCompletion(outer, nestLevel)
}
@@ -81,8 +86,9 @@ private predicate mayRaise(Call c) {
/** A completion of a statement or an expression. */
abstract class Completion extends TCompletion {
/** Holds if this completion is valid for node `n`. */
predicate isValidFor(AstNode n) {
private predicate isValidForSpecific(AstNode n) {
exists(AstNode other | n = other.getDesugared() and this.isValidForSpecific(other))
or
this = n.(NonReturningCall).getACompletion()
or
completionIsValidForStmt(n, this)
@@ -98,15 +104,26 @@ abstract class Completion extends TCompletion {
mustHaveMatchingCompletion(n) and
this = TMatchingCompletion(_)
or
n = any(RescueModifierExpr parent).getBody() and this = TRaiseCompletion()
n = any(RescueModifierExpr parent).getBody() and
this = [TSimpleCompletion().(TCompletion), TRaiseCompletion()]
or
mayRaise(n) and
this = TRaiseCompletion()
(
mayRaise(n)
or
n instanceof CaseMatch and not exists(n.(CaseExpr).getElseBranch())
) and
(
this = TRaiseCompletion()
or
this = TSimpleCompletion() and not n instanceof NonReturningCall
)
}
/** Holds if this completion is valid for node `n`. */
predicate isValidFor(AstNode n) {
this.isValidForSpecific(n)
or
not n instanceof NonReturningCall and
not completionIsValidForStmt(n, _) and
not mustHaveBooleanCompletion(n) and
not mustHaveMatchingCompletion(n) and
not any(Completion c).isValidForSpecific(n) and
this = TSimpleCompletion()
}
@@ -172,6 +189,8 @@ private predicate inBooleanContext(AstNode n) {
or
n = any(ConditionalLoop parent).getCondition()
or
n = any(InClause parent).getCondition()
or
exists(LogicalAndExpr parent |
n = parent.getLeftOperand()
or
@@ -218,6 +237,10 @@ private predicate inMatchingContext(AstNode n) {
w.getPattern(_) = n
)
or
n instanceof CasePattern
or
n = any(VariableReferencePattern p).getVariableAccess()
or
n.(Trees::DefaultValueParameterTree).hasDefaultValue()
}
@@ -241,7 +264,7 @@ class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
* the successor. Either a Boolean completion (`BooleanCompletion`), or a matching
* completion (`MatchingCompletion`).
*/
abstract class ConditionalCompletion extends NonNestedNormalCompletion {
abstract class ConditionalCompletion extends NormalCompletion {
boolean value;
bindingset[value]
@@ -255,7 +278,7 @@ abstract class ConditionalCompletion extends NonNestedNormalCompletion {
* A completion that represents evaluation of an expression
* with a Boolean value.
*/
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
class BooleanCompletion extends ConditionalCompletion, NonNestedNormalCompletion, TBooleanCompletion {
BooleanCompletion() { this = TBooleanCompletion(value) }
/** Gets the dual Boolean completion. */
@@ -280,10 +303,16 @@ class FalseCompletion extends BooleanCompletion {
* A completion that represents evaluation of a matching test, for example
* a test in a `rescue` statement.
*/
class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
MatchingCompletion() { this = TMatchingCompletion(value) }
class MatchingCompletion extends ConditionalCompletion {
MatchingCompletion() {
this = TMatchingCompletion(value)
or
this = TNestedCompletion(_, TMatchingCompletion(value), _)
}
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override ConditionalSuccessor getAMatchingSuccessorType() {
this = TMatchingCompletion(result.(MatchingSuccessor).getValue())
}
override string toString() { if value = true then result = "match" else result = "no-match" }
}
@@ -440,7 +469,9 @@ abstract class NestedCompletion extends Completion, TNestedCompletion {
NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) }
/** Gets a completion that is compatible with the inner completion. */
abstract Completion getAnInnerCompatibleCompletion();
Completion getAnInnerCompatibleCompletion() {
result.getOuterCompletion() = this.getInnerCompletion()
}
/** Gets the level of this nested completion. */
final int getNestLevel() { result = nestLevel }
@@ -483,9 +514,39 @@ class NestedEnsureCompletion extends NestedCompletion {
override Completion getOuterCompletion() { result = outer }
override Completion getAnInnerCompatibleCompletion() {
result.getOuterCompletion() = this.getInnerCompletion()
}
override SuccessorType getAMatchingSuccessorType() { none() }
}
/**
* A completion used for conditions in pattern matching:
*
* ```rb
* in x if x == 5 then puts "five"
* in x unless x == 4 then puts "not four"
* ```
*
* The outer (Matching) completion indicates whether there is a match, and
* the inner (Boolean) completion indicates what the condition evaluated
* to.
*
* For the condition `x == 5` above, `TNestedCompletion(true, true, 0)` and
* `TNestedCompletion(false, false, 0)` are both valid completions, while
* `TNestedCompletion(true, false, 0)` and `TNestedCompletion(false, true, 0)`
* are valid completions for `x == 4`.
*/
class NestedMatchingCompletion extends NestedCompletion, MatchingCompletion {
NestedMatchingCompletion() {
inner instanceof TBooleanCompletion and
outer instanceof TMatchingCompletion
}
override BooleanCompletion getInnerCompletion() { result = inner }
override MatchingCompletion getOuterCompletion() { result = outer }
override BooleanSuccessor getAMatchingSuccessorType() {
result.getValue() = this.getInnerCompletion().getValue()
}
override string toString() { result = NestedCompletion.super.toString() }
}

View File

@@ -419,6 +419,387 @@ module Trees {
}
}
private class CaseMatchTree extends PreOrderTree, CaseExpr, ASTInternal::TCaseMatch {
final override predicate propagatesAbnormal(AstNode child) {
child = this.getValue() or child = this.getABranch()
}
final override predicate last(AstNode last, Completion c) {
last(this.getABranch(), last, c) and
not c.(MatchingCompletion).getValue() = false
or
not exists(this.getElseBranch()) and
exists(MatchingCompletion lc, Expr lastBranch |
lastBranch = max(int i | | this.getBranch(i) order by i) and
lc.getValue() = false and
last(lastBranch, last, lc) and
c instanceof RaiseCompletion and
not c instanceof NestedCompletion
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getValue(), succ) and
c instanceof SimpleCompletion
or
last(this.getValue(), pred, c) and
first(this.getBranch(0), succ) and
c instanceof SimpleCompletion
or
exists(int i, Expr branch | branch = this.getBranch(i) |
last(branch, pred, c) and
first(this.getBranch(i + 1), succ) and
c.(MatchingCompletion).getValue() = false
)
}
}
private class PatternVariableAccessTree extends LocalVariableAccessTree, LocalVariableWriteAccess,
CasePattern {
final override predicate last(AstNode last, Completion c) {
super.last(last, c) and
c.(MatchingCompletion).getValue() = true
}
}
private class ArrayPatternTree extends ControlFlowTree, ArrayPattern {
final override predicate propagatesAbnormal(AstNode child) {
child = this.getClass() or
child = this.getPrefixElement(_) or
child = this.getRestVariableAccess() or
child = this.getSuffixElement(_)
}
final override predicate first(AstNode first) {
first(this.getClass(), first)
or
not exists(this.getClass()) and first = this
}
final override predicate last(AstNode last, Completion c) {
c.(MatchingCompletion).getValue() = false and
(
last = this and
c.isValidFor(this)
or
exists(AstNode node |
node = this.getClass() or
node = this.getPrefixElement(_) or
node = this.getSuffixElement(_)
|
last(node, last, c)
)
)
or
c.(MatchingCompletion).getValue() = true and
last = this and
c.isValidFor(this) and
not exists(this.getPrefixElement(_)) and
not exists(this.getRestVariableAccess())
or
c.(MatchingCompletion).getValue() = true and
last(max(int i | | this.getPrefixElement(i) order by i), last, c) and
not exists(this.getRestVariableAccess())
or
last(this.getRestVariableAccess(), last, c) and
not exists(this.getSuffixElement(_))
or
c.(MatchingCompletion).getValue() = true and
last(max(int i | | this.getSuffixElement(i) order by i), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(this.getClass(), pred, c) and
succ = this and
c.(MatchingCompletion).getValue() = true
or
exists(AstNode next |
pred = this and
c.(MatchingCompletion).getValue() = true and
first(next, succ)
|
next = this.getPrefixElement(0)
or
not exists(this.getPrefixElement(_)) and
next = this.getRestVariableAccess()
)
or
last(max(int i | | this.getPrefixElement(i) order by i), pred, c) and
first(this.getRestVariableAccess(), succ) and
c.(MatchingCompletion).getValue() = true
or
exists(int i |
last(this.getPrefixElement(i), pred, c) and
first(this.getPrefixElement(i + 1), succ) and
c.(MatchingCompletion).getValue() = true
)
or
last(this.getRestVariableAccess(), pred, c) and
first(this.getSuffixElement(0), succ) and
c instanceof SimpleCompletion
or
exists(int i |
last(this.getSuffixElement(i), pred, c) and
first(this.getSuffixElement(i + 1), succ) and
c.(MatchingCompletion).getValue() = true
)
}
}
private class FindPatternTree extends ControlFlowTree, FindPattern {
final override predicate propagatesAbnormal(AstNode child) {
child = this.getClass() or
child = this.getPrefixVariableAccess() or
child = this.getElement(_) or
child = this.getSuffixVariableAccess()
}
final override predicate first(AstNode first) {
first(this.getClass(), first)
or
not exists(this.getClass()) and first = this
}
final override predicate last(AstNode last, Completion c) {
last(this.getSuffixVariableAccess(), last, c)
or
last(max(int i | | this.getElement(i) order by i), last, c) and
not exists(this.getSuffixVariableAccess())
or
c.(MatchingCompletion).getValue() = false and
(
last = this and
c.isValidFor(this)
or
exists(AstNode node | node = this.getClass() or node = this.getElement(_) |
last(node, last, c)
)
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(this.getClass(), pred, c) and
succ = this and
c.(MatchingCompletion).getValue() = true
or
exists(AstNode next |
pred = this and
c.(MatchingCompletion).getValue() = true and
first(next, succ)
|
next = this.getPrefixVariableAccess()
or
not exists(this.getPrefixVariableAccess()) and
next = this.getElement(0)
)
or
last(this.getPrefixVariableAccess(), pred, c) and
first(this.getElement(0), succ) and
c instanceof SimpleCompletion
or
c.(MatchingCompletion).getValue() = true and
exists(int i |
last(this.getElement(i), pred, c) and
first(this.getElement(i + 1), succ)
)
or
c.(MatchingCompletion).getValue() = true and
last(max(int i | | this.getElement(i) order by i), pred, c) and
first(this.getSuffixVariableAccess(), succ)
}
}
private class HashPatternTree extends ControlFlowTree, HashPattern {
final override predicate propagatesAbnormal(AstNode child) {
child = this.getClass() or
child = this.getValue(_) or
child = this.getRestVariableAccess()
}
final override predicate first(AstNode first) {
first(this.getClass(), first)
or
not exists(this.getClass()) and first = this
}
final override predicate last(AstNode last, Completion c) {
c.(MatchingCompletion).getValue() = false and
(
last = this and
c.isValidFor(this)
or
exists(AstNode node |
node = this.getClass() or
node = this.getValue(_)
|
last(node, last, c)
)
)
or
c.(MatchingCompletion).getValue() = true and
last = this and
not exists(this.getValue(_)) and
not exists(this.getRestVariableAccess())
or
c.(MatchingCompletion).getValue() = true and
last(max(int i | | this.getValue(i) order by i), last, c) and
not exists(this.getRestVariableAccess())
or
last(this.getRestVariableAccess(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(this.getClass(), pred, c) and
succ = this and
c.(MatchingCompletion).getValue() = true
or
exists(AstNode next |
pred = this and
c.(MatchingCompletion).getValue() = true and
first(next, succ)
|
next = this.getValue(0)
or
not exists(this.getValue(_)) and
next = this.getRestVariableAccess()
)
or
last(max(int i | | this.getValue(i) order by i), pred, c) and
first(this.getRestVariableAccess(), succ) and
c.(MatchingCompletion).getValue() = true
or
exists(int i |
last(this.getValue(i), pred, c) and
first(this.getValue(i + 1), succ) and
c.(MatchingCompletion).getValue() = true
)
}
}
private class LineLiteralTree extends LeafTree, LineLiteral { }
private class FileLiteralTree extends LeafTree, FileLiteral { }
private class EncodingLiteralTree extends LeafTree, EncodingLiteral { }
private class AlternativePatternTree extends PreOrderTree, AlternativePattern {
final override predicate propagatesAbnormal(AstNode child) { child = this.getAnAlternative() }
final override predicate last(AstNode last, Completion c) {
last(this.getAnAlternative(), last, c) and
c.(MatchingCompletion).getValue() = true
or
last(max(int i | | this.getAlternative(i) order by i), last, c) and
c.(MatchingCompletion).getValue() = false
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getAlternative(0), succ) and
c instanceof SimpleCompletion
or
exists(int i |
last(this.getAlternative(i), pred, c) and
first(this.getAlternative(i + 1), succ) and
c.(MatchingCompletion).getValue() = false
)
}
}
private class AsPatternTree extends PreOrderTree, AsPattern {
final override predicate propagatesAbnormal(AstNode child) { child = this.getPattern() }
final override predicate last(AstNode last, Completion c) {
last(this.getPattern(), last, c) and
c.(MatchingCompletion).getValue() = false
or
last(this.getVariableAccess(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getPattern(), succ) and
c instanceof SimpleCompletion
or
last(this.getPattern(), pred, c) and
first(this.getVariableAccess(), succ) and
c.(MatchingCompletion).getValue() = true
}
}
private class ParenthesizedPatternTree extends StandardPreOrderTree, ParenthesizedPattern {
override ControlFlowTree getChildElement(int i) { result = this.getPattern() and i = 0 }
}
private class VariableReferencePatternTree extends StandardPreOrderTree, VariableReferencePattern {
override ControlFlowTree getChildElement(int i) { result = this.getVariableAccess() and i = 0 }
}
private class InClauseTree extends PreOrderTree, InClause {
final override predicate propagatesAbnormal(AstNode child) {
child = this.getPattern() or
child = this.getCondition()
}
private predicate lastCondition(AstNode last, BooleanCompletion c, boolean flag) {
last(this.getCondition(), last, c) and
(
flag = true and this.hasIfCondition()
or
flag = false and this.hasUnlessCondition()
)
}
final override predicate last(AstNode last, Completion c) {
last(this.getPattern(), last, c) and
c.(MatchingCompletion).getValue() = false
or
exists(BooleanCompletion bc, boolean flag, MatchingCompletion mc |
lastCondition(last, bc, flag) and
c =
any(NestedMatchingCompletion nmc |
nmc.getInnerCompletion() = bc and nmc.getOuterCompletion() = mc
)
|
mc.getValue() = false and
bc.getValue() = flag.booleanNot()
or
not exists(this.getBody()) and
mc.getValue() = true and
bc.getValue() = flag
)
or
last(this.getBody(), last, c)
or
not exists(this.getBody()) and
not exists(this.getCondition()) and
last(this.getPattern(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getPattern(), succ) and
c instanceof SimpleCompletion
or
exists(Expr next |
last(this.getPattern(), pred, c) and
c.(MatchingCompletion).getValue() = true and
first(next, succ)
|
next = this.getCondition()
or
not exists(this.getCondition()) and next = this.getBody()
)
or
exists(boolean flag |
lastCondition(pred, c, flag) and
c.(BooleanCompletion).getValue() = flag and
first(this.getBody(), succ)
)
}
}
private class CharacterTree extends LeafTree, CharacterLiteral { }
private class ClassDeclarationTree extends NamespaceTree, ClassDeclaration {
@@ -542,7 +923,8 @@ module Trees {
c instanceof SimpleCompletion
or
this.hasDefaultValue() and
c.(MatchingCompletion).getValue() = true
c.(MatchingCompletion).getValue() = true and
c.isValidFor(this)
)
}