Skip to content

Commit 7b3fe52

Browse files
committed
Model Go function epilogues in the body CFG
1 parent 3732983 commit 7b3fe52

6 files changed

Lines changed: 134 additions & 292 deletions

File tree

go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll

Lines changed: 51 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ module CfgImpl {
162162

163163
class BlockStmt extends Go::BlockStmt {
164164
BlockStmt() {
165+
not this = any(Go::FuncDef fd).getBody() and
165166
not this = any(Go::SwitchStmt sw).getBody() and
166167
not this = any(Go::SelectStmt sel).getBody()
167168
}
@@ -453,7 +454,7 @@ module CfgImpl {
453454
// The call of a `defer` statement is not invoked at the statement
454455
// itself; its callee and arguments are evaluated in place, but the call
455456
// is only invoked later, at function exit (modelled by the `defer-invoke`
456-
// node and `deferExitStep`). Marking it as pre-order means no in-order
457+
// node and `additionalSuccessor`). Marking it as pre-order means no in-order
457458
// "invocation" node (and hence no inline exceptional-exit edge) is
458459
// created at the `defer` statement.
459460
e = any(Go::DeferStmt s).getCall()
@@ -603,7 +604,7 @@ module CfgImpl {
603604
tag = "implicit-field:" + i.toString()
604605
)
605606
or
606-
// Deferred-call invocation node, placed at function exit by `deferExitStep`
607+
// Deferred-call invocation node, placed at function exit by `additionalSuccessor`
607608
n = any(Go::DeferStmt s).getCall() and tag = "defer-invoke"
608609
)
609610
}
@@ -800,40 +801,31 @@ module CfgImpl {
800801
exists(fd.getResultVar(0)) and
801802
n.isAdditional(fd.getBody(), "result-read:0")
802803
)
803-
}
804-
805-
additional predicate overridesCallableEndAbruptCompletion(
806-
Ast::Callable c, AbruptCompletion completion
807-
) {
808-
// For functions with result variables, the library's default routing of a
809-
// `return` straight to the normal exit node is suppressed so that the
810-
// return is instead caught by `endAbruptCompletion` above and routed
811-
// through the result-read epilogue.
812-
//
813-
// For functions containing `defer` statements, the default routing is
814-
// likewise suppressed so that returns are routed through the deferred-call
815-
// epilogue (see `deferExitStep`) instead.
816-
(exists(c.(Go::FuncDef).getResultVar(0)) or funcHasDefer(c.(Go::FuncDef))) and
817-
completion.getSuccessorType() instanceof ReturnSuccessor
804+
or
805+
// Function bodies are excluded from `Ast::BlockStmt`, so handle goto
806+
// targets among their top-level statements here.
807+
exists(Go::FuncDef fd, Go::Stmt target, Label l |
808+
ast = fd.getBody() and
809+
target = fd.getBody().getAStmt() and
810+
not target instanceof Go::GotoStmt and
811+
hasLabel(target, l) and
812+
n.isBefore(target) and
813+
c.getSuccessorType() instanceof GotoSuccessor and
814+
c.hasLabel(l)
815+
)
818816
}
819817

820818
additional predicate overridesAbruptCompletionEdge(
821819
PreControlFlowNode source, PreControlFlowNode target, AbruptCompletion completion
822820
) {
821+
completion.getSuccessorType() instanceof ReturnSuccessor and
822+
target instanceof NormalExitNodeImpl and
823+
exists(PreControlFlowNode replacement | additionalSuccessor(source, replacement, _))
824+
or
823825
completion.getSuccessorType() instanceof ExceptionSuccessor and
824826
target instanceof ExceptionalExitNodeImpl and
825827
exists(PreControlFlowNode nextDefer |
826-
deferExitStep(source, nextDefer, _) and deferInvoke(nextDefer, _)
827-
)
828-
}
829-
830-
additional predicate callableExitStep(PreControlFlowNode n, Ast::Callable c, boolean normal) {
831-
// The last result-read node of the epilogue steps to the normal exit node.
832-
exists(Go::FuncDef fd, int j | fd = c |
833-
normal = true and
834-
exists(fd.getResultVar(j)) and
835-
not exists(fd.getResultVar(j + 1)) and
836-
n.isAdditional(fd.getBody(), "result-read:" + j.toString())
828+
additionalSuccessor(source, nextDefer, _) and deferInvoke(nextDefer, _)
837829
)
838830
}
839831

