From 42ebe560235ee06d1017c806f821da0197be948f Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 3 Jun 2026 14:29:10 +0100 Subject: [PATCH 1/2] Make all lines in logging tests reachable --- .../semmle/go/concepts/LoggerCall/logrus.go | 16 ++-- .../semmle/go/concepts/LoggerCall/main.go | 2 +- .../semmle/go/concepts/LoggerCall/stdlib.go | 82 +++++++++++++------ 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/logrus.go b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/logrus.go index bdb57aae2e1b..56677fff99b8 100644 --- a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/logrus.go +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/logrus.go @@ -13,7 +13,7 @@ func logSomething(entry *logrus.Entry) { entry.Traceln(text) // $ logger=text } -func logrusCalls() { +func logrusCalls(selector int) { err := errors.New("Error") var fields logrus.Fields = nil var fn logrus.LogFunction = nil @@ -27,11 +27,15 @@ func logrusCalls() { tmp = logrus.WithFields(fields) // $ logger=fields logSomething(tmp) - logrus.Error(text) // $ logger=text - logrus.Fatalf(fmt, text) // $ logger=fmt logger=text - logrus.Panicln(text) // $ logger=text - logrus.Infof(fmt, text) // $ logger=fmt logger=text - logrus.FatalFn(fn) // $ logger=fn + logrus.Error(text) // $ logger=text + logrus.Infof(fmt, text) // $ logger=fmt logger=text + if selector == 0 { + logrus.Fatalf(fmt, text) // $ logger=fmt logger=text + } else if selector == 1 { + logrus.Panicln(text) // $ logger=text + } else if selector == 2 { + logrus.FatalFn(fn) // $ logger=fn + } // components corresponding to the format specifier "%T" are not considered vulnerable logrus.Infof("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go index ae3699c19661..fd393f0d2048 100644 --- a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go @@ -8,6 +8,6 @@ var v []byte func main() { glogTest(len(v)) - stdlib() + stdlib(len(v)) slogTest() } diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/stdlib.go b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/stdlib.go index 6fbf3c43fd35..e77e83a2ac5b 100644 --- a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/stdlib.go +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/stdlib.go @@ -4,37 +4,69 @@ import ( "log" ) -func stdlib() { +func stdlib(selector int) { var logger log.Logger logger.SetPrefix("prefix: ") - logger.Fatal(text) // $ logger=text - logger.Fatalf(fmt, text) // $ logger=fmt logger=text - logger.Fatalln(text) // $ logger=text - logger.Panic(text) // $ logger=text - logger.Panicf(fmt, text) // $ logger=fmt logger=text - logger.Panicln(text) // $ logger=text - logger.Print(text) // $ logger=text - logger.Printf(fmt, text) // $ logger=fmt logger=text - logger.Println(text) // $ logger=text + switch selector { + case 0: + logger.Fatal(text) // $ logger=text + case 1: + logger.Fatalf(fmt, text) // $ logger=fmt logger=text + case 2: + logger.Fatalln(text) // $ logger=text + case 3: + logger.Panic(text) // $ logger=text + case 4: + logger.Panicf(fmt, text) // $ logger=fmt logger=text + case 5: + logger.Panicln(text) // $ logger=text + case 6: + logger.Print(text) // $ logger=text + case 7: + logger.Printf(fmt, text) // $ logger=fmt logger=text + case 8: + logger.Println(text) // $ logger=text + } // components corresponding to the format specifier "%T" are not considered vulnerable - logger.Fatalf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v - logger.Panicf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v - logger.Printf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + switch selector { + case 9: + logger.Fatalf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + case 10: + logger.Panicf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + case 11: + logger.Printf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + } log.SetPrefix("prefix: ") - log.Fatal(text) // $ logger=text - log.Fatalf(fmt, text) // $ logger=fmt logger=text - log.Fatalln(text) // $ logger=text - log.Panic(text) // $ logger=text - log.Panicf(fmt, text) // $ logger=fmt logger=text - log.Panicln(text) // $ logger=text - log.Print(text) // $ logger=text - log.Printf(fmt, text) // $ logger=fmt logger=text - log.Println(text) // $ logger=text + switch selector { + case 12: + log.Fatal(text) // $ logger=text + case 13: + log.Fatalf(fmt, text) // $ logger=fmt logger=text + case 14: + log.Fatalln(text) // $ logger=text + case 15: + log.Panic(text) // $ logger=text + case 16: + log.Panicf(fmt, text) // $ logger=fmt logger=text + case 17: + log.Panicln(text) // $ logger=text + case 18: + log.Print(text) // $ logger=text + case 19: + log.Printf(fmt, text) // $ logger=fmt logger=text + case 20: + log.Println(text) // $ logger=text + } // components corresponding to the format specifier "%T" are not considered vulnerable - log.Fatalf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v - log.Panicf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v - log.Printf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + switch selector { + case 21: + log.Fatalf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + case 22: + log.Panicf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + case 23: + log.Printf("%s: found type %T", text, v) // $ logger="%s: found type %T" logger=text type-logger=v + } } From 07cf89568f4ed7d7075b65dd3851f7e307b66c30 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 16 Jun 2026 19:36:56 +0100 Subject: [PATCH 2/2] Test CFG for function epilogue (read-result nodes and calls to defered functions) --- .../ControlFlowNode_getASuccessor.expected | 259 ++++++++++++++++++ .../ControlFlowGraph/NoretFunctions.expected | 1 + .../controlflow/ControlFlowGraph/epilogues.go | 118 ++++++++ 3 files changed, 378 insertions(+) create mode 100644 go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/epilogues.go diff --git a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected index f14d1319795f..3768d015167e 100644 --- a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected +++ b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected @@ -34,6 +34,265 @@ | DuplicateSwitchCase.go:16:1:16:14 | function declaration | DuplicateSwitchCase.go:0:0:0:0 | exit | | DuplicateSwitchCase.go:16:6:16:9 | skip | DuplicateSwitchCase.go:16:1:16:14 | function declaration | | DuplicateSwitchCase.go:16:13:16:14 | skip | DuplicateSwitchCase.go:16:1:16:14 | exit | +| epilogues.go:0:0:0:0 | entry | epilogues.go:3:1:3:12 | skip | +| epilogues.go:3:1:3:12 | skip | epilogues.go:8:1:10:1 | skip | +| epilogues.go:8:1:10:1 | skip | epilogues.go:12:21:12:23 | skip | +| epilogues.go:12:1:14:1 | entry | epilogues.go:12:7:12:7 | argument corresponding to l | +| epilogues.go:12:1:14:1 | function declaration | epilogues.go:16:20:16:27 | skip | +| epilogues.go:12:7:12:7 | argument corresponding to l | epilogues.go:12:7:12:7 | initialization of l | +| epilogues.go:12:7:12:7 | initialization of l | epilogues.go:12:25:12:27 | argument corresponding to msg | +| epilogues.go:12:21:12:23 | skip | epilogues.go:12:1:14:1 | function declaration | +| epilogues.go:12:25:12:27 | argument corresponding to msg | epilogues.go:12:25:12:27 | initialization of msg | +| epilogues.go:12:25:12:27 | initialization of msg | epilogues.go:12:37:12:40 | argument corresponding to code | +| epilogues.go:12:37:12:40 | argument corresponding to code | epilogues.go:12:37:12:40 | initialization of code | +| epilogues.go:12:37:12:40 | initialization of code | epilogues.go:13:2:13:12 | selection of Println | +| epilogues.go:13:2:13:12 | selection of Println | epilogues.go:13:14:13:14 | l | +| epilogues.go:13:2:13:33 | call to Println | epilogues.go:12:1:14:1 | exit | +| epilogues.go:13:14:13:14 | implicit dereference | epilogues.go:12:1:14:1 | exit | +| epilogues.go:13:14:13:14 | implicit dereference | epilogues.go:13:14:13:21 | selection of prefix | +| epilogues.go:13:14:13:14 | l | epilogues.go:13:14:13:14 | implicit dereference | +| epilogues.go:13:14:13:21 | selection of prefix | epilogues.go:13:24:13:26 | msg | +| epilogues.go:13:24:13:26 | msg | epilogues.go:13:29:13:32 | code | +| epilogues.go:13:29:13:32 | code | epilogues.go:13:2:13:33 | call to Println | +| epilogues.go:16:1:18:1 | entry | epilogues.go:16:7:16:7 | argument corresponding to l | +| epilogues.go:16:1:18:1 | function declaration | epilogues.go:23:6:23:15 | skip | +| epilogues.go:16:7:16:7 | argument corresponding to l | epilogues.go:16:7:16:7 | initialization of l | +| epilogues.go:16:7:16:7 | initialization of l | epilogues.go:16:29:16:31 | argument corresponding to msg | +| epilogues.go:16:20:16:27 | skip | epilogues.go:16:1:18:1 | function declaration | +| epilogues.go:16:29:16:31 | argument corresponding to msg | epilogues.go:16:29:16:31 | initialization of msg | +| epilogues.go:16:29:16:31 | initialization of msg | epilogues.go:17:2:17:12 | selection of Println | +| epilogues.go:17:2:17:12 | selection of Println | epilogues.go:17:14:17:14 | l | +| epilogues.go:17:2:17:27 | call to Println | epilogues.go:16:1:18:1 | exit | +| epilogues.go:17:14:17:14 | l | epilogues.go:17:14:17:21 | selection of prefix | +| epilogues.go:17:14:17:21 | selection of prefix | epilogues.go:17:24:17:26 | msg | +| epilogues.go:17:24:17:26 | msg | epilogues.go:17:2:17:27 | call to Println | +| epilogues.go:23:1:27:1 | entry | epilogues.go:24:5:24:5 | skip | +| epilogues.go:23:1:27:1 | function declaration | epilogues.go:31:6:31:13 | skip | +| epilogues.go:23:6:23:15 | skip | epilogues.go:23:1:27:1 | function declaration | +| epilogues.go:24:5:24:5 | assignment to r | epilogues.go:24:21:24:21 | r | +| epilogues.go:24:5:24:5 | skip | epilogues.go:24:10:24:16 | recover | +| epilogues.go:24:10:24:16 | recover | epilogues.go:24:10:24:18 | call to recover | +| epilogues.go:24:10:24:18 | call to recover | epilogues.go:24:5:24:5 | assignment to r | +| epilogues.go:24:21:24:21 | r | epilogues.go:24:26:24:28 | nil | +| epilogues.go:24:21:24:28 | ...!=... | epilogues.go:23:1:27:1 | exit | +| epilogues.go:24:21:24:28 | ...!=... | epilogues.go:24:21:24:28 | ...!=... is false | +| epilogues.go:24:21:24:28 | ...!=... | epilogues.go:24:21:24:28 | ...!=... is true | +| epilogues.go:24:21:24:28 | ...!=... is false | epilogues.go:23:1:27:1 | exit | +| epilogues.go:24:21:24:28 | ...!=... is true | epilogues.go:25:3:25:13 | selection of Println | +| epilogues.go:24:26:24:28 | nil | epilogues.go:24:21:24:28 | ...!=... | +| epilogues.go:25:3:25:13 | selection of Println | epilogues.go:25:15:25:26 | "recovered:" | +| epilogues.go:25:3:25:30 | call to Println | epilogues.go:23:1:27:1 | exit | +| epilogues.go:25:15:25:26 | "recovered:" | epilogues.go:25:29:25:29 | r | +| epilogues.go:25:29:25:29 | r | epilogues.go:25:3:25:30 | call to Println | +| epilogues.go:31:1:33:1 | entry | epilogues.go:31:15:31:15 | argument corresponding to x | +| epilogues.go:31:1:33:1 | function declaration | epilogues.go:36:6:36:12 | skip | +| epilogues.go:31:6:31:13 | skip | epilogues.go:31:1:33:1 | function declaration | +| epilogues.go:31:15:31:15 | argument corresponding to x | epilogues.go:31:15:31:15 | initialization of x | +| epilogues.go:31:15:31:15 | initialization of x | epilogues.go:32:9:32:9 | x | +| epilogues.go:32:2:32:13 | return statement | epilogues.go:31:1:33:1 | exit | +| epilogues.go:32:9:32:9 | x | epilogues.go:32:13:32:13 | 2 | +| epilogues.go:32:9:32:13 | ...*... | epilogues.go:32:2:32:13 | return statement | +| epilogues.go:32:13:32:13 | 2 | epilogues.go:32:9:32:13 | ...*... | +| epilogues.go:36:1:38:1 | entry | epilogues.go:37:2:37:12 | selection of Println | +| epilogues.go:36:1:38:1 | function declaration | epilogues.go:42:6:42:18 | skip | +| epilogues.go:36:6:36:12 | skip | epilogues.go:36:1:38:1 | function declaration | +| epilogues.go:37:2:37:12 | selection of Println | epilogues.go:37:14:37:19 | "void" | +| epilogues.go:37:2:37:20 | call to Println | epilogues.go:36:1:38:1 | exit | +| epilogues.go:37:14:37:19 | "void" | epilogues.go:37:2:37:20 | call to Println | +| epilogues.go:42:1:48:1 | entry | epilogues.go:42:20:42:20 | argument corresponding to x | +| epilogues.go:42:1:48:1 | function declaration | epilogues.go:51:6:51:21 | skip | +| epilogues.go:42:6:42:18 | skip | epilogues.go:42:1:48:1 | function declaration | +| epilogues.go:42:20:42:20 | argument corresponding to x | epilogues.go:42:20:42:20 | initialization of x | +| epilogues.go:42:20:42:20 | initialization of x | epilogues.go:42:28:42:33 | zero value for result | +| epilogues.go:42:28:42:33 | implicit read of result | epilogues.go:42:40:42:42 | implicit read of err | +| epilogues.go:42:28:42:33 | initialization of result | epilogues.go:42:40:42:42 | zero value for err | +| epilogues.go:42:28:42:33 | zero value for result | epilogues.go:42:28:42:33 | initialization of result | +| epilogues.go:42:40:42:42 | implicit read of err | epilogues.go:42:1:48:1 | exit | +| epilogues.go:42:40:42:42 | initialization of err | epilogues.go:43:5:43:5 | x | +| epilogues.go:42:40:42:42 | zero value for err | epilogues.go:42:40:42:42 | initialization of err | +| epilogues.go:43:5:43:5 | x | epilogues.go:43:9:43:9 | 0 | +| epilogues.go:43:5:43:9 | ...<... | epilogues.go:43:5:43:9 | ...<... is false | +| epilogues.go:43:5:43:9 | ...<... | epilogues.go:43:5:43:9 | ...<... is true | +| epilogues.go:43:5:43:9 | ...<... is false | epilogues.go:47:9:47:9 | x | +| epilogues.go:43:5:43:9 | ...<... is true | epilogues.go:44:3:44:8 | skip | +| epilogues.go:43:9:43:9 | 0 | epilogues.go:43:5:43:9 | ...<... | +| epilogues.go:44:3:44:8 | assignment to result | epilogues.go:45:3:45:8 | return statement | +| epilogues.go:44:3:44:8 | skip | epilogues.go:44:13:44:13 | x | +| epilogues.go:44:12:44:13 | -... | epilogues.go:44:3:44:8 | assignment to result | +| epilogues.go:44:13:44:13 | x | epilogues.go:44:12:44:13 | -... | +| epilogues.go:45:3:45:8 | return statement | epilogues.go:42:28:42:33 | implicit read of result | +| epilogues.go:47:2:47:14 | return statement | epilogues.go:42:28:42:33 | implicit read of result | +| epilogues.go:47:9:47:9 | implicit write of result | epilogues.go:47:12:47:14 | nil | +| epilogues.go:47:9:47:9 | x | epilogues.go:47:9:47:9 | implicit write of result | +| epilogues.go:47:12:47:14 | implicit write of err | epilogues.go:47:2:47:14 | return statement | +| epilogues.go:47:12:47:14 | nil | epilogues.go:47:12:47:14 | implicit write of err | +| epilogues.go:51:1:54:1 | entry | epilogues.go:51:23:51:23 | argument corresponding to x | +| epilogues.go:51:1:54:1 | function declaration | epilogues.go:59:6:59:25 | skip | +| epilogues.go:51:6:51:21 | skip | epilogues.go:51:1:54:1 | function declaration | +| epilogues.go:51:23:51:23 | argument corresponding to x | epilogues.go:51:23:51:23 | initialization of x | +| epilogues.go:51:23:51:23 | initialization of x | epilogues.go:51:31:51:31 | zero value for n | +| epilogues.go:51:31:51:31 | implicit read of n | epilogues.go:51:1:54:1 | exit | +| epilogues.go:51:31:51:31 | initialization of n | epilogues.go:52:2:52:2 | skip | +| epilogues.go:51:31:51:31 | zero value for n | epilogues.go:51:31:51:31 | initialization of n | +| epilogues.go:52:2:52:2 | assignment to n | epilogues.go:53:2:53:7 | return statement | +| epilogues.go:52:2:52:2 | skip | epilogues.go:52:6:52:6 | x | +| epilogues.go:52:6:52:6 | x | epilogues.go:52:10:52:10 | 1 | +| epilogues.go:52:6:52:10 | ...+... | epilogues.go:52:2:52:2 | assignment to n | +| epilogues.go:52:10:52:10 | 1 | epilogues.go:52:6:52:10 | ...+... | +| epilogues.go:53:2:53:7 | return statement | epilogues.go:51:31:51:31 | implicit read of n | +| epilogues.go:59:1:62:1 | entry | epilogues.go:59:27:59:27 | argument corresponding to l | +| epilogues.go:59:1:62:1 | function declaration | epilogues.go:66:6:66:26 | skip | +| epilogues.go:59:6:59:25 | skip | epilogues.go:59:1:62:1 | function declaration | +| epilogues.go:59:27:59:27 | argument corresponding to l | epilogues.go:59:27:59:27 | initialization of l | +| epilogues.go:59:27:59:27 | initialization of l | epilogues.go:59:41:59:45 | argument corresponding to items | +| epilogues.go:59:41:59:45 | argument corresponding to items | epilogues.go:59:41:59:45 | initialization of items | +| epilogues.go:59:41:59:45 | initialization of items | epilogues.go:60:8:60:8 | l | +| epilogues.go:60:2:60:33 | defer statement | epilogues.go:61:2:61:12 | selection of Println | +| epilogues.go:60:8:60:8 | l | epilogues.go:60:8:60:12 | selection of log | +| epilogues.go:60:8:60:12 | selection of log | epilogues.go:60:14:60:20 | "count" | +| epilogues.go:60:8:60:33 | call to log | epilogues.go:59:1:62:1 | exit | +| epilogues.go:60:14:60:20 | "count" | epilogues.go:60:23:60:25 | len | +| epilogues.go:60:23:60:25 | len | epilogues.go:60:27:60:31 | items | +| epilogues.go:60:23:60:32 | call to len | epilogues.go:60:2:60:33 | defer statement | +| epilogues.go:60:27:60:31 | items | epilogues.go:60:23:60:32 | call to len | +| epilogues.go:61:2:61:12 | selection of Println | epilogues.go:61:14:61:25 | "processing" | +| epilogues.go:61:2:61:38 | call to Println | epilogues.go:60:8:60:33 | call to log | +| epilogues.go:61:14:61:25 | "processing" | epilogues.go:61:28:61:30 | len | +| epilogues.go:61:28:61:30 | len | epilogues.go:61:32:61:36 | items | +| epilogues.go:61:28:61:37 | call to len | epilogues.go:61:2:61:38 | call to Println | +| epilogues.go:61:32:61:36 | items | epilogues.go:61:28:61:37 | call to len | +| epilogues.go:66:1:71:1 | entry | epilogues.go:66:28:66:33 | argument corresponding to prefix | +| epilogues.go:66:1:71:1 | function declaration | epilogues.go:77:6:77:20 | skip | +| epilogues.go:66:6:66:26 | skip | epilogues.go:66:1:71:1 | function declaration | +| epilogues.go:66:28:66:33 | argument corresponding to prefix | epilogues.go:66:28:66:33 | initialization of prefix | +| epilogues.go:66:28:66:33 | initialization of prefix | epilogues.go:67:2:67:2 | skip | +| epilogues.go:67:2:67:2 | assignment to l | epilogues.go:68:8:68:8 | l | +| epilogues.go:67:2:67:2 | skip | epilogues.go:67:7:67:31 | struct literal | +| epilogues.go:67:7:67:31 | struct literal | epilogues.go:67:25:67:30 | prefix | +| epilogues.go:67:17:67:30 | init of key-value pair | epilogues.go:67:2:67:2 | assignment to l | +| epilogues.go:67:25:67:30 | prefix | epilogues.go:67:17:67:30 | init of key-value pair | +| epilogues.go:68:2:68:24 | defer statement | epilogues.go:69:10:69:10 | l | +| epilogues.go:68:8:68:8 | l | epilogues.go:68:8:68:17 | selection of logValue | +| epilogues.go:68:8:68:17 | selection of logValue | epilogues.go:68:19:68:23 | "bye" | +| epilogues.go:68:8:68:24 | call to logValue | epilogues.go:66:1:71:1 | exit | +| epilogues.go:68:19:68:23 | "bye" | epilogues.go:68:2:68:24 | defer statement | +| epilogues.go:69:2:69:25 | defer statement | epilogues.go:70:2:70:12 | selection of Println | +| epilogues.go:69:8:69:15 | selection of log | epilogues.go:69:17:69:21 | "ptr" | +| epilogues.go:69:8:69:25 | call to log | epilogues.go:68:8:68:24 | call to logValue | +| epilogues.go:69:9:69:10 | &... | epilogues.go:69:8:69:15 | selection of log | +| epilogues.go:69:10:69:10 | l | epilogues.go:69:9:69:10 | &... | +| epilogues.go:69:17:69:21 | "ptr" | epilogues.go:69:24:69:24 | 7 | +| epilogues.go:69:24:69:24 | 7 | epilogues.go:69:2:69:25 | defer statement | +| epilogues.go:70:2:70:12 | selection of Println | epilogues.go:70:14:70:19 | "body" | +| epilogues.go:70:2:70:20 | call to Println | epilogues.go:69:8:69:25 | call to log | +| epilogues.go:70:14:70:19 | "body" | epilogues.go:70:2:70:20 | call to Println | +| epilogues.go:77:1:82:1 | entry | epilogues.go:77:22:77:22 | argument corresponding to x | +| epilogues.go:77:1:82:1 | function declaration | epilogues.go:87:6:87:20 | skip | +| epilogues.go:77:6:77:20 | skip | epilogues.go:77:1:82:1 | function declaration | +| epilogues.go:77:22:77:22 | argument corresponding to x | epilogues.go:77:22:77:22 | initialization of x | +| epilogues.go:77:22:77:22 | initialization of x | epilogues.go:78:8:80:2 | function literal | +| epilogues.go:78:2:80:15 | defer statement | epilogues.go:81:2:81:12 | selection of Println | +| epilogues.go:78:8:80:2 | entry | epilogues.go:78:13:78:17 | argument corresponding to label | +| epilogues.go:78:8:80:2 | function literal | epilogues.go:80:4:80:9 | "done" | +| epilogues.go:78:8:80:15 | function call | epilogues.go:77:1:82:1 | exit | +| epilogues.go:78:13:78:17 | argument corresponding to label | epilogues.go:78:13:78:17 | initialization of label | +| epilogues.go:78:13:78:17 | initialization of label | epilogues.go:78:27:78:27 | argument corresponding to n | +| epilogues.go:78:27:78:27 | argument corresponding to n | epilogues.go:78:27:78:27 | initialization of n | +| epilogues.go:78:27:78:27 | initialization of n | epilogues.go:79:3:79:13 | selection of Println | +| epilogues.go:79:3:79:13 | selection of Println | epilogues.go:79:15:79:19 | label | +| epilogues.go:79:3:79:23 | call to Println | epilogues.go:78:8:80:2 | exit | +| epilogues.go:79:15:79:19 | label | epilogues.go:79:22:79:22 | n | +| epilogues.go:79:22:79:22 | n | epilogues.go:79:3:79:23 | call to Println | +| epilogues.go:80:4:80:9 | "done" | epilogues.go:80:12:80:12 | x | +| epilogues.go:80:12:80:12 | x | epilogues.go:80:14:80:14 | 1 | +| epilogues.go:80:12:80:14 | ...+... | epilogues.go:78:2:80:15 | defer statement | +| epilogues.go:80:14:80:14 | 1 | epilogues.go:80:12:80:14 | ...+... | +| epilogues.go:81:2:81:12 | selection of Println | epilogues.go:81:14:81:19 | "body" | +| epilogues.go:81:2:81:23 | call to Println | epilogues.go:78:8:80:15 | function call | +| epilogues.go:81:14:81:19 | "body" | epilogues.go:81:22:81:22 | x | +| epilogues.go:81:22:81:22 | x | epilogues.go:81:2:81:23 | call to Println | +| epilogues.go:87:1:98:1 | entry | epilogues.go:87:22:87:22 | argument corresponding to x | +| epilogues.go:87:1:98:1 | function declaration | epilogues.go:102:6:102:24 | skip | +| epilogues.go:87:6:87:20 | skip | epilogues.go:87:1:98:1 | function declaration | +| epilogues.go:87:22:87:22 | argument corresponding to x | epilogues.go:87:22:87:22 | initialization of x | +| epilogues.go:87:22:87:22 | initialization of x | epilogues.go:87:30:87:35 | zero value for result | +| epilogues.go:87:30:87:35 | implicit read of result | epilogues.go:87:1:98:1 | exit | +| epilogues.go:87:30:87:35 | initialization of result | epilogues.go:88:8:92:2 | function literal | +| epilogues.go:87:30:87:35 | zero value for result | epilogues.go:87:30:87:35 | initialization of result | +| epilogues.go:88:2:92:4 | defer statement | epilogues.go:93:5:93:5 | x | +| epilogues.go:88:8:92:2 | entry | epilogues.go:89:6:89:6 | skip | +| epilogues.go:88:8:92:2 | function literal | epilogues.go:88:2:92:4 | defer statement | +| epilogues.go:88:8:92:4 | function call | epilogues.go:87:1:98:1 | exit | +| epilogues.go:88:8:92:4 | function call | epilogues.go:87:30:87:35 | implicit read of result | +| epilogues.go:89:6:89:6 | assignment to r | epilogues.go:89:22:89:22 | r | +| epilogues.go:89:6:89:6 | skip | epilogues.go:89:11:89:17 | recover | +| epilogues.go:89:11:89:17 | recover | epilogues.go:89:11:89:19 | call to recover | +| epilogues.go:89:11:89:19 | call to recover | epilogues.go:89:6:89:6 | assignment to r | +| epilogues.go:89:22:89:22 | r | epilogues.go:89:27:89:29 | nil | +| epilogues.go:89:22:89:29 | ...!=... | epilogues.go:88:8:92:2 | exit | +| epilogues.go:89:22:89:29 | ...!=... | epilogues.go:89:22:89:29 | ...!=... is false | +| epilogues.go:89:22:89:29 | ...!=... | epilogues.go:89:22:89:29 | ...!=... is true | +| epilogues.go:89:22:89:29 | ...!=... is false | epilogues.go:88:8:92:2 | exit | +| epilogues.go:89:22:89:29 | ...!=... is true | epilogues.go:90:4:90:9 | skip | +| epilogues.go:89:27:89:29 | nil | epilogues.go:89:22:89:29 | ...!=... | +| epilogues.go:90:4:90:9 | assignment to result | epilogues.go:88:8:92:2 | exit | +| epilogues.go:90:4:90:9 | skip | epilogues.go:90:13:90:14 | -... | +| epilogues.go:90:13:90:14 | -... | epilogues.go:90:4:90:9 | assignment to result | +| epilogues.go:93:5:93:5 | x | epilogues.go:93:9:93:9 | 0 | +| epilogues.go:93:5:93:9 | ...<... | epilogues.go:93:5:93:9 | ...<... is false | +| epilogues.go:93:5:93:9 | ...<... | epilogues.go:93:5:93:9 | ...<... is true | +| epilogues.go:93:5:93:9 | ...<... is false | epilogues.go:96:2:96:7 | skip | +| epilogues.go:93:5:93:9 | ...<... is true | epilogues.go:94:3:94:7 | panic | +| epilogues.go:93:9:93:9 | 0 | epilogues.go:93:5:93:9 | ...<... | +| epilogues.go:94:3:94:7 | panic | epilogues.go:94:9:94:13 | "neg" | +| epilogues.go:94:3:94:14 | call to panic | epilogues.go:88:8:92:4 | function call | +| epilogues.go:94:9:94:13 | "neg" | epilogues.go:94:3:94:14 | call to panic | +| epilogues.go:96:2:96:7 | assignment to result | epilogues.go:97:9:97:14 | result | +| epilogues.go:96:2:96:7 | skip | epilogues.go:96:11:96:11 | x | +| epilogues.go:96:11:96:11 | x | epilogues.go:96:15:96:15 | x | +| epilogues.go:96:11:96:15 | ...*... | epilogues.go:96:2:96:7 | assignment to result | +| epilogues.go:96:15:96:15 | x | epilogues.go:96:11:96:15 | ...*... | +| epilogues.go:97:2:97:14 | return statement | epilogues.go:88:8:92:4 | function call | +| epilogues.go:97:9:97:14 | implicit write of result | epilogues.go:97:2:97:14 | return statement | +| epilogues.go:97:9:97:14 | result | epilogues.go:97:9:97:14 | implicit write of result | +| epilogues.go:102:1:110:1 | entry | epilogues.go:102:26:102:26 | argument corresponding to x | +| epilogues.go:102:1:110:1 | function declaration | epilogues.go:115:6:115:22 | skip | +| epilogues.go:102:6:102:24 | skip | epilogues.go:102:1:110:1 | function declaration | +| epilogues.go:102:26:102:26 | argument corresponding to x | epilogues.go:102:26:102:26 | initialization of x | +| epilogues.go:102:26:102:26 | initialization of x | epilogues.go:102:34:102:35 | zero value for ok | +| epilogues.go:102:34:102:35 | implicit read of ok | epilogues.go:102:43:102:43 | implicit read of n | +| epilogues.go:102:34:102:35 | initialization of ok | epilogues.go:102:43:102:43 | zero value for n | +| epilogues.go:102:34:102:35 | zero value for ok | epilogues.go:102:34:102:35 | initialization of ok | +| epilogues.go:102:43:102:43 | implicit read of n | epilogues.go:102:1:110:1 | exit | +| epilogues.go:102:43:102:43 | initialization of n | epilogues.go:103:8:103:17 | epiRecover | +| epilogues.go:102:43:102:43 | zero value for n | epilogues.go:102:43:102:43 | initialization of n | +| epilogues.go:103:2:103:19 | defer statement | epilogues.go:104:5:104:5 | x | +| epilogues.go:103:8:103:17 | epiRecover | epilogues.go:103:2:103:19 | defer statement | +| epilogues.go:103:8:103:19 | call to epiRecover | epilogues.go:102:1:110:1 | exit | +| epilogues.go:103:8:103:19 | call to epiRecover | epilogues.go:102:34:102:35 | implicit read of ok | +| epilogues.go:104:5:104:5 | x | epilogues.go:104:10:104:10 | 0 | +| epilogues.go:104:5:104:10 | ...==... | epilogues.go:104:5:104:10 | ...==... is false | +| epilogues.go:104:5:104:10 | ...==... | epilogues.go:104:5:104:10 | ...==... is true | +| epilogues.go:104:5:104:10 | ...==... is false | epilogues.go:107:2:107:2 | skip | +| epilogues.go:104:5:104:10 | ...==... is true | epilogues.go:105:3:105:8 | return statement | +| epilogues.go:104:10:104:10 | 0 | epilogues.go:104:5:104:10 | ...==... | +| epilogues.go:105:3:105:8 | return statement | epilogues.go:103:8:103:19 | call to epiRecover | +| epilogues.go:107:2:107:2 | assignment to n | epilogues.go:108:2:108:3 | skip | +| epilogues.go:107:2:107:2 | skip | epilogues.go:107:6:107:6 | x | +| epilogues.go:107:6:107:6 | x | epilogues.go:107:2:107:2 | assignment to n | +| epilogues.go:108:2:108:3 | assignment to ok | epilogues.go:109:2:109:7 | return statement | +| epilogues.go:108:2:108:3 | skip | epilogues.go:108:7:108:10 | true | +| epilogues.go:108:7:108:10 | true | epilogues.go:108:2:108:3 | assignment to ok | +| epilogues.go:109:2:109:7 | return statement | epilogues.go:103:8:103:19 | call to epiRecover | +| epilogues.go:115:1:118:1 | entry | epilogues.go:116:8:116:17 | epiRecover | +| epilogues.go:115:1:118:1 | function declaration | epilogues.go:0:0:0:0 | exit | +| epilogues.go:115:6:115:22 | skip | epilogues.go:115:1:118:1 | function declaration | +| epilogues.go:116:2:116:19 | defer statement | epilogues.go:117:2:117:6 | panic | +| epilogues.go:116:8:116:17 | epiRecover | epilogues.go:116:2:116:19 | defer statement | +| epilogues.go:116:8:116:19 | call to epiRecover | epilogues.go:115:1:118:1 | exit | +| epilogues.go:117:2:117:6 | panic | epilogues.go:117:8:117:13 | "boom" | +| epilogues.go:117:2:117:14 | call to panic | epilogues.go:116:8:116:19 | call to epiRecover | +| epilogues.go:117:8:117:13 | "boom" | epilogues.go:117:2:117:14 | call to panic | | equalitytests.go:0:0:0:0 | entry | equalitytests.go:3:1:5:1 | skip | | equalitytests.go:3:1:5:1 | skip | equalitytests.go:7:1:9:1 | skip | | equalitytests.go:7:1:9:1 | skip | equalitytests.go:11:6:11:18 | skip | diff --git a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected index 4e466b74504b..2715352ef253 100644 --- a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected +++ b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected @@ -1,3 +1,4 @@ +| epilogues.go:115:6:115:22 | epiRecoverUnnamed | github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph.epiRecoverUnnamed | | file://:0:0:0:0 | Exit | os.Exit | | file://:0:0:0:0 | Fatal | log.Fatal | | file://:0:0:0:0 | Fatal | log.Logger.Fatal | diff --git a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/epilogues.go b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/epilogues.go new file mode 100644 index 000000000000..ed87a94c8e0b --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/epilogues.go @@ -0,0 +1,118 @@ +package main + +import "fmt" + +// epiLogger has methods with both pointer and value receivers, used to check +// that the receiver and arguments of a deferred call are evaluated at the +// `defer` statement rather than in the function epilogue. +type epiLogger struct { + prefix string +} + +func (l *epiLogger) log(msg string, code int) { + fmt.Println(l.prefix, msg, code) +} + +func (l epiLogger) logValue(msg string) { + fmt.Println(l.prefix, msg) +} + +// epiRecover recovers from a panic. It is used as a deferred function so we can +// check that control flow returns to the result-read nodes and the normal exit +// node after recovering. +func epiRecover() { + if r := recover(); r != nil { + fmt.Println("recovered:", r) + } +} + +// epiPlain has no named result variable and a single `return` with a child +// expression. +func epiPlain(x int) int { + return x * 2 +} + +// epiVoid has no named result variable and no `return` statement at all. +func epiVoid() { + fmt.Println("void") +} + +// epiNamedMixed has named result variables and a mix of a bare `return` (no +// child expressions) and a `return` with child expressions. +func epiNamedMixed(x int) (result int, err error) { + if x < 0 { + result = -x + return + } + return x, nil +} + +// epiNamedBareOnly has a named result variable and only a bare `return`. +func epiNamedBareOnly(x int) (n int) { + n = x + 1 + return +} + +// epiDeferReceiverArgs has a deferred call with a (pointer) receiver and +// arguments that are expressions, so we can check the receiver `l` and the +// arguments `"count"` and `len(items)` are evaluated at the `defer` statement. +func epiDeferReceiverArgs(l *epiLogger, items []int) { + defer l.log("count", len(items)) + fmt.Println("processing", len(items)) +} + +// epiDeferValueReceiver has deferred calls with a value receiver and an +// address-of receiver, both with arguments evaluated at the `defer` statement. +func epiDeferValueReceiver(prefix string) { + l := epiLogger{prefix: prefix} + defer l.logValue("bye") + defer (&l).log("ptr", 7) + fmt.Println("body") +} + +// epiDeferFuncLit has a deferred function literal with parameters, so we can +// check that the arguments `"done"` and `x+1` are evaluated at the `defer` +// statement and that control flow enters the function literal body when it is +// invoked at the function epilogue. +func epiDeferFuncLit(x int) { + defer func(label string, n int) { + fmt.Println(label, n) + }("done", x+1) + fmt.Println("body", x) +} + +// epiRecoverNamed has a named result variable and a deferred closure containing +// `recover()`. After recovering on the panic path, control flow should return +// to the result-read nodes and the normal exit node. +func epiRecoverNamed(x int) (result int) { + defer func() { + if r := recover(); r != nil { + result = -1 + } + }() + if x < 0 { + panic("neg") + } + result = x * x + return result +} + +// epiRecoverNamedBare has named result variables, a deferred function +// containing `recover()`, and only bare `return` statements. +func epiRecoverNamedBare(x int) (ok bool, n int) { + defer epiRecover() + if x == 0 { + return + } + n = x + ok = true + return +} + +// epiRecoverUnnamed has no named result variables and a deferred function +// containing `recover()`; after recovering, control flow should reach the +// normal exit node directly (there are no result-read nodes). +func epiRecoverUnnamed() { + defer epiRecover() + panic("boom") +}