From e444730da3e576ae356c25f0af2eeb090f0afd1c Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 29 Jun 2026 10:19:13 +0100 Subject: [PATCH] Fix defer logic for panics --- .../go/controlflow/ControlFlowGraphShared.qll | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index d9e5635121d..c212d43168b 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -933,6 +933,22 @@ module GoCfg { n.isAfter(fd.getBody()) } + /** + * Holds if `n` is an exceptional-exit predecessor of `fd`: the in-order + * node of a call that always panics or never returns normally. In Go, + * deferred functions run on panic, so these nodes must also enter the + * deferred-call chain. + */ + private predicate exceptionalExitPred(PreControlFlowNode n, Go::FuncDef fd) { + exists(Go::CallExpr call | + call.getEnclosingFunction() = fd and + (call.getTarget().mustPanic() or call.getTarget().mustNotReturnNormally()) and + not call = any(Go::DeferStmt s).getCall() and + not call = any(Go::GoStmt s).getCall() and + n.isIn(call) + ) + } + /** * Holds if, after running its deferred calls, `fd` should continue at * `target` on a normal exit. For functions with result variables this is @@ -976,6 +992,15 @@ module GoCfg { deferInvoke(n1, firstD) and deferChainExitTarget(fd, n2) ) + or + // (e) an unconditional panic with active defers flows to the last-registered active defer + exists(Go::DeferStmt d, PreControlFlowNode reg | + deferRegistration(reg, d) and + d.getEnclosingFunction() = fd and + exceptionalExitPred(n1, fd) and + n1 = notDeferReach(reg) and + deferInvoke(n2, d) + ) ) }