Skip to content

Commit 13f7fd8

Browse files
authored
Merge pull request github#7283 from aibaars/ruby-pattern-matching-cfg
Ruby: pattern matching: CFG
2 parents 634ed91 + fd4915a commit 13f7fd8

File tree

13 files changed

+1363
-43
lines changed

13 files changed

+1363
-43
lines changed

ruby/ql/lib/codeql/ruby/ast/internal/AST.qll

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,6 @@ private module Cached {
246246
explicitAssignmentNode(g, _)
247247
or
248248
casePattern(g)
249-
or
250-
classReferencePattern(g)
251249
)
252250
} or
253251
TScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier i) {
@@ -293,8 +291,6 @@ private module Cached {
293291
explicitAssignmentNode(g, _)
294292
or
295293
casePattern(g)
296-
or
297-
classReferencePattern(g)
298294
} or
299295
TTokenMethodName(MethodName::Token g) { MethodName::range(g) } or
300296
TTokenSuperCall(Ruby::Super g) { vcall(g) } or

ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,7 @@ predicate casePattern(Ruby::AstNode node) {
4848
node = any(Ruby::KeywordPattern parent).getValue()
4949
or
5050
node = any(Ruby::ParenthesizedPattern parent).getChild()
51-
}
52-
53-
/**
54-
* Holds if `node` is a class reference used in an
55-
* array, find, or hash pattern.
56-
*/
57-
predicate classReferencePattern(Ruby::AstNode node) {
51+
or
5852
node = any(Ruby::ArrayPattern p).getClass()
5953
or
6054
node = any(Ruby::FindPattern p).getClass()

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

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
private import codeql.ruby.AST
88
private import codeql.ruby.ast.internal.AST
9+
private import codeql.ruby.ast.internal.Control
910
private import codeql.ruby.controlflow.ControlFlowGraph
1011
private import ControlFlowGraphImpl
1112
private import NonReturning
@@ -27,6 +28,10 @@ private newtype TCompletion =
2728
outer instanceof NonNestedNormalCompletion and
2829
nestLevel = 0
2930
or
31+
inner instanceof TBooleanCompletion and
32+
outer instanceof TMatchingCompletion and
33+
nestLevel = 0
34+
or
3035
inner instanceof NormalCompletion and
3136
nestedEnsureCompletion(outer, nestLevel)
3237
}
@@ -81,8 +86,9 @@ private predicate mayRaise(Call c) {
8186

8287
/** A completion of a statement or an expression. */
8388
abstract class Completion extends TCompletion {
84-
/** Holds if this completion is valid for node `n`. */
85-
predicate isValidFor(AstNode n) {
89+
private predicate isValidForSpecific(AstNode n) {
90+
exists(AstNode other | n = other.getDesugared() and this.isValidForSpecific(other))
91+
or
8692
this = n.(NonReturningCall).getACompletion()
8793
or
8894
completionIsValidForStmt(n, this)
@@ -98,15 +104,26 @@ abstract class Completion extends TCompletion {
98104
mustHaveMatchingCompletion(n) and
99105
this = TMatchingCompletion(_)
100106
or
101-
n = any(RescueModifierExpr parent).getBody() and this = TRaiseCompletion()
107+
n = any(RescueModifierExpr parent).getBody() and
108+
this = [TSimpleCompletion().(TCompletion), TRaiseCompletion()]
102109
or
103-
mayRaise(n) and
104-
this = TRaiseCompletion()
110+
(
111+
mayRaise(n)
112+
or
113+
n instanceof CaseMatch and not exists(n.(CaseExpr).getElseBranch())
114+
) and
115+
(
116+
this = TRaiseCompletion()
117+
or
118+
this = TSimpleCompletion() and not n instanceof NonReturningCall
119+
)
120+
}
121+
122+
/** Holds if this completion is valid for node `n`. */
123+
predicate isValidFor(AstNode n) {
124+
this.isValidForSpecific(n)
105125
or
106-
not n instanceof NonReturningCall and
107-
not completionIsValidForStmt(n, _) and
108-
not mustHaveBooleanCompletion(n) and
109-
not mustHaveMatchingCompletion(n) and
126+
not any(Completion c).isValidForSpecific(n) and
110127
this = TSimpleCompletion()
111128
}
112129

@@ -172,6 +189,8 @@ private predicate inBooleanContext(AstNode n) {
172189
or
173190
n = any(ConditionalLoop parent).getCondition()
174191
or
192+
n = any(InClause parent).getCondition()
193+
or
175194
exists(LogicalAndExpr parent |
176195
n = parent.getLeftOperand()
177196
or
@@ -218,6 +237,10 @@ private predicate inMatchingContext(AstNode n) {
218237
w.getPattern(_) = n
219238
)
220239
or
240+
n instanceof CasePattern
241+
or
242+
n = any(VariableReferencePattern p).getVariableAccess()
243+
or
221244
n.(Trees::DefaultValueParameterTree).hasDefaultValue()
222245
}
223246

@@ -241,7 +264,7 @@ class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
241264
* the successor. Either a Boolean completion (`BooleanCompletion`), or a matching
242265
* completion (`MatchingCompletion`).
243266
*/
244-
abstract class ConditionalCompletion extends NonNestedNormalCompletion {
267+
abstract class ConditionalCompletion extends NormalCompletion {
245268
boolean value;
246269

247270
bindingset[value]
@@ -255,7 +278,7 @@ abstract class ConditionalCompletion extends NonNestedNormalCompletion {
255278
* A completion that represents evaluation of an expression
256279
* with a Boolean value.
257280
*/
258-
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
281+
class BooleanCompletion extends ConditionalCompletion, NonNestedNormalCompletion, TBooleanCompletion {
259282
BooleanCompletion() { this = TBooleanCompletion(value) }
260283

261284
/** Gets the dual Boolean completion. */
@@ -280,10 +303,16 @@ class FalseCompletion extends BooleanCompletion {
280303
* A completion that represents evaluation of a matching test, for example
281304
* a test in a `rescue` statement.
282305
*/
283-
class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
284-
MatchingCompletion() { this = TMatchingCompletion(value) }
306+
class MatchingCompletion extends ConditionalCompletion {
307+
MatchingCompletion() {
308+
this = TMatchingCompletion(value)
309+
or
310+
this = TNestedCompletion(_, TMatchingCompletion(value), _)
311+
}
285312

286-
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
313+
override ConditionalSuccessor getAMatchingSuccessorType() {
314+
this = TMatchingCompletion(result.(MatchingSuccessor).getValue())
315+
}
287316

288317
override string toString() { if value = true then result = "match" else result = "no-match" }
289318
}
@@ -440,7 +469,9 @@ abstract class NestedCompletion extends Completion, TNestedCompletion {
440469
NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) }
441470

442471
/** Gets a completion that is compatible with the inner completion. */
443-
abstract Completion getAnInnerCompatibleCompletion();
472+
Completion getAnInnerCompatibleCompletion() {
473+
result.getOuterCompletion() = this.getInnerCompletion()
474+
}
444475

445476
/** Gets the level of this nested completion. */
446477
final int getNestLevel() { result = nestLevel }
@@ -483,9 +514,39 @@ class NestedEnsureCompletion extends NestedCompletion {
483514

484515
override Completion getOuterCompletion() { result = outer }
485516

486-
override Completion getAnInnerCompatibleCompletion() {
487-
result.getOuterCompletion() = this.getInnerCompletion()
517+
override SuccessorType getAMatchingSuccessorType() { none() }
518+
}
519+
520+
/**
521+
* A completion used for conditions in pattern matching:
522+
*
523+
* ```rb
524+
* in x if x == 5 then puts "five"
525+
* in x unless x == 4 then puts "not four"
526+
* ```
527+
*
528+
* The outer (Matching) completion indicates whether there is a match, and
529+
* the inner (Boolean) completion indicates what the condition evaluated
530+
* to.
531+
*
532+
* For the condition `x == 5` above, `TNestedCompletion(true, true, 0)` and
533+
* `TNestedCompletion(false, false, 0)` are both valid completions, while
534+
* `TNestedCompletion(true, false, 0)` and `TNestedCompletion(false, true, 0)`
535+
* are valid completions for `x == 4`.
536+
*/
537+
class NestedMatchingCompletion extends NestedCompletion, MatchingCompletion {
538+
NestedMatchingCompletion() {
539+
inner instanceof TBooleanCompletion and
540+
outer instanceof TMatchingCompletion
488541
}
489542

490-
override SuccessorType getAMatchingSuccessorType() { none() }
543+
override BooleanCompletion getInnerCompletion() { result = inner }
544+
545+
override MatchingCompletion getOuterCompletion() { result = outer }
546+
547+
override BooleanSuccessor getAMatchingSuccessorType() {
548+
result.getValue() = this.getInnerCompletion().getValue()
549+
}
550+
551+
override string toString() { result = NestedCompletion.super.toString() }
491552
}

0 commit comments

Comments
 (0)