Skip to content

Commit 0b22331

Browse files
committed
C#: Simplify CFG logic for finally blocks
1 parent 249eea9 commit 0b22331

2 files changed

Lines changed: 34 additions & 53 deletions

File tree

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

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private predicate goto(ControlFlowElement cfe, GotoCompletion gc, string label,
108108
// Special case: when a `goto` happens inside a `try` statement with a
109109
// `finally` block, flow does not go directly to the target, but instead
110110
// to the `finally` block (and from there possibly to the target)
111-
not cfe = any(Statements::TryStmtTree t | t.hasFinally()).getBlockOrCatchFinallyPred(_) and
111+
not cfe = any(Statements::TryStmtTree t | t.hasFinally()).getAFinallyPredecessor(_, true) and
112112
label = gc.getLabel() and
113113
enclosing = cfe.getEnclosingCallable()
114114
}
@@ -1355,26 +1355,30 @@ module Statements {
13551355
/**
13561356
* Gets a last element from a `try` or `catch` block of this `try` statement
13571357
* that may finish with completion `c`, such that control may be transferred
1358-
* to the `finally` block (if it exists).
1358+
* to the `finally` block (if it exists), but only if `finalizable = true`.
13591359
*/
13601360
pragma[nomagic]
1361-
ControlFlowElement getBlockOrCatchFinallyPred(Completion c) {
1362-
this.lastBlock(result, c) and
1361+
ControlFlowElement getAFinallyPredecessor(Completion c, boolean finalizable) {
1362+
// Exit completions skip the `finally` block
1363+
(if c instanceof ExitCompletion then finalizable = false else finalizable = true) and
13631364
(
1364-
// Any non-throw completion from the `try` block will always continue directly
1365-
// to the `finally` block
1366-
not c instanceof ThrowCompletion
1365+
this.lastBlock(result, c) and
1366+
(
1367+
// Any non-throw completion from the `try` block will always continue directly
1368+
// to the `finally` block
1369+
not c instanceof ThrowCompletion
1370+
or
1371+
// Any completion from the `try` block will continue to the `finally` block
1372+
// when there are no catch clauses
1373+
not exists(this.getACatchClause())
1374+
)
1375+
or
1376+
// Last element from any of the `catch` clause blocks continues to the `finally` block
1377+
result = lastCatchClauseBlock(this.getACatchClause(), c)
13671378
or
1368-
// Any completion from the `try` block will continue to the `finally` block
1369-
// when there are no catch clauses
1370-
not exists(this.getACatchClause())
1379+
// Last element of last `catch` clause continues to the `finally` block
1380+
result = lastLastCatchClause(this.getACatchClause(), c)
13711381
)
1372-
or
1373-
// Last element from any of the `catch` clause blocks continues to the `finally` block
1374-
result = lastCatchClauseBlock(this.getACatchClause(), c)
1375-
or
1376-
// Last element of last `catch` clause continues to the `finally` block
1377-
result = lastLastCatchClause(this.getACatchClause(), c)
13781382
}
13791383

13801384
pragma[nomagic]
@@ -1387,19 +1391,19 @@ module Statements {
13871391
ControlFlowElement last, NormalCompletion finally, Completion outer, int nestLevel
13881392
) {
13891393
this.lastFinally0(last, finally) and
1390-
exists(this.getBlockOrCatchFinallyPred(any(Completion c0 | outer = c0.getOuterCompletion()))) and
1394+
exists(
1395+
this.getAFinallyPredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true)
1396+
) and
13911397
nestLevel = this.nestLevel()
13921398
}
13931399

13941400
final override predicate last(ControlFlowElement last, Completion c) {
1395-
last = this.getBlockOrCatchFinallyPred(c) and
1396-
(
1401+
exists(boolean finalizable | last = this.getAFinallyPredecessor(c, finalizable) |
13971402
// If there is no `finally` block, last elements are from the body, from
13981403
// the blocks of one of the `catch` clauses, or from the last `catch` clause
13991404
not this.hasFinally()
14001405
or
1401-
// Exit completions ignore the `finally` block
1402-
c instanceof ExitCompletion
1406+
finalizable = false
14031407
)
14041408
or
14051409
this.lastFinally(last, c, any(NormalCompletion nc), _)
@@ -1433,43 +1437,22 @@ module Statements {
14331437
first(this.getCatchClause(0), succ)
14341438
or
14351439
exists(CatchClause cc, int i | cc = this.getCatchClause(i) |
1440+
// Flow from one `catch` clause to the next
14361441
pred = cc and
14371442
last(this.getCatchClause(i), cc, c) and
1438-
(
1439-
// Flow from one `catch` clause to the next
1440-
first(this.getCatchClause(i + 1), succ) and
1441-
c = any(MatchingCompletion mc | not mc.isMatch())
1442-
or
1443-
// Flow from last `catch` clause to first element of `finally` block
1444-
this.getCatchClause(i).isLast() and
1445-
first(this.getFinally(), succ) and
1446-
c instanceof ThrowCompletion // inherited from `try` block
1447-
)
1443+
first(this.getCatchClause(i + 1), succ) and
1444+
c = any(MatchingCompletion mc | not mc.isMatch())
14481445
or
1446+
// Flow from last element of `catch` clause filter to next `catch` clause
14491447
last(this.getCatchClause(i), pred, c) and
14501448
last(cc.getFilterClause(), pred, _) and
1451-
(
1452-
// Flow from last element of `catch` clause filter to next `catch` clause
1453-
first(this.getCatchClause(i + 1), succ) and
1454-
c instanceof FalseCompletion
1455-
or
1456-
// Flow from last element of `catch` clause filter, of last clause, to first
1457-
// element of `finally` block
1458-
this.getCatchClause(i).isLast() and
1459-
first(this.getFinally(), succ) and
1460-
c instanceof ThrowCompletion // inherited from `try` block
1461-
)
1462-
or
1463-
// Flow from last element of a `catch` block to first element of `finally` block
1464-
pred = lastCatchClauseBlock(cc, c) and
1465-
first(this.getFinally(), succ)
1449+
first(this.getCatchClause(i + 1), succ) and
1450+
c instanceof FalseCompletion
14661451
)
14671452
or
1468-
// Flow from last element of `try` block to first element of `finally` block
1469-
this.lastBlock(pred, c) and
1470-
first(this.getFinally(), succ) and
1471-
not c instanceof ExitCompletion and
1472-
(c instanceof ThrowCompletion implies not exists(this.getACatchClause()))
1453+
// Flow into `finally` block
1454+
pred = getAFinallyPredecessor(c, true) and
1455+
first(this.getFinally(), succ)
14731456
}
14741457
}
14751458

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,9 +1537,7 @@
15371537
| ExitMethods.cs:88:9:88:28 | ...; | ExitMethods.cs:88:9:88:27 | call to method Exit | exit |
15381538
| ExitMethods.cs:88:26:88:26 | 0 | ExitMethods.cs:88:26:88:26 | 0 | normal |
15391539
| ExitMethods.cs:92:5:102:5 | {...} | ExitMethods.cs:95:13:95:18 | call to method Exit | exit |
1540-
| ExitMethods.cs:92:5:102:5 | {...} | ExitMethods.cs:100:13:100:40 | call to method WriteLine | exit [normal] (0) |
15411540
| ExitMethods.cs:93:9:101:9 | try {...} ... | ExitMethods.cs:95:13:95:18 | call to method Exit | exit |
1542-
| ExitMethods.cs:93:9:101:9 | try {...} ... | ExitMethods.cs:100:13:100:40 | call to method WriteLine | exit [normal] (0) |
15431541
| ExitMethods.cs:94:9:96:9 | {...} | ExitMethods.cs:95:13:95:18 | call to method Exit | exit |
15441542
| ExitMethods.cs:95:13:95:18 | call to method Exit | ExitMethods.cs:95:13:95:18 | call to method Exit | exit |
15451543
| ExitMethods.cs:95:13:95:18 | this access | ExitMethods.cs:95:13:95:18 | this access | normal |

0 commit comments

Comments
 (0)