Skip to content

Commit f0f5d44

Browse files
committed
C#: Replace BreakNormalCompletion with a nested completion
1 parent 17df059 commit f0f5d44

File tree

6 files changed

+215
-228
lines changed

6 files changed

+215
-228
lines changed

csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll

Lines changed: 80 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,28 @@ private newtype TCompletion =
3636
TEmptinessCompletion(boolean isEmpty) { isEmpty = true or isEmpty = false } or
3737
TReturnCompletion() or
3838
TBreakCompletion() or
39-
TBreakNormalCompletion() or
4039
TContinueCompletion() or
4140
TGotoCompletion(string label) { label = any(GotoStmt gs).getLabel() } or
4241
TThrowCompletion(ExceptionClass ec) or
4342
TExitCompletion() or
44-
TNestedCompletion(ConditionalCompletion inner, Completion outer) {
45-
outer = TReturnCompletion()
46-
or
47-
outer = TBreakCompletion()
48-
or
49-
outer = TContinueCompletion()
50-
or
51-
outer = TGotoCompletion(_)
52-
or
53-
outer = TThrowCompletion(_)
43+
TNestedCompletion(Completion inner, Completion outer) {
44+
inner instanceof NormalCompletion and
45+
(
46+
outer = TReturnCompletion()
47+
or
48+
outer = TBreakCompletion()
49+
or
50+
outer = TContinueCompletion()
51+
or
52+
outer = TGotoCompletion(_)
53+
or
54+
outer = TThrowCompletion(_)
55+
or
56+
outer = TExitCompletion()
57+
)
5458
or
55-
outer = TExitCompletion()
59+
inner = TBreakCompletion() and
60+
outer instanceof NonNestedNormalCompletion
5661
}
5762

5863
pragma[noinline]
@@ -534,8 +539,10 @@ private predicate mustHaveEmptinessCompletion(ControlFlowElement cfe) { foreachE
534539
*/
535540
abstract class NormalCompletion extends Completion { }
536541

542+
abstract private class NonNestedNormalCompletion extends NormalCompletion { }
543+
537544
/** A simple (normal) completion. */
538-
class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
545+
class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
539546
override NormalSuccessor getAMatchingSuccessorType() { any() }
540547

541548
override string toString() { result = "normal" }
@@ -547,7 +554,7 @@ class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
547554
* completion (`NullnessCompletion`), a matching completion (`MatchingCompletion`),
548555
* or an emptiness completion (`EmptinessCompletion`).
549556
*/
550-
abstract class ConditionalCompletion extends NormalCompletion { }
557+
abstract class ConditionalCompletion extends NonNestedNormalCompletion { }
551558

552559
/**
553560
* A completion that represents evaluation of an expression
@@ -635,39 +642,6 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
635642
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }
636643
}
637644

638-
/**
639-
* A completion that represents evaluation of a statement or
640-
* expression resulting in a loop break.
641-
*
642-
* This completion is added for technical reasons only: when a loop
643-
* body can complete with a break completion, the loop itself completes
644-
* normally. However, if we choose `TSimpleCompletion` as the completion
645-
* of the loop, we lose the information that the last element actually
646-
* completed with a break, meaning that the control flow edge out of the
647-
* breaking node cannot be marked with a `break` label.
648-
*
649-
* Example:
650-
*
651-
* ```csharp
652-
* while (...) {
653-
* ...
654-
* break;
655-
* }
656-
* return;
657-
* ```
658-
*
659-
* The `break` on line 3 completes with a `TBreakCompletion`, therefore
660-
* the `while` loop can complete with a `TBreakNormalCompletion`, so we
661-
* get an edge `break --break--> return`. (If we instead used a
662-
* `TSimpleCompletion`, we would get a less precise edge
663-
* `break --normal--> return`.)
664-
*/
665-
class BreakNormalCompletion extends NormalCompletion, TBreakNormalCompletion {
666-
override BreakSuccessor getAMatchingSuccessorType() { any() }
667-
668-
override string toString() { result = "normal (break)" }
669-
}
670-
671645
/**
672646
* A nested completion. For example, in
673647
*
@@ -691,12 +665,17 @@ class BreakNormalCompletion extends NormalCompletion, TBreakNormalCompletion {
691665
* and an inner `false` completion. `b2` also has a (normal) `true` completion.
692666
*/
693667
class NestedCompletion extends Completion, TNestedCompletion {
694-
private ConditionalCompletion inner;
695-
private Completion outer;
668+
Completion inner;
669+
Completion outer;
696670

697671
NestedCompletion() { this = TNestedCompletion(inner, outer) }
698672

699-
override ConditionalCompletion getInnerCompletion() { result = inner }
673+
/** Gets a completion that is compatible with the inner completion. */
674+
Completion getAnInnerCompatibleCompletion() {
675+
result.getOuterCompletion() = this.getInnerCompletion()
676+
}
677+
678+
override Completion getInnerCompletion() { result = inner }
700679

701680
override Completion getOuterCompletion() { result = outer }
702681

@@ -705,6 +684,57 @@ class NestedCompletion extends Completion, TNestedCompletion {
705684
override string toString() { result = outer + " [" + inner + "]" }
706685
}
707686

687+
/**
688+
* A nested completion for a loop that exists with a `break`.
689+
*
690+
* This completion is added for technical reasons only: when a loop
691+
* body can complete with a break completion, the loop itself completes
692+
* normally. However, if we choose `TSimpleCompletion` as the completion
693+
* of the loop, we lose the information that the last element actually
694+
* completed with a break, meaning that the control flow edge out of the
695+
* breaking node cannot be marked with a `break` label.
696+
*
697+
* Example:
698+
*
699+
* ```csharp
700+
* while (...) {
701+
* ...
702+
* break;
703+
* }
704+
* return;
705+
* ```
706+
*
707+
* The `break` on line 3 completes with a `TBreakCompletion`, therefore
708+
* the `while` loop can complete with a `NestedBreakCompletion`, so we
709+
* get an edge `break --break--> return`. (If we instead used a
710+
* `TSimpleCompletion`, we would get a less precise edge
711+
* `break --normal--> return`.)
712+
*/
713+
class NestedBreakCompletion extends NormalCompletion, NestedCompletion {
714+
NestedBreakCompletion() {
715+
inner = TBreakCompletion() and
716+
outer instanceof NonNestedNormalCompletion
717+
}
718+
719+
override BreakCompletion getInnerCompletion() { result = inner }
720+
721+
override SimpleCompletion getOuterCompletion() { result = outer }
722+
723+
override Completion getAnInnerCompatibleCompletion() {
724+
result = inner and
725+
outer = TSimpleCompletion()
726+
or
727+
result = TNestedCompletion(outer, inner)
728+
}
729+
730+
override SuccessorType getAMatchingSuccessorType() {
731+
outer instanceof SimpleCompletion and
732+
result instanceof BreakSuccessor
733+
or
734+
result = outer.(ConditionalCompletion).getAMatchingSuccessorType()
735+
}
736+
}
737+
708738
/**
709739
* A completion that represents evaluation of a statement or an
710740
* expression resulting in a return from a callable.

csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,8 +1040,7 @@ module Statements {
10401040
c instanceof NormalCompletion
10411041
or
10421042
// A statement exits with a `break` completion
1043-
last(this.getStmt(_), last, any(BreakCompletion bc)) and
1044-
c instanceof BreakNormalCompletion
1043+
last(this.getStmt(_), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
10451044
or
10461045
// A statement exits abnormally
10471046
last(this.getStmt(_), last, c) and
@@ -1123,12 +1122,8 @@ module Statements {
11231122
last(this.getCondition(), last, c) and
11241123
c instanceof FalseCompletion
11251124
or
1126-
// Body exits with a break completion; the loop exits normally
1127-
// Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion`
1128-
// in order to be able to get the correct break label in the control flow
1129-
// graph from the `result` node to the node after the loop.
1130-
last(body, last, any(BreakCompletion bc)) and
1131-
c instanceof BreakNormalCompletion
1125+
// Body exits with a break completion
1126+
last(body, last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
11321127
or
11331128
// Body exits with a completion that does not continue the loop
11341129
last(body, last, c) and
@@ -1252,12 +1247,8 @@ module Statements {
12521247
last = this and
12531248
c.(EmptinessCompletion).isEmpty()
12541249
or
1255-
// Body exits with a break completion; the loop exits normally
1256-
// Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion`
1257-
// in order to be able to get the correct break label in the control flow
1258-
// graph from the `result` node to the node after the loop.
1259-
last(this.getBody(), last, any(BreakCompletion bc)) and
1260-
c instanceof BreakNormalCompletion
1250+
// Body exits with a break completion
1251+
last(this.getBody(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
12611252
or
12621253
// Body exits abnormally
12631254
last(this.getBody(), last, c) and
@@ -1375,15 +1366,10 @@ module Statements {
13751366
or
13761367
// If the `finally` block completes normally, it inherits any non-normal
13771368
// completion that was current before the `finally` block was entered
1378-
exists(NormalCompletion finally, Completion outer | this.lastFinally(last, finally, outer) |
1379-
c =
1380-
any(NestedCompletion nc |
1381-
nc.getInnerCompletion() = finally and nc.getOuterCompletion() = outer
1382-
)
1383-
or
1384-
not finally instanceof ConditionalCompletion and
1385-
c = outer
1386-
)
1369+
c =
1370+
any(NestedCompletion nc |
1371+
this.lastFinally(last, nc.getAnInnerCompatibleCompletion(), nc.getOuterCompletion())
1372+
)
13871373
}
13881374

13891375
/**

csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -860,8 +860,7 @@ module FinallySplitting {
860860
(
861861
exit(pred, c, _)
862862
or
863-
exit(pred, any(BreakCompletion bc), _) and
864-
c instanceof BreakNormalCompletion
863+
exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
865864
)
866865
}
867866

@@ -870,8 +869,7 @@ module FinallySplitting {
870869
(
871870
exit(last, c, _)
872871
or
873-
exit(last, any(BreakCompletion bc), _) and
874-
c instanceof BreakNormalCompletion
872+
exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
875873
)
876874
}
877875

csharp/ql/test/library-tests/controlflow/graph/Condition.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,7 @@ conditionFlow
13071307
| BreakInTry.cs:31:21:31:32 | ... == ... | BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | false |
13081308
| BreakInTry.cs:31:21:31:32 | ... == ... | BreakInTry.cs:32:21:32:21 | ; | true |
13091309
| BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:32:21:32:21 | [finally: break] ; | true |
1310+
| BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:35:7:35:7 | ; | false |
13101311
| BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:43:17:43:23 | return ...; | true |
13111312
| BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:46:9:52:9 | {...} | false |
13121313
| BreakInTry.cs:49:21:49:31 | ... == ... | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | false |

0 commit comments

Comments
 (0)