From a2d624b84dfdd21208311d6a45c0d6f3f594ea51 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 23 Sep 2026 16:06:03 +0100 Subject: [PATCH 1/3] Add test showing bug --- .../UnreachableStatement/UnreachableStatement.expected | 1 + .../query-tests/RedundantCode/UnreachableStatement/main.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected index 9c70b439d5b3..4b77961fa955 100644 --- a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected +++ b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected @@ -10,3 +10,4 @@ | main.go:147:2:147:17 | return statement | This statement is unreachable. | | main.go:153:2:153:22 | return statement | This statement is unreachable. | | main.go:159:2:159:43 | return statement | This statement is unreachable. | +| main.go:164:2:164:10 | select statement | This statement is unreachable. | diff --git a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go index cc26b717f605..7034e82f3679 100644 --- a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go +++ b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go @@ -159,4 +159,9 @@ func test19() mystruct { return mystruct{test10(1), test10(2) == 2} // $ Alert } +func test20() { + go panic("panic in another goroutine") + select {} // $ SPURIOUS: Alert // reachable after starting the goroutine +} + func main() {} From 107f24ea34c6b3d387bc65d4c1d43ab16bad6ec5 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 23 Sep 2026 16:17:43 +0100 Subject: [PATCH 2/3] Fix CFG for `go panic()` --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll | 6 ++++++ .../UnreachableStatement/UnreachableStatement.expected | 1 - .../query-tests/RedundantCode/UnreachableStatement/main.go | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll index 1da4b1e51231..ee7c9b1136a5 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll @@ -664,6 +664,9 @@ module CfgImpl { /** Helper: blank identifier check */ private predicate notBlankIdent(Go::Expr e) { not e instanceof Go::BlankIdent } + /** Holds if `e` is invoked in a newly started goroutine. */ + private predicate isGoStmtCall(Ast::AstNode e) { e = any(Go::GoStmt s).getCall() } + /** Helper: implicit field selection for promoted selectors */ additional predicate implicitFieldSelection(Ast::AstNode e, int index, Go::Field implicitField) { exists(Go::StructType baseType, Go::PromotedField child, int implicitFieldDepth | @@ -704,6 +707,7 @@ module CfgImpl { Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always ) { ast instanceof Go::CallExpr and + not isGoStmtCall(ast) and ( not exists(ast.(Go::CallExpr).getTarget()) or ast.(Go::CallExpr).getTarget().mayPanic() @@ -727,6 +731,7 @@ module CfgImpl { // exception completion so that the shared library's default In->After step // is suppressed. ast instanceof Go::CallExpr and + not isGoStmtCall(ast) and exists(Go::Function target | target = ast.(Go::CallExpr).getTarget() | target.mustPanic() or target.mustNotReturnNormally() ) and @@ -848,6 +853,7 @@ module CfgImpl { private predicate mayPanic(Ast::AstNode ast) { ast instanceof Go::CallExpr and not ast = any(Go::DeferStmt s).getCall() and + not isGoStmtCall(ast) and (not exists(ast.(Go::CallExpr).getTarget()) or ast.(Go::CallExpr).getTarget().mayPanic()) and not exists(Go::Function target | target = ast.(Go::CallExpr).getTarget() | target.mustNotReturnNormally() and not target.mustPanic() diff --git a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected index 4b77961fa955..9c70b439d5b3 100644 --- a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected +++ b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected @@ -10,4 +10,3 @@ | main.go:147:2:147:17 | return statement | This statement is unreachable. | | main.go:153:2:153:22 | return statement | This statement is unreachable. | | main.go:159:2:159:43 | return statement | This statement is unreachable. | -| main.go:164:2:164:10 | select statement | This statement is unreachable. | diff --git a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go index 7034e82f3679..85d5fc80a938 100644 --- a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go +++ b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go @@ -161,7 +161,7 @@ func test19() mystruct { func test20() { go panic("panic in another goroutine") - select {} // $ SPURIOUS: Alert // reachable after starting the goroutine + select {} // OK: reachable after starting the goroutine } func main() {} From 1b6ad90a73b2d606e9d4b4d1b0bb80382ddf1638 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 23 Sep 2026 22:23:34 +0100 Subject: [PATCH 3/3] Update test output --- .../ControlFlowGraph/ControlFlowNode_getASuccessor.expected | 2 -- .../UnreachableStatement/CONSISTENCY/CfgConsistency.expected | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) 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 28c1fdb2735a..ae8d5d7e77c4 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 @@ -3127,7 +3127,6 @@ | stmts.go:135:3:135:14 | expression statement | stmts.go:135:3:135:14 | Before call to test5 | | stmts.go:135:9:135:13 | false | stmts.go:135:3:135:14 | call to test5 | | stmts.go:140:1:142:1 | Entry | stmts.go:140:13:140:13 | f | -| stmts.go:140:1:142:1 | Exceptional Exit | stmts.go:140:1:142:1 | Exit | | stmts.go:140:1:142:1 | Normal Exit | stmts.go:140:1:142:1 | Exit | | stmts.go:140:1:142:1 | function declaration | stmts.go:145:1:159:1 | function declaration | | stmts.go:140:13:140:13 | f | stmts.go:140:23:142:1 | block statement | @@ -3139,7 +3138,6 @@ | stmts.go:141:5:141:5 | f | stmts.go:141:5:141:7 | call to f | | stmts.go:141:5:141:7 | After call to f | stmts.go:141:2:141:7 | go statement | | stmts.go:141:5:141:7 | Before call to f | stmts.go:141:5:141:5 | f | -| stmts.go:141:5:141:7 | call to f | stmts.go:140:1:142:1 | Exceptional Exit | | stmts.go:141:5:141:7 | call to f | stmts.go:141:5:141:7 | After call to f | | stmts.go:145:1:159:1 | Entry | stmts.go:145:13:145:14 | xs | | stmts.go:145:1:159:1 | Exceptional Exit | stmts.go:145:1:159:1 | Exit | diff --git a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/CONSISTENCY/CfgConsistency.expected b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/CONSISTENCY/CfgConsistency.expected index 15227b6e59b4..e38f26b8c1ce 100644 --- a/go/ql/test/query-tests/RedundantCode/UnreachableStatement/CONSISTENCY/CfgConsistency.expected +++ b/go/ql/test/query-tests/RedundantCode/UnreachableStatement/CONSISTENCY/CfgConsistency.expected @@ -1,5 +1,5 @@ consistencyOverview -| deadEnd | 9 | +| deadEnd | 10 | deadEnd | main.go:17:2:17:10 | select statement | | main.go:109:2:109:10 | select statement | @@ -10,3 +10,4 @@ deadEnd | main.go:145:2:145:10 | select statement | | main.go:151:2:151:10 | select statement | | main.go:157:2:157:10 | select statement | +| main.go:164:2:164:10 | select statement |