Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 47a8586

Browse files
authored
Merge pull request #239 from smowton/smowton/feature/find-noreturn-user-functions
Switch from using mustPanic to mayReturnNormally to construct a call-expression's CFG
2 parents d8ff2d1 + 6e5ee47 commit 47a8586

9 files changed

Lines changed: 143 additions & 17 deletions

File tree

ql/src/semmle/go/Scopes.qll

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,26 @@ class Function extends ValueEntity, @functionobject {
347347
this.(DeclaredFunction).getFuncDecl() = result.getACallee()
348348
}
349349

350+
/** Gets the declaration of this function, if any. */
351+
FuncDecl getFuncDecl() { none() }
352+
350353
/** Holds if this function has no observable side effects. */
351354
predicate mayHaveSideEffects() { none() }
352355

356+
/**
357+
* Holds if this function may return without panicking, exiting the process, or looping forever.
358+
*
359+
* This predicate is an over-approximation: it may hold for functions that can never
360+
* return normally, but it never fails to hold for functions that can.
361+
*
362+
* Note this is declared here and not in `DeclaredFunction` so that library models can override this
363+
* by extending `Function` rather than having to remember to extend `DeclaredFunction`.
364+
*/
365+
predicate mayReturnNormally() {
366+
not mustPanic() and
367+
(ControlFlow::mayReturnNormally(getFuncDecl()) or not exists(getBody()))
368+
}
369+
353370
/**
354371
* Holds if calling this function may cause a runtime panic.
355372
*
@@ -493,8 +510,7 @@ class Method extends Function {
493510

494511
/** A declared function. */
495512
class DeclaredFunction extends Function, DeclaredEntity, @declfunctionobject {
496-
/** Gets the declaration of this function. */
497-
FuncDecl getFuncDecl() { result.getNameExpr() = this.getDeclaration() }
513+
override FuncDecl getFuncDecl() { result.getNameExpr() = this.getDeclaration() }
498514

499515
override BlockStmt getBody() { result = getFuncDecl().getBody() }
500516

ql/src/semmle/go/controlflow/ControlFlowGraph.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,14 @@ module ControlFlow {
259259
* Gets the exit node of function or file `root`.
260260
*/
261261
Node exitNode(Root root) { result = MkExitNode(root) }
262+
263+
/**
264+
* Holds if the function `f` may return without panicking, exiting the process, or looping forever.
265+
*
266+
* This is defined conservatively, and so may also hold of a function that in fact
267+
* cannot return normally, but never fails to hold of a function that can return normally.
268+
*/
269+
predicate mayReturnNormally(FuncDecl f) { CFG::mayReturnNormally(f.getBody()) }
262270
}
263271

264272
class Write = ControlFlow::WriteNode;

ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ module CFG {
918918
}
919919

920920
override Completion getCompletion() {
921-
not getTarget().mustPanic() and
921+
(not exists(getTarget()) or getTarget().mayReturnNormally()) and
922922
result = Done()
923923
or
924924
(not exists(getTarget()) or getTarget().mayPanic()) and
@@ -1924,6 +1924,17 @@ module CFG {
19241924
)
19251925
}
19261926

1927+
/**
1928+
* Holds if the function `f` may return without panicking, exiting the process, or looping forever.
1929+
*
1930+
* This is defined conservatively, and so may also hold of a function that in fact
1931+
* cannot return normally, but never fails to hold of a function that can return normally.
1932+
*/
1933+
cached
1934+
predicate mayReturnNormally(ControlFlowTree root) {
1935+
exists(ControlFlow::Node last, Completion cmpl | lastNode(root, last, cmpl) and cmpl != Panic())
1936+
}
1937+
19271938
/** Gets a successor of `nd`, that is, a node that is executed after `nd`. */
19281939
cached
19291940
ControlFlow::Node succ(ControlFlow::Node nd) { any(ControlFlowTree tree).succ(nd, result) }

ql/src/semmle/go/frameworks/Stdlib.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,13 @@ module OS {
453453
inp.isParameter(0) and outp.isResult()
454454
}
455455
}
456+
457+
/** The `os.Exit` function, which ends the process. */
458+
private class Exit extends Function {
459+
Exit() { hasQualifiedName("os", "Exit") }
460+
461+
override predicate mayReturnNormally() { none() }
462+
}
456463
}
457464

458465
/** Provides models of commonly used functions in the `path` package. */
@@ -786,6 +793,13 @@ module Log {
786793

787794
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
788795
}
796+
797+
/** A fatal log function, which calls `os.Exit`. */
798+
private class FatalLogFunction extends Function {
799+
FatalLogFunction() { exists(string fn | fn.matches("Fatal%") | hasQualifiedName("log", fn)) }
800+
801+
override predicate mayReturnNormally() { none() }
802+
}
789803
}
790804

