Skip to content

Commit b887165

Browse files
hvitvedaibaars
authored andcommitted
Ruby: Code review suggestions
1 parent 3689481 commit b887165

File tree

5 files changed

+141
-66
lines changed

5 files changed

+141
-66
lines changed

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

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ private newtype TCompletion =
2828
outer instanceof NonNestedNormalCompletion and
2929
nestLevel = 0
3030
or
31+
inner instanceof TBooleanCompletion and
32+
outer instanceof TMatchingCompletion and
33+
nestLevel = 0
34+
or
3135
inner instanceof NormalCompletion and
3236
nestedEnsureCompletion(outer, nestLevel)
3337
}
@@ -82,9 +86,8 @@ private predicate mayRaise(Call c) {
8286

8387
/** A completion of a statement or an expression. */
8488
abstract class Completion extends TCompletion {
85-
/** Holds if this completion is valid for node `n`. */
86-
predicate isValidFor(AstNode n) {
87-
exists(AstNode other | n = other.getDesugared() and this.isValidFor(other))
89+
private predicate isValidForSpecific(AstNode n) {
90+
exists(AstNode other | n = other.getDesugared() and this.isValidForSpecific(other))
8891
or
8992
this = n.(NonReturningCall).getACompletion()
9093
or
@@ -101,19 +104,26 @@ abstract class Completion extends TCompletion {
101104
mustHaveMatchingCompletion(n) and
102105
this = TMatchingCompletion(_)
103106
or
104-
n = any(RescueModifierExpr parent).getBody() and this = TRaiseCompletion()
107+
n = any(RescueModifierExpr parent).getBody() and
108+
this = [TSimpleCompletion().(TCompletion), TRaiseCompletion()]
105109
or
106110
(
107111
mayRaise(n)
108112
or
109113
n instanceof CaseMatch and not exists(n.(CaseExpr).getElseBranch())
110114
) and
111-
this = TRaiseCompletion()
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)
112125
or
113-
not n instanceof NonReturningCall and
114-
not completionIsValidForStmt(n, _) and
115-
not mustHaveBooleanCompletion(n) and
116-
not mustHaveMatchingCompletion(n) and
126+
not any(Completion c).isValidForSpecific(n) and
117127
this = TSimpleCompletion()
118128
}
119129

@@ -254,7 +264,7 @@ class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
254264
* the successor. Either a Boolean completion (`BooleanCompletion`), or a matching
255265
* completion (`MatchingCompletion`).
256266
*/
257-
abstract class ConditionalCompletion extends NonNestedNormalCompletion {
267+
abstract class ConditionalCompletion extends NormalCompletion {
258268
boolean value;
259269

260270
bindingset[value]
@@ -268,7 +278,7 @@ abstract class ConditionalCompletion extends NonNestedNormalCompletion {
268278
* A completion that represents evaluation of an expression
269279
* with a Boolean value.
270280
*/
271-
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
281+
class BooleanCompletion extends ConditionalCompletion, NonNestedNormalCompletion, TBooleanCompletion {
272282
BooleanCompletion() { this = TBooleanCompletion(value) }
273283

274284
/** Gets the dual Boolean completion. */
@@ -293,10 +303,16 @@ class FalseCompletion extends BooleanCompletion {
293303
* A completion that represents evaluation of a matching test, for example
294304
* a test in a `rescue` statement.
295305
*/
296-
class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
297-
MatchingCompletion() { this = TMatchingCompletion(value) }
306+
class MatchingCompletion extends ConditionalCompletion {
307+
MatchingCompletion() {
308+
this = TMatchingCompletion(value)
309+
or
310+
this = TNestedCompletion(_, TMatchingCompletion(value), _)
311+
}
298312

299-
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
313+
override SuccessorType getAMatchingSuccessorType() {
314+
this = TMatchingCompletion(result.(MatchingSuccessor).getValue())
315+
}
300316

301317
override string toString() { if value = true then result = "match" else result = "no-match" }
302318
}
@@ -502,3 +518,41 @@ class NestedEnsureCompletion extends NestedCompletion {
502518

503519
override SuccessorType getAMatchingSuccessorType() { none() }
504520
}
521+
522+
/**
523+
* A completion used for conditions in pattern matching:
524+
*
525+
* ```rb
526+
* in x if x == 5 then puts "five"
527+
* in x unless x == 4 then puts "not four"
528+
* ```
529+
*
530+
* The outer (Matching) completion indicates whether there is a match, and
531+
* the inner (Boolean) completion indicates what the condition evaluated
532+
* to.
533+
*
534+
* For the condition `x == 5` above, `TNestedCompletion(true, true, 0)` and
535+
* `TNestedCompletion(false, false, 0)` are both valid completions, while
536+
* `TNestedCompletion(true, false, 0)` and `TNestedCompletion(false, true, 0)`
537+
* are valid completions for `x == 4`.
538+
*/
539+
class NestedMatchingCompletion extends NestedCompletion, MatchingCompletion {
540+
NestedMatchingCompletion() {
541+
inner instanceof TBooleanCompletion and
542+
outer instanceof TMatchingCompletion
543+
}
544+
545+
override BooleanCompletion getInnerCompletion() { result = inner }
546+
547+
override MatchingCompletion getOuterCompletion() { result = outer }
548+
549+
override Completion getAnInnerCompatibleCompletion() {
550+
result.getOuterCompletion() = this.getInnerCompletion()
551+
}
552+
553+
override BooleanSuccessor getAMatchingSuccessorType() {
554+
result.getValue() = this.getInnerCompletion().getValue()
555+
}
556+
557+
override string toString() { result = NestedCompletion.super.toString() }
558+
}

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

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -429,12 +429,12 @@ module Trees {
429429
not c.(MatchingCompletion).getValue() = false
430430
or
431431
not exists(this.getElseBranch()) and
432-
exists(Completion lc, Expr lastBranch |
432+
exists(MatchingCompletion lc, Expr lastBranch |
433433
lastBranch = max(int i | | this.getBranch(i) order by i) and
434-
lc.(MatchingCompletion).getValue() = false and
434+
lc.getValue() = false and
435435
last(lastBranch, last, lc) and
436-
c.getInnerCompletion() = lc and
437-
c instanceof RaiseCompletion
436+
c instanceof RaiseCompletion and
437+
not c instanceof NestedCompletion
438438
)
439439
}
440440

@@ -480,7 +480,8 @@ module Trees {
480480
final override predicate last(AstNode last, Completion c) {
481481
c.(MatchingCompletion).getValue() = false and
482482
(
483-
last = this
483+
last = this and
484+
c.isValidFor(this)
484485
or
485486
exists(AstNode node |
486487
node = this.getClass() or
@@ -493,6 +494,7 @@ module Trees {
493494
or
494495
c.(MatchingCompletion).getValue() = true and
495496
last = this and
497+
c.isValidFor(this) and
496498
not exists(this.getPrefixElement(_)) and
497499
not exists(this.getRestVariableAccess())
498500
or
@@ -512,14 +514,16 @@ module Trees {
512514
succ = this and
513515
c.(MatchingCompletion).getValue() = true
514516
or
515-
pred = this and
516-
first(this.getPrefixElement(0), succ) and
517-
c.(MatchingCompletion).getValue() = true
518-
or
519-
not exists(this.getPrefixElement(_)) and
520-
pred = this and
521-
first(this.getRestVariableAccess(), succ) and
522-
c.(MatchingCompletion).getValue() = true
517+
exists(AstNode next |
518+
pred = this and
519+
c.(MatchingCompletion).getValue() = true and
520+
first(next, succ)
521+
|
522+
next = this.getPrefixElement(0)
523+
or
524+
not exists(this.getPrefixElement(_)) and
525+
next = this.getRestVariableAccess()
526+
)
523527
or
524528
last(max(int i | | this.getPrefixElement(i) order by i), pred, c) and
525529
first(this.getRestVariableAccess(), succ) and
@@ -565,7 +569,8 @@ module Trees {
565569
or
566570
c.(MatchingCompletion).getValue() = false and
567571
(
568-
last = this
572+
last = this and
573+
c.isValidFor(this)
569574
or
570575
exists(AstNode node | node = this.getClass() or node = this.getElement(_) |
571576
last(node, last, c)
@@ -578,14 +583,16 @@ module Trees {
578583
succ = this and
579584
c.(MatchingCompletion).getValue() = true
580585
or
581-
pred = this and
582-
first(this.getPrefixVariableAccess(), succ) and
583-
c.(MatchingCompletion).getValue() = true
584-
or
585-
pred = this and
586-
first(this.getElement(0), succ) and
587-
not exists(this.getPrefixVariableAccess()) and
588-
c.(MatchingCompletion).getValue() = true
586+
exists(AstNode next |
587+
pred = this and
588+
c.(MatchingCompletion).getValue() = true and
589+
first(next, succ)
590+
|
591+
next = this.getPrefixVariableAccess()
592+
or
593+
not exists(this.getPrefixVariableAccess()) and
594+
next = this.getElement(0)
595+
)
589596
or
590597
last(this.getPrefixVariableAccess(), pred, c) and
591598
first(this.getElement(0), succ) and
@@ -619,7 +626,8 @@ module Trees {
619626
final override predicate last(AstNode last, Completion c) {
620627
c.(MatchingCompletion).getValue() = false and
621628
(
622-
last = this
629+
last = this and
630+
c.isValidFor(this)
623631
or
624632
exists(AstNode node |
625633
node = this.getClass() or
@@ -646,14 +654,16 @@ module Trees {
646654
succ = this and
647655
c.(MatchingCompletion).getValue() = true
648656
or
649-
pred = this and
650-
first(this.getValue(0), succ) and
651-
c.(MatchingCompletion).getValue() = true
652-
or
653-
not exists(this.getValue(_)) and
654-
pred = this and
655-
first(this.getRestVariableAccess(), succ) and
656-
c.(MatchingCompletion).getValue() = true
657+
exists(AstNode next |
658+
pred = this and
659+
c.(MatchingCompletion).getValue() = true and
660+
first(next, succ)
661+
|
662+
next = this.getValue(0)
663+
or
664+
not exists(this.getValue(_)) and
665+
next = this.getRestVariableAccess()
666+
)
657667
or
658668
last(max(int i | | this.getValue(i) order by i), pred, c) and
659669
first(this.getRestVariableAccess(), succ) and
@@ -732,20 +742,36 @@ module Trees {
732742
child = this.getCondition()
733743
}
734744

745+
private predicate lastCondition(AstNode last, BooleanCompletion c, boolean flag) {
746+
last(this.getCondition(), last, c) and
747+
(
748+
flag = true and this.hasIfCondition()
749+
or
750+
flag = false and this.hasUnlessCondition()
751+
)
752+
}
753+
735754
final override predicate last(AstNode last, Completion c) {
736755
last(this.getPattern(), last, c) and
737756
c.(MatchingCompletion).getValue() = false
738757
or
739-
exists(Completion pc |
740-
last(this.getCondition(), last, pc) and
741-
pc.(ConditionalCompletion).getValue() = false and
742-
c.(MatchingCompletion).getValue() = false
758+
exists(BooleanCompletion bc, boolean flag, MatchingCompletion mc |
759+
lastCondition(last, bc, flag) and
760+
c =
761+
any(NestedMatchingCompletion nmc |
762+
nmc.getInnerCompletion() = bc and nmc.getOuterCompletion() = mc
763+
)
764+
|
765+
mc.getValue() = false and
766+
bc.getValue() = flag.booleanNot()
767+
or
768+
not exists(this.getBody()) and
769+
mc.getValue() = true and
770+
bc.getValue() = flag
743771
)
744772
or
745773
last(this.getBody(), last, c)
746774
or
747-
not exists(this.getBody()) and last(this.getCondition(), last, c)
748-
or
749775
not exists(this.getBody()) and
750776
not exists(this.getCondition()) and
751777
last(this.getPattern(), last, c)
@@ -766,15 +792,10 @@ module Trees {
766792
not exists(this.getCondition()) and next = this.getBody()
767793
)
768794
or
769-
exists(Completion pc, boolean flag |
770-
flag = true and this.hasIfCondition()
771-
or
772-
flag = false and this.hasUnlessCondition()
773-
|
774-
last(this.getCondition(), pred, pc) and
775-
pc.(ConditionalCompletion).getValue() = flag and
776-
first(this.getBody(), succ) and
777-
c.(MatchingCompletion).getValue() = true
795+
exists(boolean flag |
796+
lastCondition(pred, c, flag) and
797+
c.(BooleanCompletion).getValue() = flag and
798+
first(this.getBody(), succ)
778799
)
779800
}
780801
}

ruby/ql/test/library-tests/controlflow/graph/Cfg.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -627,8 +627,8 @@ case.rb:
627627
#-----| match -> x
628628

629629
# 14| ... == ...
630-
#-----| no-match -> in ... then ...
631-
#-----| match -> 6
630+
#-----| false -> in ... then ...
631+
#-----| true -> 6
632632

633633
# 14| x
634634
#-----| -> 5
@@ -649,8 +649,8 @@ case.rb:
649649
#-----| match -> x
650650

651651
# 15| ... < ...
652-
#-----| match -> 7
653-
#-----| no-match -> 8
652+
#-----| false -> 7
653+
#-----| true -> 8
654654

655655
# 15| x
656656
#-----| -> 0

ruby/ql/test/library-tests/controlflow/graph/Nodes.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ positionalArguments
8383
| case.rb:3:29:3:37 | call to puts | case.rb:3:34:3:37 | "x2" |
8484
| case.rb:4:17:4:24 | call to puts | case.rb:4:22:4:24 | "2" |
8585
| case.rb:14:13:14:18 | ... == ... | case.rb:14:18:14:18 | 5 |
86-
| case.rb:15:13:15:17 | ... < ... | case.rb:15:17:15:17 | 0 |
86+
| case.rb:15:17:15:21 | ... < ... | case.rb:15:21:15:21 | 0 |
8787
| case.rb:28:14:28:25 | call to raise | case.rb:28:20:28:25 | "oops" |
8888
| case.rb:76:8:76:18 | call to [] | case.rb:76:11:76:13 | "foo" |
8989
| case.rb:76:8:76:18 | call to [] | case.rb:76:15:76:17 | "bar" |

ruby/ql/test/library-tests/controlflow/graph/case.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def case_match value
1212
in 2
1313
4
1414
in x if x == 5 then 6
15-
in x if x < 0 then 7
15+
in x unless x < 0 then 7
1616
else 8
1717
end
1818
end

0 commit comments

Comments
 (0)