@@ -937,12 +929,12 @@ module CfgImpl {
937929

938930
/**
939931
* Holds if `n` is a normal-exit predecessor of `fd`: a `return` statement
940-
* node, or the fall-through node after the body.
932+
* node, or the normal fall-through from the body's last statement.
941933
*/
942934
private predicate normalExitPred(PreControlFlowNode n, Go::FuncDef fd) {
943935
exists(Go::ReturnStmt ret | ret.getEnclosingFunction() = fd and n.isIn(ret))
944936
or
945-
n.isAfter(fd.getBody())
937+
n.isAfter(getLastRankedChild(fd.getBody()))
946938
}
947939

948940
/**
@@ -968,17 +960,16 @@ module CfgImpl {
968960
/**
969961
* Holds if, after running its deferred calls, `fd` should continue at
970962
* `target` on a normal exit. For functions with result variables this is
971-
* the start of the result-read epilogue; otherwise it is the normal exit
972-
* node directly.
963+
* the start of the result-read epilogue; otherwise it is the function
964+
* body's `After` node.
973965
*/
974966
private predicate deferChainExitTarget(Go::FuncDef fd, PreControlFlowNode target) {
975967
exists(fd.getResultVar(0)) and target.isAdditional(fd.getBody(), "result-read:0")
976968
or
977-
not exists(fd.getResultVar(_)) and
978-
target.(NormalExitNodeImpl).getEnclosingCallable() = fd
969+
not exists(fd.getResultVar(_)) and target.isAfter(fd.getBody())
979970
}
980971

981-
additional predicate deferExitStep(
972+
additional predicate additionalSuccessor(
982973
PreControlFlowNode n1, PreControlFlowNode n2, SuccessorType successorType
983974
) {
984975
exists(Go::FuncDef fd | funcHasDefer(fd) |
@@ -1028,10 +1019,6 @@ module CfgImpl {
10281019
)
10291020
}
10301021

1031-
additional predicate overridesCallableBodyExit(Ast::Callable c) {
1032-
funcHasDefer(c.(Go::FuncDef))
1033-
}
1034-
10351022
additional predicate overridesDefaultControlFlow(Ast::AstNode ast) {
10361023
exists(Go::SelectStmt sel, Go::RecvStmt recv |
10371024
recv = sel.getACommClause().getComm() and
@@ -1054,6 +1041,10 @@ module CfgImpl {
10541041
) {
10551042
ast = any(Go::FuncDef fd | hasFuncDefPrologue(fd)).getBody() and source.isBefore(ast)
10561043
or
1044+
ast = any(Go::FuncDef fd | funcHasDefer(fd)).getBody() and
1045+
source.isAfter(getLastRankedChild(ast)) and
1046+
target.isAfter(ast)
1047+
or
10571048
exists(getFirstEpilogueTag(ast)) and
10581049
(
10591050
source.isAfter(getLastRankedChild(ast))
@@ -1693,21 +1684,16 @@ module CfgImpl {
16931684
* variables: zero-init:0 → zero-init:1 → ... → first statement.
16941685
* - Epilogue: return → result-read:0 → result-read:1 → ... → result-read:last
16951686
*
1696-
* The last result-read node goes to `NormalExit(fd)` via the shared
1697-
* library's `callableExitStep` hook.
1687+
* The last result-read node goes to `After(body)`, from which the shared
1688+
* callable CFG continues to the normal exit.
16981689
*/
16991690
private predicate hasFuncDefPrologue(Go::FuncDef fd) { exists(fd.getResultVar(_)) }
17001691

17011692
private predicate funcDefBodyStart(Go::FuncDef fd, PreControlFlowNode n) {
17021693
n.isBefore(getRankedChild(fd.getBody(), 1))
17031694
or
17041695
not exists(getRankedChild(fd.getBody(), _)) and
1705-
n.isAfter(fd.getBody()) and
1706-
// When Before(body) and After(body) are the same node (the shared library's
1707-
// "simple leaf node" optimization merges them for empty bodies without
1708-
// additional nodes), don't generate Before→After as it would be a self-loop.
1709-
// The callable exit mechanism already routes After(body) → NormalExit.
1710-
not n.isBefore(fd.getBody())
1696+
n.isAdditional(fd.getBody(), "result-read:0")
17111697
}
17121698

17131699
private predicate funcDefStep(PreControlFlowNode n1, PreControlFlowNode n2) {
@@ -1737,12 +1723,26 @@ module CfgImpl {
17371723
)
17381724
)
17391725
or
1740-
// result-read:j → result-read:(j+1); the last result-read node steps to
1741-
// the normal exit node via the `callableExitStep` hook.
1726+
// result-read:j → result-read:(j+1)
17421727
exists(int j | exists(fd.getResultVar(j + 1)) |
17431728
n1.isAdditional(fd.getBody(), "result-read:" + j.toString()) and
17441729
n2.isAdditional(fd.getBody(), "result-read:" + (j + 1).toString())
17451730
)
1731+
or
1732+
// Normal fall-through enters the result-read epilogue when there are
1733+
// named results but no deferred calls.
1734+
not funcHasDefer(fd) and
1735+
exists(fd.getResultVar(0)) and
1736+
n1.isAfter(getLastRankedChild(fd.getBody())) and
1737+
n2.isAdditional(fd.getBody(), "result-read:0")
1738+
or
1739+
// The completed result-read epilogue reaches `After(body)`.
1740+
exists(int j |
1741+
exists(fd.getResultVar(j)) and
1742+
not exists(fd.getResultVar(j + 1)) and
1743+
n1.isAdditional(fd.getBody(), "result-read:" + j.toString()) and
1744+
n2.isAfter(fd.getBody())
1745+
)
17461746
)
17471747
}
17481748
}
@@ -1766,15 +1766,6 @@ module CfgImpl {
17661766
)
17671767
}
17681768

1769-
predicate overridesCallableEndAbruptCompletion(Ast::Callable c, AbruptCompletion completion) {
1770-
exists(c.(Go::FuncDef).getResultVar(0)) and
1771-
completion.getSuccessorType() instanceof ReturnSuccessor
1772-
}
1773-
1774-
predicate callableExitStep(PreControlFlowNode n, Ast::Callable c, boolean normal) {
1775-
Input1::callableExitStep(n, c, normal)
1776-
}
1777-
17781769
predicate overridesDefaultControlFlow(Ast::AstNode ast) {
17791770
Input1::overridesDefaultControlFlow(ast)
17801771
}
@@ -1804,28 +1795,18 @@ module CfgImpl {
18041795
Input1::endAbruptCompletion(ast, n, c)
18051796
}
18061797

1807-
predicate overridesCallableEndAbruptCompletion(Ast::Callable c, AbruptCompletion completion) {
1808-
Input1::overridesCallableEndAbruptCompletion(c, completion)
1809-
}
1810-
18111798
predicate overridesAbruptCompletionEdge(
18121799
PreControlFlowNode source, PreControlFlowNode target, AbruptCompletion completion
18131800
) {
18141801
Input1::overridesAbruptCompletionEdge(source, target, completion)
18151802
}
18161803

1817-
predicate callableExitStep(PreControlFlowNode n, Ast::Callable c, boolean normal) {
1818-
Input1::callableExitStep(n, c, normal)
1819-
}
1820-
1821-
predicate deferExitStep(
1804+
predicate additionalSuccessor(
18221805
PreControlFlowNode n1, PreControlFlowNode n2, SuccessorType successorType
18231806
) {
1824-
Input1::deferExitStep(n1, n2, successorType)
1807+
Input1::additionalSuccessor(n1, n2, successorType)
18251808
}
18261809

1827-
predicate overridesCallableBodyExit(Ast::Callable c) { Input1::overridesCallableBodyExit(c) }
1828-
18291810
predicate overridesDefaultControlFlow(Ast::AstNode ast) {
18301811
Input1::overridesDefaultControlFlow(ast)
18311812
}

go/ql/test/experimental/InconsistentCode/CONSISTENCY/CfgConsistency.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ consistencyOverview
22
| multipleSuccessors | 4 |
33
| selfLoop | 1 |
44
multipleSuccessors
5-
| DeferInLoop.go:5:36:14:1 | After block statement | successor | DeferInLoop.go:5:1:14:1 | Normal Exit |
6-
| DeferInLoop.go:5:36:14:1 | After block statement | successor | DeferInLoop.go:8:9:8:20 | defer-invoke call to Close |
7-
| DeferInLoop.go:8:9:8:20 | defer-invoke call to Close | successor | DeferInLoop.go:5:1:14:1 | Normal Exit |
5+
| DeferInLoop.go:6:2:13:2 | After range statement | successor | DeferInLoop.go:5:36:14:1 | After block statement |
6+
| DeferInLoop.go:6:2:13:2 | After range statement | successor | DeferInLoop.go:8:9:8:20 | defer-invoke call to Close |
7+
| DeferInLoop.go:8:9:8:20 | defer-invoke call to Close | successor | DeferInLoop.go:5:36:14:1 | After block statement |
88
| DeferInLoop.go:8:9:8:20 | defer-invoke call to Close | successor | DeferInLoop.go:8:9:8:20 | defer-invoke call to Close |
99
selfLoop
1010
| DeferInLoop.go:8:9:8:20 | defer-invoke call to Close | successor |

go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CONSISTENCY/CfgConsistency.expected

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ multipleSuccessors
99
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:18:2:19:10 | comm clause |
1010
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:20:2:24:10 | comm clause |
1111
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:25:2:25:18 | comm clause |
12-
| stmts7.go:57:38:62:1 | After block statement | successor | stmts7.go:57:1:62:1 | Normal Exit |
13-
| stmts7.go:57:38:62:1 | After block statement | successor | stmts7.go:59:9:59:22 | defer-invoke call to recoverPanic |
14-
| stmts7.go:64:31:68:1 | After block statement | successor | stmts7.go:64:1:68:1 | Normal Exit |
15-
| stmts7.go:64:31:68:1 | After block statement | successor | stmts7.go:66:9:66:22 | defer-invoke call to recoverPanic |
16-
| stmts7.go:66:9:66:22 | defer-invoke call to recoverPanic | successor | stmts7.go:64:1:68:1 | Normal Exit |
12+
| stmts7.go:61:2:61:20 | After expression statement | successor | stmts7.go:57:38:62:1 | After block statement |
13+
| stmts7.go:61:2:61:20 | After expression statement | successor | stmts7.go:59:9:59:22 | defer-invoke call to recoverPanic |
14+
| stmts7.go:65:2:67:2 | After for statement | successor | stmts7.go:64:31:68:1 | After block statement |
15+
| stmts7.go:65:2:67:2 | After for statement | successor | stmts7.go:66:9:66:22 | defer-invoke call to recoverPanic |
16+
| stmts7.go:66:9:66:22 | defer-invoke call to recoverPanic | successor | stmts7.go:64:31:68:1 | After block statement |
1717
| stmts7.go:66:9:66:22 | defer-invoke call to recoverPanic | successor | stmts7.go:66:9:66:22 | defer-invoke call to recoverPanic |
18-
| stmts7.go:70:31:77:1 | After block statement | successor | stmts7.go:70:1:77:1 | Normal Exit |
19-
| stmts7.go:70:31:77:1 | After block statement | successor | stmts7.go:74:8:74:21 | defer-invoke call to recoverPanic |
18+
| stmts7.go:75:1:76:20 | After labeled statement | successor | stmts7.go:70:31:77:1 | After block statement |
19+
| stmts7.go:75:1:76:20 | After labeled statement | successor | stmts7.go:74:8:74:21 | defer-invoke call to recoverPanic |
2020
| stmts.go:50:2:59:2 | select statement | successor | stmts.go:51:2:52:31 | comm clause |
2121
| stmts.go:50:2:59:2 | select statement | successor | stmts.go:53:2:55:16 | comm clause |
2222
| stmts.go:50:2:59:2 | select statement | successor | stmts.go:56:2:57:15 | comm clause |

0 commit comments

Comments
 (0)