791805
/** Provides models of some functions in the `encoding/json` package. */

ql/src/semmle/go/frameworks/Testing.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,13 @@ module TestFile {
8383
}
8484
}
8585
}
86+
87+
/** Provides classes modelling Ginkgo. */
88+
module Ginkgo {
89+
/** The Ginkgo `Fail` function, which always panics. */
90+
private class FailFunction extends Function {
91+
FailFunction() { hasQualifiedName("github.com/onsi/ginkgo", "Fail") }
92+
93+
override predicate mustPanic() { any() }
94+
}
95+
}

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

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,45 @@
655655
| main.go:84:11:84:12 | 19 | main.go:84:9:84:12 | ...+... |
656656
| main.go:84:15:84:15 | x | main.go:84:2:84:2 | assignment to x |
657657
| main.go:85:2:85:7 | return statement | main.go:82:18:82:18 | implicit read of a |
658+
| noretfunctions.go:0:0:0:0 | entry | noretfunctions.go:3:1:6:1 | skip |
659+
| noretfunctions.go:3:1:6:1 | skip | noretfunctions.go:8:6:8:12 | skip |
660+
| noretfunctions.go:8:1:8:1 | entry | noretfunctions.go:9:2:9:8 | selection of Exit |
661+
| noretfunctions.go:8:1:10:1 | function declaration | noretfunctions.go:12:6:12:11 | skip |
662+
| noretfunctions.go:8:6:8:12 | skip | noretfunctions.go:8:1:10:1 | function declaration |
663+
| noretfunctions.go:9:2:9:8 | selection of Exit | noretfunctions.go:9:10:9:10 | 1 |
664+
| noretfunctions.go:9:2:9:11 | call to Exit | noretfunctions.go:10:1:10:1 | exit |
665+
| noretfunctions.go:9:10:9:10 | 1 | noretfunctions.go:9:2:9:11 | call to Exit |
666+
| noretfunctions.go:12:1:12:1 | entry | noretfunctions.go:12:13:12:13 | argument corresponding to x |
667+
| noretfunctions.go:12:1:16:1 | function declaration | noretfunctions.go:18:6:18:12 | skip |
668+
| noretfunctions.go:12:6:12:11 | skip | noretfunctions.go:12:1:16:1 | function declaration |
669+
| noretfunctions.go:12:13:12:13 | argument corresponding to x | noretfunctions.go:12:13:12:13 | initialization of x |
670+
| noretfunctions.go:12:13:12:13 | initialization of x | noretfunctions.go:13:5:13:5 | x |
671+
| noretfunctions.go:13:5:13:5 | x | noretfunctions.go:13:10:13:10 | 0 |
672+
| noretfunctions.go:13:5:13:10 | ...!=... | noretfunctions.go:13:10:13:10 | ...!=... is false |
673+
| noretfunctions.go:13:5:13:10 | ...!=... | noretfunctions.go:13:10:13:10 | ...!=... is true |
674+
| noretfunctions.go:13:5:13:10 | ...!=... | noretfunctions.go:16:1:16:1 | exit |
675+
| noretfunctions.go:13:10:13:10 | 0 | noretfunctions.go:13:5:13:10 | ...!=... |
676+
| noretfunctions.go:13:10:13:10 | ...!=... is false | noretfunctions.go:16:1:16:1 | exit |
677+
| noretfunctions.go:13:10:13:10 | ...!=... is true | noretfunctions.go:14:3:14:9 | selection of Exit |
678+
| noretfunctions.go:14:3:14:9 | selection of Exit | noretfunctions.go:14:11:14:11 | x |
679+
| noretfunctions.go:14:3:14:12 | call to Exit | noretfunctions.go:16:1:16:1 | exit |
680+
| noretfunctions.go:14:11:14:11 | x | noretfunctions.go:14:3:14:12 | call to Exit |
681+
| noretfunctions.go:18:1:18:1 | entry | noretfunctions.go:18:16:18:17 | skip |
682+
| noretfunctions.go:18:1:18:17 | function declaration | noretfunctions.go:20:6:20:22 | skip |
683+
| noretfunctions.go:18:6:18:12 | skip | noretfunctions.go:18:1:18:17 | function declaration |
684+
| noretfunctions.go:18:16:18:17 | skip | noretfunctions.go:18:17:18:17 | exit |
685+
| noretfunctions.go:20:1:20:1 | entry | noretfunctions.go:21:2:21:10 | selection of Fatal |
686+
| noretfunctions.go:20:1:22:1 | function declaration | noretfunctions.go:24:6:24:23 | skip |
687+
| noretfunctions.go:20:6:20:22 | skip | noretfunctions.go:20:1:22:1 | function declaration |
688+
| noretfunctions.go:21:2:21:10 | selection of Fatal | noretfunctions.go:21:12:21:18 | "Oh no" |
689+
| noretfunctions.go:21:2:21:19 | call to Fatal | noretfunctions.go:22:1:22:1 | exit |
690+
| noretfunctions.go:21:12:21:18 | "Oh no" | noretfunctions.go:21:2:21:19 | call to Fatal |
691+
| noretfunctions.go:24:1:24:1 | entry | noretfunctions.go:25:2:25:11 | selection of Fatalf |
692+
| noretfunctions.go:24:1:26:1 | function declaration | noretfunctions.go:0:0:0:0 | exit |
693+
| noretfunctions.go:24:6:24:23 | skip | noretfunctions.go:24:1:26:1 | function declaration |
694+
| noretfunctions.go:25:2:25:11 | selection of Fatalf | noretfunctions.go:25:13:25:30 | "It's as I feared" |
695+
| noretfunctions.go:25:2:25:31 | call to Fatalf | noretfunctions.go:26:1:26:1 | exit |
696+
| noretfunctions.go:25:13:25:30 | "It's as I feared" | noretfunctions.go:25:2:25:31 | call to Fatalf |
658697
| stmts2.go:0:0:0:0 | entry | stmts2.go:3:6:3:11 | skip |
659698
| stmts2.go:3:1:3:1 | entry | stmts2.go:4:2:4:2 | skip |
660699
| stmts2.go:3:1:7:1 | function declaration | stmts2.go:9:6:9:11 | skip |
@@ -1063,7 +1102,6 @@
10631102
| stmts.go:77:17:77:22 | ...-... | stmts.go:79:3:79:7 | test5 |
10641103
| stmts.go:77:21:77:22 | 19 | stmts.go:77:17:77:22 | ...-... |
10651104
| stmts.go:79:3:79:7 | test5 | stmts.go:79:9:79:13 | false |
1066-
| stmts.go:79:3:79:14 | call to test5 | stmts.go:82:9:82:9 | x |
10671105
| stmts.go:79:3:79:14 | call to test5 | stmts.go:107:1:107:1 | exit |
10681106
| stmts.go:79:9:79:13 | false | stmts.go:79:3:79:14 | call to test5 |
10691107
| stmts.go:82:9:82:9 | x | stmts.go:83:7:83:7 | 1 |
@@ -1079,38 +1117,28 @@
10791117
| stmts.go:84:10:84:10 | case 3 | stmts.go:85:3:85:7 | test5 |
10801118
| stmts.go:84:10:84:10 | case 3 | stmts.go:88:9:88:9 | x |
10811119
| stmts.go:85:3:85:7 | test5 | stmts.go:85:9:85:12 | true |
1082-
| stmts.go:85:3:85:13 | call to test5 | stmts.go:88:9:88:9 | x |
1083-
| stmts.go:85:3:85:13 | call to test5 | stmts.go:107:1:107:1 | exit |
10841120
| stmts.go:85:9:85:12 | true | stmts.go:85:3:85:13 | call to test5 |
10851121
| stmts.go:88:9:88:9 | x | stmts.go:89:7:89:7 | 1 |
10861122
| stmts.go:88:9:88:9 | x | stmts.go:96:9:96:9 | x |
10871123
| stmts.go:89:7:89:7 | 1 | stmts.go:89:7:89:7 | case 1 |
10881124
| stmts.go:89:7:89:7 | case 1 | stmts.go:90:3:90:7 | test5 |
10891125
| stmts.go:89:7:89:7 | case 1 | stmts.go:92:7:92:11 | ...-... |
10901126
| stmts.go:90:3:90:7 | test5 | stmts.go:90:9:90:13 | false |
1091-
| stmts.go:90:3:90:14 | call to test5 | stmts.go:91:3:91:13 | skip |
1092-
| stmts.go:90:3:90:14 | call to test5 | stmts.go:107:1:107:1 | exit |
10931127
| stmts.go:90:9:90:13 | false | stmts.go:90:3:90:14 | call to test5 |
10941128
| stmts.go:91:3:91:13 | skip | stmts.go:93:3:93:7 | test5 |
10951129
| stmts.go:92:7:92:11 | ...-... | stmts.go:92:7:92:11 | case ...-... |
10961130
| stmts.go:92:7:92:11 | case ...-... | stmts.go:93:3:93:7 | test5 |
10971131
| stmts.go:92:7:92:11 | case ...-... | stmts.go:96:9:96:9 | x |
10981132
| stmts.go:93:3:93:7 | test5 | stmts.go:93:9:93:12 | true |
1099-
| stmts.go:93:3:93:13 | call to test5 | stmts.go:96:9:96:9 | x |
1100-
| stmts.go:93:3:93:13 | call to test5 | stmts.go:107:1:107:1 | exit |
11011133
| stmts.go:93:9:93:12 | true | stmts.go:93:3:93:13 | call to test5 |
11021134
| stmts.go:96:9:96:9 | x | stmts.go:98:7:98:7 | 2 |
11031135
| stmts.go:97:2:97:9 | skip | stmts.go:102:2:102:2 | true |
11041136
| stmts.go:98:7:98:7 | 2 | stmts.go:98:7:98:7 | case 2 |
11051137
| stmts.go:98:7:98:7 | case 2 | stmts.go:97:2:97:9 | skip |
11061138
| stmts.go:98:7:98:7 | case 2 | stmts.go:99:3:99:7 | test5 |
11071139
| stmts.go:99:3:99:7 | test5 | stmts.go:99:9:99:12 | true |
1108-
| stmts.go:99:3:99:13 | call to test5 | stmts.go:102:2:102:2 | true |
1109-
| stmts.go:99:3:99:13 | call to test5 | stmts.go:107:1:107:1 | exit |
11101140
| stmts.go:99:9:99:12 | true | stmts.go:99:3:99:13 | call to test5 |
11111141
| stmts.go:102:2:102:2 | true | stmts.go:105:7:105:10 | true |
1112-
| stmts.go:104:3:104:7 | skip | stmts.go:107:1:107:1 | exit |
1113-
| stmts.go:105:2:105:11 | skip | stmts.go:107:1:107:1 | exit |
11141142
| stmts.go:105:7:105:10 | case true | stmts.go:105:10:105:10 | true is false |
11151143
| stmts.go:105:7:105:10 | case true | stmts.go:105:10:105:10 | true is true |
11161144
| stmts.go:105:7:105:10 | true | stmts.go:105:7:105:10 | case true |
@@ -1137,12 +1165,9 @@
11371165
| stmts.go:114:7:114:13 | case float32 | stmts.go:115:3:115:7 | test5 |
11381166
| stmts.go:114:7:114:13 | case float32 | stmts.go:119:9:119:9 | skip |
11391167
| stmts.go:115:3:115:7 | test5 | stmts.go:115:9:115:12 | true |
1140-
| stmts.go:115:3:115:13 | call to test5 | stmts.go:116:3:116:7 | test5 |
11411168
| stmts.go:115:3:115:13 | call to test5 | stmts.go:123:1:123:1 | exit |
11421169
| stmts.go:115:9:115:12 | true | stmts.go:115:3:115:13 | call to test5 |
11431170
| stmts.go:116:3:116:7 | test5 | stmts.go:116:9:116:13 | false |
1144-
| stmts.go:116:3:116:14 | call to test5 | stmts.go:119:9:119:9 | skip |
1145-
| stmts.go:116:3:116:14 | call to test5 | stmts.go:123:1:123:1 | exit |
11461171
| stmts.go:116:9:116:13 | false | stmts.go:116:3:116:14 | call to test5 |
11471172
| stmts.go:119:9:119:9 | assignment to y | stmts.go:119:17:119:17 | y |
11481173
| stmts.go:119:9:119:9 | skip | stmts.go:119:14:119:14 | x |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
| file://:0:0:0:0 | Exit | package os |
2+
| file://:0:0:0:0 | Fatal | package log |
3+
| file://:0:0:0:0 | Fatalf | package log |
4+
| file://:0:0:0:0 | Fatalln | package log |
5+
| noretfunctions.go:8:6:8:12 | isNoRet | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
6+
| noretfunctions.go:20:6:20:22 | noRetUsesLogFatal | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
7+
| noretfunctions.go:24:6:24:23 | noRetUsesLogFatalf | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
8+
| stmts7.go:10:6:10:15 | canRecover | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
9+
| stmts.go:8:6:8:10 | test5 | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
10+
| stmts.go:44:6:44:10 | test6 | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
11+
| stmts.go:110:6:110:10 | test9 | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import go
2+
3+
from Function f
4+
where not f.mayReturnNormally()
5+
select f, f.getPackage()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
)
7+
8+
func isNoRet() {
9+
os.Exit(1)
10+
}
11+
12+
func mayRet(x int) {
13+
if x != 0 {
14+
os.Exit(x)
15+
}
16+
}
17+
18+
func doesRet() {}
19+
20+
func noRetUsesLogFatal() {
21+
log.Fatal("Oh no")
22+
}
23+
24+
func noRetUsesLogFatalf() {
25+
log.Fatalf("It's as I feared")
26+
}

0 commit comments

Comments
 (0)