From 316b4144d341d0ba02defdaef1bd7d0e4bf6e6b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Jun 2026 22:23:18 +0000 Subject: [PATCH 1/5] Initial plan From d83455db64d8f22622b586f14e0b3581c19fb603 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:58:42 +0000 Subject: [PATCH 2/5] Migrate Go expression switch CFG to shared library; handle fallthrough via fallsThrough --- .../go/controlflow/ControlFlowGraphShared.qll | 201 +++++++----------- .../codeql/controlflow/ControlFlowGraph.qll | 30 ++- 2 files changed, 101 insertions(+), 130 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index d2ce186390a4..9949ef763186 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -67,6 +67,13 @@ module GoCfg { e = any(Go::ImportSpec is).getPathExpr() or e.getParent*() = any(Go::ArrayTypeExpr ate).getLength() + or + // The body block of an expression switch is transparent: the shared + // switch model wires control flow directly from the switch to its case + // clauses (in control-flow order) and between cases, so the enclosing + // block must not introduce its own nodes or default left-to-right + // sequencing of the case clauses. + e = any(Go::ExpressionSwitchStmt sw).getBody() } AstNode getChild(AstNode n, int index) { @@ -217,19 +224,31 @@ module GoCfg { } class Switch extends AstNode { - Switch() { none() } - - Expr getExpr() { none() } - - Case getCase(int index) { none() } - - Stmt getStmt(int index) { none() } + Switch() { this instanceof Go::ExpressionSwitchStmt } + + Expr getExpr() { result = this.(Go::ExpressionSwitchStmt).getExpr() } + + Case getCase(int index) { result = this.(Go::ExpressionSwitchStmt).getCase(index) } + + Stmt getStmt(int index) { + // Go nests each case clause's body statements under the clause rather + // than in a flat list, so we expose a flattened view in which every + // case clause is immediately followed by its own body statements. This + // lets the shared library compute the body of a case as the statements + // between it and the next clause. + result = + rank[index + 1](Go::Stmt s, int caseIdx, int inner | + switchFlatItem(this, s, caseIdx, inner) + | + s order by caseIdx, inner + ) + } } class Case extends AstNode { - Case() { none() } + Case() { this = any(Go::ExpressionSwitchStmt sw).getACase() } - AstNode getPattern(int index) { none() } + AstNode getPattern(int index) { result = this.(Go::CaseClause).getExpr(index) } Expr getGuard() { none() } @@ -237,7 +256,36 @@ module GoCfg { } class DefaultCase extends Case { - DefaultCase() { none() } + DefaultCase() { not exists(this.(Go::CaseClause).getExpr(_)) } + } + + /** Gets the initializer of `switch` statement `switch`, if any. */ + AstNode getSwitchInit(Switch switch) { result = switch.(Go::ExpressionSwitchStmt).getInit() } + + /** + * Go has no implicit fall-through between case clauses; a case that runs to + * the end of its body breaks out of the switch. Fall-through only happens + * when the case body ends with an explicit `fallthrough` statement, in + * which case control transfers to the next case clause's body (in source + * order). The shared library models this by chaining the body of such a + * case to the body of the following case. + */ + predicate fallsThrough(Case c) { + c.(Go::CaseClause).getStmt(max(int i | exists(c.(Go::CaseClause).getStmt(i)))) instanceof + Go::FallthroughStmt + } + + /** + * Holds if `s` is the flattened body element at position (`caseIdx`, + * `inner`) of expression switch `sw`: either the `caseIdx`-th case clause + * itself (with `inner` = -1) or its `inner`-th body statement. + */ + private predicate switchFlatItem( + Go::ExpressionSwitchStmt sw, Go::Stmt s, int caseIdx, int inner + ) { + s = sw.getCase(caseIdx) and inner = -1 + or + s = sw.getCase(caseIdx).getStmt(inner) } class ConditionalExpr extends Expr { @@ -315,16 +363,10 @@ module GoCfg { class CallableContext = Void; - private newtype TLabel = - TGoLabel(string l) { exists(Go::LabeledStmt ls | l = ls.getLabel()) } or - TFallthrough() + private newtype TLabel = TGoLabel(string l) { exists(Go::LabeledStmt ls | l = ls.getLabel()) } class Label extends TLabel { - string toString() { - exists(string l | this = TGoLabel(l) and result = l) - or - this = TFallthrough() and result = "fallthrough" - } + string toString() { exists(string l | this = TGoLabel(l) and result = l) } } private Label getLabelOfStmt(Go::Stmt s) { @@ -339,19 +381,11 @@ module GoCfg { l = TGoLabel(n.(Go::BreakStmt).getLabel()) or l = TGoLabel(n.(Go::ContinueStmt).getLabel()) - or - l = TFallthrough() and n instanceof Go::FallthroughStmt } predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { kind.isBoolean() and - ( - n = any(Go::ForStmt fs).getCond() - or - exists(Go::ExpressionSwitchStmt ess | - not exists(ess.getExpr()) and n = ess.getACase().(Go::CaseClause).getExpr(_) - ) - ) + n = any(Go::ForStmt fs).getCond() } predicate preOrderExpr(Ast::Expr e) { @@ -515,17 +549,6 @@ module GoCfg { not exists(n.(Go::SliceExpr).getMax()) and tag = "implicit-max" or - // Implicit true in tagless switch - n instanceof Go::ExpressionSwitchStmt and - not exists(n.(Go::ExpressionSwitchStmt).getExpr()) and - tag = "implicit-true" - or - // Case check nodes - exists(int i | - exists(n.(Go::CaseClause).getExpr(i)) and - tag = "case-check:" + i.toString() - ) - or // Type switch implicit variable exists(Go::TypeSwitchStmt ts, Go::DefineStmt ds | ds = ts.getAssign() and @@ -709,12 +732,6 @@ module GoCfg { c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and always = false or - ast instanceof Go::FallthroughStmt and - n.injects(ast) and - c.getSuccessorType() instanceof BreakSuccessor and - c.hasLabel(TFallthrough()) and - always = true - or ast instanceof Go::GotoStmt and n.injects(ast) and c.getSuccessorType() instanceof GotoSuccessor and @@ -1403,10 +1420,10 @@ module GoCfg { } private predicate switchStmt(PreControlFlowNode n1, PreControlFlowNode n2) { - exprSwitch(n1, n2) or typeSwitch(n1, n2) or caseClause(n1, n2) + typeSwitch(n1, n2) or typeCaseClause(n1, n2) } - private predicate switchCasesStartOrAfter(Go::SwitchStmt sw, PreControlFlowNode n) { + private predicate switchCasesStartOrAfter(Go::TypeSwitchStmt sw, PreControlFlowNode n) { n.isBefore(sw.getNonDefaultCase(0)) or not exists(sw.getANonDefaultCase()) and n.isBefore(sw.getDefault()) @@ -1414,31 +1431,6 @@ module GoCfg { not exists(sw.getACase()) and n.isAfter(sw) } - private predicate exprSwitch(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Go::ExpressionSwitchStmt sw | - n1.isBefore(sw) and - ( - n2.isBefore(sw.getInit()) - or - not exists(sw.getInit()) and - ( - n2.isBefore(sw.getExpr()) - or - not exists(sw.getExpr()) and switchCasesStartOrAfter(sw, n2) - ) - ) - or - n1.isAfter(sw.getInit()) and - ( - n2.isBefore(sw.getExpr()) - or - not exists(sw.getExpr()) and switchCasesStartOrAfter(sw, n2) - ) - or - n1.isAfter(sw.getExpr()) and switchCasesStartOrAfter(sw, n2) - ) - } - private predicate typeSwitch(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::TypeSwitchStmt sw | n1.isBefore(sw) and @@ -1454,46 +1446,6 @@ module GoCfg { ) } - /** - * Holds if `sw` is a tagless expression switch, in which case the case - * expressions are themselves the boolean conditions being tested and are - * therefore in a boolean conditional context. - */ - private predicate isBooleanSwitch(Go::SwitchStmt sw) { - sw instanceof Go::ExpressionSwitchStmt and - not exists(sw.(Go::ExpressionSwitchStmt).getExpr()) - } - - /** - * Holds if `n` is the control-flow node immediately after evaluating case - * expression `caseExpr` of switch `sw` on the branch where `caseExpr` - * matches. - */ - private predicate afterCaseExprMatch(Go::SwitchStmt sw, Go::Expr caseExpr, PreControlFlowNode n) { - caseExpr = sw.getACase().(Go::CaseClause).getAnExpr() and - ( - isBooleanSwitch(sw) and n.isAfterTrue(caseExpr) - or - not isBooleanSwitch(sw) and n.isAfter(caseExpr) - ) - } - - /** - * Holds if `n` is the control-flow node immediately after evaluating case - * expression `caseExpr` of switch `sw` on the branch where `caseExpr` - * does not match. - */ - private predicate afterCaseExprNoMatch( - Go::SwitchStmt sw, Go::Expr caseExpr, PreControlFlowNode n - ) { - caseExpr = sw.getACase().(Go::CaseClause).getAnExpr() and - ( - isBooleanSwitch(sw) and n.isAfterFalse(caseExpr) - or - not isBooleanSwitch(sw) and n.isAfter(caseExpr) - ) - } - /** * Holds if `cc` is a case clause of a type switch with an assignment that * implicitly declares a variable whose type narrows to the case type. In @@ -1509,19 +1461,17 @@ module GoCfg { ) } - private predicate caseClause(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Go::SwitchStmt sw, Go::CaseClause cc, int i | cc = sw.getNonDefaultCase(i) | + private predicate typeCaseClause(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::TypeSwitchStmt sw, Go::CaseClause cc, int i | cc = sw.getNonDefaultCase(i) | n1.isBefore(cc) and n2.isBefore(cc.getExpr(0)) or - // For a tagless expression switch the case expressions are themselves - // booleans in a conditional context, so we only fall through to the - // next case expression on the false branch. - exists(int j | - afterCaseExprNoMatch(sw, cc.getExpr(j), n1) and n2.isBefore(cc.getExpr(j + 1)) - ) + // A type switch is not boolean, so each case type test has a single + // "after" node from which control flows both to the case body (on a + // match) and on to the next test (on a mismatch). + exists(int j | n1.isAfter(cc.getExpr(j)) and n2.isBefore(cc.getExpr(j + 1))) or exists(int last | last = max(int j | exists(cc.getExpr(j))) | - afterCaseExprMatch(sw, cc.getExpr(last), n1) and + n1.isAfter(cc.getExpr(last)) and ( hasTypeSwitchVar(cc) and n2.isAdditional(cc, "type-switch-var") or @@ -1533,7 +1483,7 @@ module GoCfg { ) ) or - afterCaseExprNoMatch(sw, cc.getExpr(last), n1) and + n1.isAfter(cc.getExpr(last)) and ( n2.isBefore(sw.getNonDefaultCase(i + 1)) or @@ -1546,7 +1496,7 @@ module GoCfg { ) ) or - exists(Go::SwitchStmt sw, Go::CaseClause def | def = sw.getDefault() | + exists(Go::TypeSwitchStmt sw, Go::CaseClause def | def = sw.getDefault() | n1.isBefore(def) and ( hasTypeSwitchVar(def) and n2.isAdditional(def, "type-switch-var") @@ -1560,7 +1510,7 @@ module GoCfg { ) ) or - exists(Go::SwitchStmt sw, Go::CaseClause cc | + exists(Go::TypeSwitchStmt sw, Go::CaseClause cc | sw.getACase() = cc and hasTypeSwitchVar(cc) and n1.isAdditional(cc, "type-switch-var") and @@ -1571,11 +1521,10 @@ module GoCfg { ) ) or - exists(Go::CaseClause cc | + exists(Go::TypeSwitchStmt sw, Go::CaseClause cc | cc = sw.getACase() | exists(int j | n1.isAfter(cc.getStmt(j)) and n2.isBefore(cc.getStmt(j + 1))) or - exists(Go::SwitchStmt sw, int last | - sw.getACase() = cc and + exists(int last | last = max(int j | exists(cc.getStmt(j))) and n1.isAfter(cc.getStmt(last)) and n2.isAfter(sw) diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index 1dea0a3e21c2..329d72e34350 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -266,6 +266,14 @@ signature module AstSig { Stmt getStmt(int index); } + /** + * Gets the initializer of `switch` statement `switch`, if any. + * + * Only some languages (e.g. Go) support an initializer that is evaluated + * before the switch expression. + */ + default AstNode getSwitchInit(Switch switch) { none() } + /** A case in a switch. */ class Case extends AstNode { /** Gets the pattern being matched by this case at the specified (zero-based) `index`. */ @@ -1866,11 +1874,25 @@ module Make0 Ast> { not exists(getRankedCaseCfgOrder(switch, _)) and firstCase.isAfter(switch) | n1.isBefore(switch) and - n2.isBefore(switch.getExpr()) + ( + n2.isBefore(getSwitchInit(switch)) + or + not exists(getSwitchInit(switch)) and + ( + n2.isBefore(switch.getExpr()) + or + not exists(switch.getExpr()) and + n2 = firstCase + ) + ) or - n1.isBefore(switch) and - not exists(switch.getExpr()) and - n2 = firstCase + n1.isAfter(getSwitchInit(switch)) and + ( + n2.isBefore(switch.getExpr()) + or + not exists(switch.getExpr()) and + n2 = firstCase + ) or n1.isAfter(switch.getExpr()) and n2 = firstCase From 4eba3607cb46adb44edb177c15b7f05299b0684f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:57:40 +0000 Subject: [PATCH 3/5] Fix switch-case sanitizer edge for shared CFG and accept CFG expected --- .../go/controlflow/ControlFlowGraph.qll | 15 +- .../go/controlflow/ControlFlowGraphShared.qll | 18 ++- .../dataflow/internal/TaintTrackingUtil.qll | 18 ++- .../ControlFlowNode_getASuccessor.expected | 149 +++++++++++------- .../codeql/controlflow/ControlFlowGraph.qll | 7 +- 5 files changed, 141 insertions(+), 66 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index 9ee373e25103..6546af1700b3 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -361,9 +361,16 @@ module ControlFlow { } /** - * Holds if `pred` is the node for the case `testExpr` in an expression - * switch statement which is switching on `switchExpr`, and `succ` is the - * node to be executed next if the case test succeeds. + * Holds if `pred` is the node reached when a case of the expression switch + * statement switching on `switchExpr` matches, `testExpr` is one of that + * case's test expressions, and `succ` is the node to be executed next when + * the case matches. + * + * In the control-flow graph the individual case test expressions of a case + * clause all funnel into a single "matched" node for the clause, from which + * control transfers to the case body. Hence `pred` is that shared matched + * node, and the same `(pred, succ)` pair is reported once per test + * expression `testExpr` of the clause. */ predicate isSwitchCaseTestPassingEdge( ControlFlow::Node pred, ControlFlow::Node succ, Expr switchExpr, Expr testExpr @@ -372,7 +379,7 @@ module ControlFlow { ess.getExpr() = switchExpr and cc = ess.getACase() and testExpr = cc.getExpr(i) and - pred.isAfter(testExpr) and + pred.isAfter(cc) and succ.isFirstNodeOf(cc.getStmt(0)) ) } diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 9949ef763186..186e06df8954 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -77,10 +77,20 @@ module GoCfg { } AstNode getChild(AstNode n, int index) { - not n instanceof Go::FuncDef and - not skipCfg(n) and - not skipCfg(result) and - result = n.getChild(index) + ( + not n instanceof Go::FuncDef and + not skipCfg(n) and + result = n.getChild(index) + or + // The body block of an expression switch is transparent (see `skipCfg`), + // so it is not itself a child and contributes no children. Expose the + // case clauses directly as children of the switch instead, so that the + // AST child chain stays connected for abrupt-completion propagation + // (e.g. a panicking call in a case body reaching the enclosing + // function's exceptional exit). + result = n.(Go::ExpressionSwitchStmt).getBody().getChild(index) + ) and + not skipCfg(result) } class Callable extends AstNode { diff --git a/go/ql/lib/semmle/go/dataflow/internal/TaintTrackingUtil.qll b/go/ql/lib/semmle/go/dataflow/internal/TaintTrackingUtil.qll index f9f148744939..c08b5be25f22 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/TaintTrackingUtil.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/TaintTrackingUtil.qll @@ -341,10 +341,22 @@ private ControlFlow::Node getANonTestPassingPredecessor( ) { isPossibleInputNode(inputNode, succ.getRoot()) and result = succ.getAPredecessor() and - not exists(Expr testExpr, DataFlow::Node switchExprNode | + not exists(DataFlow::Node switchExprNode | flowsToSwitchExpression(inputNode, switchExprNode) and - ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(), testExpr) and - testExpr.isConst() + // The case body is reachable only by matching a constant: at least one of + // the case's test expressions is constant, and none of them is + // non-constant. (All test expressions of a case share the same matched + // edge `result -> succ`, so a case mixing constant and non-constant tests + // must not be treated as a constant-only match.) + exists(Expr testExpr | + ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(), testExpr) and + testExpr.isConst() + ) and + not exists(Expr nonConstTestExpr | + ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(), + nonConstTestExpr) and + not nonConstTestExpr.isConst() + ) ) } 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 b126eb5ddaf1..444e9e83bcb1 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 @@ -14,14 +14,16 @@ | DuplicateSwitchCase.go:3:29:12:1 | param-init:0 block statement | DuplicateSwitchCase.go:4:2:11:2 | expression-switch statement | | DuplicateSwitchCase.go:4:2:11:2 | After expression-switch statement | DuplicateSwitchCase.go:3:29:12:1 | After block statement | | DuplicateSwitchCase.go:4:2:11:2 | expression-switch statement | DuplicateSwitchCase.go:5:2:6:9 | case clause | +| DuplicateSwitchCase.go:5:2:6:9 | After case clause [match] | DuplicateSwitchCase.go:6:3:6:9 | expression statement | +| DuplicateSwitchCase.go:5:2:6:9 | After case clause [no-match] | DuplicateSwitchCase.go:7:2:8:8 | case clause | | DuplicateSwitchCase.go:5:2:6:9 | case clause | DuplicateSwitchCase.go:5:7:5:20 | Before ...==... | | DuplicateSwitchCase.go:5:7:5:9 | After msg | DuplicateSwitchCase.go:5:14:5:20 | Before "start" | | DuplicateSwitchCase.go:5:7:5:9 | Before msg | DuplicateSwitchCase.go:5:7:5:9 | msg | | DuplicateSwitchCase.go:5:7:5:9 | msg | DuplicateSwitchCase.go:5:7:5:9 | After msg | -| DuplicateSwitchCase.go:5:7:5:20 | ...==... | DuplicateSwitchCase.go:5:7:5:20 | After ...==... [false] | -| DuplicateSwitchCase.go:5:7:5:20 | ...==... | DuplicateSwitchCase.go:5:7:5:20 | After ...==... [true] | -| DuplicateSwitchCase.go:5:7:5:20 | After ...==... [false] | DuplicateSwitchCase.go:7:2:8:8 | case clause | -| DuplicateSwitchCase.go:5:7:5:20 | After ...==... [true] | DuplicateSwitchCase.go:6:3:6:9 | expression statement | +| DuplicateSwitchCase.go:5:7:5:20 | ...==... | DuplicateSwitchCase.go:5:7:5:20 | After ...==... [match] | +| DuplicateSwitchCase.go:5:7:5:20 | ...==... | DuplicateSwitchCase.go:5:7:5:20 | After ...==... [no-match] | +| DuplicateSwitchCase.go:5:7:5:20 | After ...==... [match] | DuplicateSwitchCase.go:5:2:6:9 | After case clause [match] | +| DuplicateSwitchCase.go:5:7:5:20 | After ...==... [no-match] | DuplicateSwitchCase.go:5:2:6:9 | After case clause [no-match] | | DuplicateSwitchCase.go:5:7:5:20 | Before ...==... | DuplicateSwitchCase.go:5:7:5:9 | Before msg | | DuplicateSwitchCase.go:5:14:5:20 | "start" | DuplicateSwitchCase.go:5:14:5:20 | After "start" | | DuplicateSwitchCase.go:5:14:5:20 | After "start" | DuplicateSwitchCase.go:5:7:5:20 | ...==... | @@ -35,14 +37,16 @@ | DuplicateSwitchCase.go:6:3:6:9 | call to start | DuplicateSwitchCase.go:3:1:12:1 | Exceptional Exit | | DuplicateSwitchCase.go:6:3:6:9 | call to start | DuplicateSwitchCase.go:6:3:6:9 | After call to start | | DuplicateSwitchCase.go:6:3:6:9 | expression statement | DuplicateSwitchCase.go:6:3:6:9 | Before call to start | +| DuplicateSwitchCase.go:7:2:8:8 | After case clause [match] | DuplicateSwitchCase.go:8:3:8:8 | expression statement | +| DuplicateSwitchCase.go:7:2:8:8 | After case clause [no-match] | DuplicateSwitchCase.go:9:2:10:34 | case clause | | DuplicateSwitchCase.go:7:2:8:8 | case clause | DuplicateSwitchCase.go:7:7:7:20 | Before ...==... | | DuplicateSwitchCase.go:7:7:7:9 | After msg | DuplicateSwitchCase.go:7:14:7:20 | Before "start" | | DuplicateSwitchCase.go:7:7:7:9 | Before msg | DuplicateSwitchCase.go:7:7:7:9 | msg | | DuplicateSwitchCase.go:7:7:7:9 | msg | DuplicateSwitchCase.go:7:7:7:9 | After msg | -| DuplicateSwitchCase.go:7:7:7:20 | ...==... | DuplicateSwitchCase.go:7:7:7:20 | After ...==... [false] | -| DuplicateSwitchCase.go:7:7:7:20 | ...==... | DuplicateSwitchCase.go:7:7:7:20 | After ...==... [true] | -| DuplicateSwitchCase.go:7:7:7:20 | After ...==... [false] | DuplicateSwitchCase.go:9:2:10:34 | case clause | -| DuplicateSwitchCase.go:7:7:7:20 | After ...==... [true] | DuplicateSwitchCase.go:8:3:8:8 | expression statement | +| DuplicateSwitchCase.go:7:7:7:20 | ...==... | DuplicateSwitchCase.go:7:7:7:20 | After ...==... [match] | +| DuplicateSwitchCase.go:7:7:7:20 | ...==... | DuplicateSwitchCase.go:7:7:7:20 | After ...==... [no-match] | +| DuplicateSwitchCase.go:7:7:7:20 | After ...==... [match] | DuplicateSwitchCase.go:7:2:8:8 | After case clause [match] | +| DuplicateSwitchCase.go:7:7:7:20 | After ...==... [no-match] | DuplicateSwitchCase.go:7:2:8:8 | After case clause [no-match] | | DuplicateSwitchCase.go:7:7:7:20 | Before ...==... | DuplicateSwitchCase.go:7:7:7:9 | Before msg | | DuplicateSwitchCase.go:7:14:7:20 | "start" | DuplicateSwitchCase.go:7:14:7:20 | After "start" | | DuplicateSwitchCase.go:7:14:7:20 | After "start" | DuplicateSwitchCase.go:7:7:7:20 | ...==... | @@ -56,7 +60,8 @@ | DuplicateSwitchCase.go:8:3:8:8 | call to stop | DuplicateSwitchCase.go:3:1:12:1 | Exceptional Exit | | DuplicateSwitchCase.go:8:3:8:8 | call to stop | DuplicateSwitchCase.go:8:3:8:8 | After call to stop | | DuplicateSwitchCase.go:8:3:8:8 | expression statement | DuplicateSwitchCase.go:8:3:8:8 | Before call to stop | -| DuplicateSwitchCase.go:9:2:10:34 | case clause | DuplicateSwitchCase.go:10:3:10:34 | expression statement | +| DuplicateSwitchCase.go:9:2:10:34 | After case clause [match] | DuplicateSwitchCase.go:10:3:10:34 | expression statement | +| DuplicateSwitchCase.go:9:2:10:34 | case clause | DuplicateSwitchCase.go:9:2:10:34 | After case clause [match] | | DuplicateSwitchCase.go:10:3:10:7 | After panic | DuplicateSwitchCase.go:10:9:10:33 | Before "Message not understood." | | DuplicateSwitchCase.go:10:3:10:7 | Before panic | DuplicateSwitchCase.go:10:3:10:7 | panic | | DuplicateSwitchCase.go:10:3:10:7 | panic | DuplicateSwitchCase.go:10:3:10:7 | After panic | @@ -3618,7 +3623,8 @@ | stmts.go:79:21:79:22 | 19 | stmts.go:79:21:79:22 | After 19 | | stmts.go:79:21:79:22 | After 19 | stmts.go:79:17:79:22 | ...-... | | stmts.go:79:21:79:22 | Before 19 | stmts.go:79:21:79:22 | 19 | -| stmts.go:80:2:81:14 | case clause | stmts.go:81:3:81:14 | expression statement | +| stmts.go:80:2:81:14 | After case clause [match] | stmts.go:81:3:81:14 | expression statement | +| stmts.go:80:2:81:14 | case clause | stmts.go:80:2:81:14 | After case clause [match] | | stmts.go:81:3:81:7 | After test5 | stmts.go:81:9:81:13 | Before false | | stmts.go:81:3:81:7 | Before test5 | stmts.go:81:3:81:7 | test5 | | stmts.go:81:3:81:7 | test5 | stmts.go:81:3:81:7 | After test5 | @@ -3636,18 +3642,26 @@ | stmts.go:84:9:84:9 | After x | stmts.go:85:2:85:8 | case clause | | stmts.go:84:9:84:9 | Before x | stmts.go:84:9:84:9 | x | | stmts.go:84:9:84:9 | x | stmts.go:84:9:84:9 | After x | +| stmts.go:85:2:85:8 | After case clause [match] | stmts.go:84:2:88:2 | After expression-switch statement | +| stmts.go:85:2:85:8 | After case clause [no-match] | stmts.go:86:2:87:13 | case clause | | stmts.go:85:2:85:8 | case clause | stmts.go:85:7:85:7 | Before 1 | -| stmts.go:85:7:85:7 | 1 | stmts.go:85:7:85:7 | After 1 | -| stmts.go:85:7:85:7 | After 1 | stmts.go:84:2:88:2 | After expression-switch statement | -| stmts.go:85:7:85:7 | After 1 | stmts.go:86:2:87:13 | case clause | +| stmts.go:85:7:85:7 | 1 | stmts.go:85:7:85:7 | After 1 [match] | +| stmts.go:85:7:85:7 | 1 | stmts.go:85:7:85:7 | After 1 [no-match] | +| stmts.go:85:7:85:7 | After 1 [match] | stmts.go:85:2:85:8 | After case clause [match] | +| stmts.go:85:7:85:7 | After 1 [no-match] | stmts.go:85:2:85:8 | After case clause [no-match] | | stmts.go:85:7:85:7 | Before 1 | stmts.go:85:7:85:7 | 1 | +| stmts.go:86:2:87:13 | After case clause [match] | stmts.go:87:3:87:13 | expression statement | +| stmts.go:86:2:87:13 | After case clause [no-match] | stmts.go:84:2:88:2 | After expression-switch statement | | stmts.go:86:2:87:13 | case clause | stmts.go:86:7:86:7 | Before 2 | -| stmts.go:86:7:86:7 | 2 | stmts.go:86:7:86:7 | After 2 | -| stmts.go:86:7:86:7 | After 2 | stmts.go:86:10:86:10 | Before 3 | +| stmts.go:86:7:86:7 | 2 | stmts.go:86:7:86:7 | After 2 [match] | +| stmts.go:86:7:86:7 | 2 | stmts.go:86:7:86:7 | After 2 [no-match] | +| stmts.go:86:7:86:7 | After 2 [match] | stmts.go:86:2:87:13 | After case clause [match] | +| stmts.go:86:7:86:7 | After 2 [no-match] | stmts.go:86:10:86:10 | Before 3 | | stmts.go:86:7:86:7 | Before 2 | stmts.go:86:7:86:7 | 2 | -| stmts.go:86:10:86:10 | 3 | stmts.go:86:10:86:10 | After 3 | -| stmts.go:86:10:86:10 | After 3 | stmts.go:84:2:88:2 | After expression-switch statement | -| stmts.go:86:10:86:10 | After 3 | stmts.go:87:3:87:13 | expression statement | +| stmts.go:86:10:86:10 | 3 | stmts.go:86:10:86:10 | After 3 [match] | +| stmts.go:86:10:86:10 | 3 | stmts.go:86:10:86:10 | After 3 [no-match] | +| stmts.go:86:10:86:10 | After 3 [match] | stmts.go:86:2:87:13 | After case clause [match] | +| stmts.go:86:10:86:10 | After 3 [no-match] | stmts.go:86:2:87:13 | After case clause [no-match] | | stmts.go:86:10:86:10 | Before 3 | stmts.go:86:10:86:10 | 3 | | stmts.go:87:3:87:7 | After test5 | stmts.go:87:9:87:12 | Before true | | stmts.go:87:3:87:7 | Before test5 | stmts.go:87:3:87:7 | test5 | @@ -3666,10 +3680,13 @@ | stmts.go:90:9:90:9 | After x | stmts.go:91:2:93:13 | case clause | | stmts.go:90:9:90:9 | Before x | stmts.go:90:9:90:9 | x | | stmts.go:90:9:90:9 | x | stmts.go:90:9:90:9 | After x | +| stmts.go:91:2:93:13 | After case clause [match] | stmts.go:92:3:92:14 | expression statement | +| stmts.go:91:2:93:13 | After case clause [no-match] | stmts.go:94:2:95:13 | case clause | | stmts.go:91:2:93:13 | case clause | stmts.go:91:7:91:7 | Before 1 | -| stmts.go:91:7:91:7 | 1 | stmts.go:91:7:91:7 | After 1 | -| stmts.go:91:7:91:7 | After 1 | stmts.go:92:3:92:14 | expression statement | -| stmts.go:91:7:91:7 | After 1 | stmts.go:94:2:95:13 | case clause | +| stmts.go:91:7:91:7 | 1 | stmts.go:91:7:91:7 | After 1 [match] | +| stmts.go:91:7:91:7 | 1 | stmts.go:91:7:91:7 | After 1 [no-match] | +| stmts.go:91:7:91:7 | After 1 [match] | stmts.go:91:2:93:13 | After case clause [match] | +| stmts.go:91:7:91:7 | After 1 [no-match] | stmts.go:91:2:93:13 | After case clause [no-match] | | stmts.go:91:7:91:7 | Before 1 | stmts.go:91:7:91:7 | 1 | | stmts.go:92:3:92:7 | After test5 | stmts.go:92:9:92:13 | Before false | | stmts.go:92:3:92:7 | Before test5 | stmts.go:92:3:92:7 | test5 | @@ -3683,14 +3700,17 @@ | stmts.go:92:9:92:13 | After false | stmts.go:92:3:92:14 | call to test5 | | stmts.go:92:9:92:13 | Before false | stmts.go:92:9:92:13 | false | | stmts.go:92:9:92:13 | false | stmts.go:92:9:92:13 | After false | -| stmts.go:93:3:93:13 | fallthrough statement | stmts.go:90:2:96:2 | After expression-switch statement | +| stmts.go:93:3:93:13 | fallthrough statement | stmts.go:95:3:95:13 | expression statement | +| stmts.go:94:2:95:13 | After case clause [match] | stmts.go:95:3:95:13 | expression statement | +| stmts.go:94:2:95:13 | After case clause [no-match] | stmts.go:90:2:96:2 | After expression-switch statement | | stmts.go:94:2:95:13 | case clause | stmts.go:94:7:94:11 | Before ...-... | | stmts.go:94:7:94:7 | 2 | stmts.go:94:7:94:7 | After 2 | | stmts.go:94:7:94:7 | After 2 | stmts.go:94:11:94:11 | Before 5 | | stmts.go:94:7:94:7 | Before 2 | stmts.go:94:7:94:7 | 2 | -| stmts.go:94:7:94:11 | ...-... | stmts.go:94:7:94:11 | After ...-... | -| stmts.go:94:7:94:11 | After ...-... | stmts.go:90:2:96:2 | After expression-switch statement | -| stmts.go:94:7:94:11 | After ...-... | stmts.go:95:3:95:13 | expression statement | +| stmts.go:94:7:94:11 | ...-... | stmts.go:94:7:94:11 | After ...-... [match] | +| stmts.go:94:7:94:11 | ...-... | stmts.go:94:7:94:11 | After ...-... [no-match] | +| stmts.go:94:7:94:11 | After ...-... [match] | stmts.go:94:2:95:13 | After case clause [match] | +| stmts.go:94:7:94:11 | After ...-... [no-match] | stmts.go:94:2:95:13 | After case clause [no-match] | | stmts.go:94:7:94:11 | Before ...-... | stmts.go:94:7:94:7 | Before 2 | | stmts.go:94:11:94:11 | 5 | stmts.go:94:11:94:11 | After 5 | | stmts.go:94:11:94:11 | After 5 | stmts.go:94:7:94:11 | ...-... | @@ -3712,12 +3732,15 @@ | stmts.go:98:9:98:9 | After x | stmts.go:100:2:101:13 | case clause | | stmts.go:98:9:98:9 | Before x | stmts.go:98:9:98:9 | x | | stmts.go:98:9:98:9 | x | stmts.go:98:9:98:9 | After x | -| stmts.go:99:2:99:9 | case clause | stmts.go:98:2:102:2 | After expression-switch statement | -| stmts.go:99:2:99:9 | case clause | stmts.go:100:2:101:13 | case clause | +| stmts.go:99:2:99:9 | After case clause [match] | stmts.go:98:2:102:2 | After expression-switch statement | +| stmts.go:99:2:99:9 | case clause | stmts.go:99:2:99:9 | After case clause [match] | +| stmts.go:100:2:101:13 | After case clause [match] | stmts.go:101:3:101:13 | expression statement | +| stmts.go:100:2:101:13 | After case clause [no-match] | stmts.go:99:2:99:9 | case clause | | stmts.go:100:2:101:13 | case clause | stmts.go:100:7:100:7 | Before 2 | -| stmts.go:100:7:100:7 | 2 | stmts.go:100:7:100:7 | After 2 | -| stmts.go:100:7:100:7 | After 2 | stmts.go:99:2:99:9 | case clause | -| stmts.go:100:7:100:7 | After 2 | stmts.go:101:3:101:13 | expression statement | +| stmts.go:100:7:100:7 | 2 | stmts.go:100:7:100:7 | After 2 [match] | +| stmts.go:100:7:100:7 | 2 | stmts.go:100:7:100:7 | After 2 [no-match] | +| stmts.go:100:7:100:7 | After 2 [match] | stmts.go:100:2:101:13 | After case clause [match] | +| stmts.go:100:7:100:7 | After 2 [no-match] | stmts.go:100:2:101:13 | After case clause [no-match] | | stmts.go:100:7:100:7 | Before 2 | stmts.go:100:7:100:7 | 2 | | stmts.go:101:3:101:7 | After test5 | stmts.go:101:9:101:12 | Before true | | stmts.go:101:3:101:7 | Before test5 | stmts.go:101:3:101:7 | test5 | @@ -3733,10 +3756,18 @@ | stmts.go:101:9:101:12 | true | stmts.go:101:9:101:12 | After true | | stmts.go:104:2:108:2 | After expression-switch statement | stmts.go:75:19:109:1 | After block statement | | stmts.go:104:2:108:2 | expression-switch statement | stmts.go:107:2:107:11 | case clause | +| stmts.go:105:2:106:7 | After case clause [match] | stmts.go:106:3:106:7 | Before break statement | +| stmts.go:105:2:106:7 | case clause | stmts.go:105:2:106:7 | After case clause [match] | +| stmts.go:106:3:106:7 | Before break statement | stmts.go:106:3:106:7 | break statement | +| stmts.go:106:3:106:7 | break statement | stmts.go:104:2:108:2 | After expression-switch statement | +| stmts.go:107:2:107:11 | After case clause [match] | stmts.go:104:2:108:2 | After expression-switch statement | +| stmts.go:107:2:107:11 | After case clause [no-match] | stmts.go:105:2:106:7 | case clause | | stmts.go:107:2:107:11 | case clause | stmts.go:107:7:107:10 | Before true | -| stmts.go:107:7:107:10 | After true [true] | stmts.go:104:2:108:2 | After expression-switch statement | +| stmts.go:107:7:107:10 | After true [match] | stmts.go:107:2:107:11 | After case clause [match] | +| stmts.go:107:7:107:10 | After true [no-match] | stmts.go:107:2:107:11 | After case clause [no-match] | | stmts.go:107:7:107:10 | Before true | stmts.go:107:7:107:10 | true | -| stmts.go:107:7:107:10 | true | stmts.go:107:7:107:10 | After true [true] | +| stmts.go:107:7:107:10 | true | stmts.go:107:7:107:10 | After true [match] | +| stmts.go:107:7:107:10 | true | stmts.go:107:7:107:10 | After true [no-match] | | stmts.go:112:1:137:1 | After function declaration | stmts.go:140:1:142:1 | Before function declaration | | stmts.go:112:1:137:1 | Before function declaration | stmts.go:112:1:137:1 | function declaration | | stmts.go:112:1:137:1 | Entry | stmts.go:112:27:137:1 | block statement | @@ -4027,38 +4058,44 @@ | tst.go:3:19:12:1 | param-init:0 block statement | tst.go:4:2:11:2 | expression-switch statement | | tst.go:4:2:11:2 | After expression-switch statement | tst.go:3:19:12:1 | After block statement | | tst.go:4:2:11:2 | expression-switch statement | tst.go:5:2:5:13 | case clause | +| tst.go:5:2:5:13 | After case clause [match] | tst.go:4:2:11:2 | After expression-switch statement | +| tst.go:5:2:5:13 | After case clause [no-match] | tst.go:7:2:7:13 | case clause | | tst.go:5:2:5:13 | case clause | tst.go:5:7:5:12 | Before ...<... | | tst.go:5:7:5:7 | After x | tst.go:5:11:5:12 | Before 23 | | tst.go:5:7:5:7 | Before x | tst.go:5:7:5:7 | x | | tst.go:5:7:5:7 | x | tst.go:5:7:5:7 | After x | -| tst.go:5:7:5:12 | ...<... | tst.go:5:7:5:12 | After ...<... [false] | -| tst.go:5:7:5:12 | ...<... | tst.go:5:7:5:12 | After ...<... [true] | -| tst.go:5:7:5:12 | After ...<... [false] | tst.go:7:2:7:13 | case clause | -| tst.go:5:7:5:12 | After ...<... [true] | tst.go:4:2:11:2 | After expression-switch statement | +| tst.go:5:7:5:12 | ...<... | tst.go:5:7:5:12 | After ...<... [match] | +| tst.go:5:7:5:12 | ...<... | tst.go:5:7:5:12 | After ...<... [no-match] | +| tst.go:5:7:5:12 | After ...<... [match] | tst.go:5:2:5:13 | After case clause [match] | +| tst.go:5:7:5:12 | After ...<... [no-match] | tst.go:5:2:5:13 | After case clause [no-match] | | tst.go:5:7:5:12 | Before ...<... | tst.go:5:7:5:7 | Before x | | tst.go:5:11:5:12 | 23 | tst.go:5:11:5:12 | After 23 | | tst.go:5:11:5:12 | After 23 | tst.go:5:7:5:12 | ...<... | | tst.go:5:11:5:12 | Before 23 | tst.go:5:11:5:12 | 23 | +| tst.go:7:2:7:13 | After case clause [match] | tst.go:4:2:11:2 | After expression-switch statement | +| tst.go:7:2:7:13 | After case clause [no-match] | tst.go:9:2:9:13 | case clause | | tst.go:7:2:7:13 | case clause | tst.go:7:7:7:12 | Before ...<... | | tst.go:7:7:7:7 | After x | tst.go:7:11:7:12 | Before 42 | | tst.go:7:7:7:7 | Before x | tst.go:7:7:7:7 | x | | tst.go:7:7:7:7 | x | tst.go:7:7:7:7 | After x | -| tst.go:7:7:7:12 | ...<... | tst.go:7:7:7:12 | After ...<... [false] | -| tst.go:7:7:7:12 | ...<... | tst.go:7:7:7:12 | After ...<... [true] | -| tst.go:7:7:7:12 | After ...<... [false] | tst.go:9:2:9:13 | case clause | -| tst.go:7:7:7:12 | After ...<... [true] | tst.go:4:2:11:2 | After expression-switch statement | +| tst.go:7:7:7:12 | ...<... | tst.go:7:7:7:12 | After ...<... [match] | +| tst.go:7:7:7:12 | ...<... | tst.go:7:7:7:12 | After ...<... [no-match] | +| tst.go:7:7:7:12 | After ...<... [match] | tst.go:7:2:7:13 | After case clause [match] | +| tst.go:7:7:7:12 | After ...<... [no-match] | tst.go:7:2:7:13 | After case clause [no-match] | | tst.go:7:7:7:12 | Before ...<... | tst.go:7:7:7:7 | Before x | | tst.go:7:11:7:12 | 42 | tst.go:7:11:7:12 | After 42 | | tst.go:7:11:7:12 | After 42 | tst.go:7:7:7:12 | ...<... | | tst.go:7:11:7:12 | Before 42 | tst.go:7:11:7:12 | 42 | +| tst.go:9:2:9:13 | After case clause [match] | tst.go:4:2:11:2 | After expression-switch statement | +| tst.go:9:2:9:13 | After case clause [no-match] | tst.go:4:2:11:2 | After expression-switch statement | | tst.go:9:2:9:13 | case clause | tst.go:9:7:9:12 | Before ...<... | | tst.go:9:7:9:7 | After x | tst.go:9:11:9:12 | Before 23 | | tst.go:9:7:9:7 | Before x | tst.go:9:7:9:7 | x | | tst.go:9:7:9:7 | x | tst.go:9:7:9:7 | After x | -| tst.go:9:7:9:12 | ...<... | tst.go:9:7:9:12 | After ...<... [false] | -| tst.go:9:7:9:12 | ...<... | tst.go:9:7:9:12 | After ...<... [true] | -| tst.go:9:7:9:12 | After ...<... [false] | tst.go:4:2:11:2 | After expression-switch statement | -| tst.go:9:7:9:12 | After ...<... [true] | tst.go:4:2:11:2 | After expression-switch statement | +| tst.go:9:7:9:12 | ...<... | tst.go:9:7:9:12 | After ...<... [match] | +| tst.go:9:7:9:12 | ...<... | tst.go:9:7:9:12 | After ...<... [no-match] | +| tst.go:9:7:9:12 | After ...<... [match] | tst.go:9:2:9:13 | After case clause [match] | +| tst.go:9:7:9:12 | After ...<... [no-match] | tst.go:9:2:9:13 | After case clause [no-match] | | tst.go:9:7:9:12 | Before ...<... | tst.go:9:7:9:7 | Before x | | tst.go:9:11:9:12 | 23 | tst.go:9:11:9:12 | After 23 | | tst.go:9:11:9:12 | After 23 | tst.go:9:7:9:12 | ...<... | @@ -4074,14 +4111,16 @@ | tst.go:14:26:21:1 | param-init:0 block statement | tst.go:15:2:20:2 | expression-switch statement | | tst.go:15:2:20:2 | After expression-switch statement | tst.go:14:26:21:1 | After block statement | | tst.go:15:2:20:2 | expression-switch statement | tst.go:16:2:16:34 | case clause | +| tst.go:16:2:16:34 | After case clause [match] | tst.go:15:2:20:2 | After expression-switch statement | +| tst.go:16:2:16:34 | After case clause [no-match] | tst.go:18:2:18:39 | case clause | | tst.go:16:2:16:34 | case clause | tst.go:16:7:16:33 | Before ...<... | | tst.go:16:7:16:11 | After value | tst.go:16:15:16:33 | Before ...*... | | tst.go:16:7:16:11 | Before value | tst.go:16:7:16:11 | value | | tst.go:16:7:16:11 | value | tst.go:16:7:16:11 | After value | -| tst.go:16:7:16:33 | ...<... | tst.go:16:7:16:33 | After ...<... [false] | -| tst.go:16:7:16:33 | ...<... | tst.go:16:7:16:33 | After ...<... [true] | -| tst.go:16:7:16:33 | After ...<... [false] | tst.go:18:2:18:39 | case clause | -| tst.go:16:7:16:33 | After ...<... [true] | tst.go:15:2:20:2 | After expression-switch statement | +| tst.go:16:7:16:33 | ...<... | tst.go:16:7:16:33 | After ...<... [match] | +| tst.go:16:7:16:33 | ...<... | tst.go:16:7:16:33 | After ...<... [no-match] | +| tst.go:16:7:16:33 | After ...<... [match] | tst.go:16:2:16:34 | After case clause [match] | +| tst.go:16:7:16:33 | After ...<... [no-match] | tst.go:16:2:16:34 | After case clause [no-match] | | tst.go:16:7:16:33 | Before ...<... | tst.go:16:7:16:11 | Before value | | tst.go:16:15:16:18 | 1024 | tst.go:16:15:16:18 | After 1024 | | tst.go:16:15:16:18 | After 1024 | tst.go:16:20:16:23 | Before 1024 | @@ -4104,14 +4143,16 @@ | tst.go:16:30:16:33 | 1024 | tst.go:16:30:16:33 | After 1024 | | tst.go:16:30:16:33 | After 1024 | tst.go:16:15:16:33 | ...*... | | tst.go:16:30:16:33 | Before 1024 | tst.go:16:30:16:33 | 1024 | +| tst.go:18:2:18:39 | After case clause [match] | tst.go:15:2:20:2 | After expression-switch statement | +| tst.go:18:2:18:39 | After case clause [no-match] | tst.go:15:2:20:2 | After expression-switch statement | | tst.go:18:2:18:39 | case clause | tst.go:18:7:18:38 | Before ...<... | | tst.go:18:7:18:11 | After value | tst.go:18:15:18:38 | Before ...*... | | tst.go:18:7:18:11 | Before value | tst.go:18:7:18:11 | value | | tst.go:18:7:18:11 | value | tst.go:18:7:18:11 | After value | -| tst.go:18:7:18:38 | ...<... | tst.go:18:7:18:38 | After ...<... [false] | -| tst.go:18:7:18:38 | ...<... | tst.go:18:7:18:38 | After ...<... [true] | -| tst.go:18:7:18:38 | After ...<... [false] | tst.go:15:2:20:2 | After expression-switch statement | -| tst.go:18:7:18:38 | After ...<... [true] | tst.go:15:2:20:2 | After expression-switch statement | +| tst.go:18:7:18:38 | ...<... | tst.go:18:7:18:38 | After ...<... [match] | +| tst.go:18:7:18:38 | ...<... | tst.go:18:7:18:38 | After ...<... [no-match] | +| tst.go:18:7:18:38 | After ...<... [match] | tst.go:18:2:18:39 | After case clause [match] | +| tst.go:18:7:18:38 | After ...<... [no-match] | tst.go:18:2:18:39 | After case clause [no-match] | | tst.go:18:7:18:38 | Before ...<... | tst.go:18:7:18:11 | Before value | | tst.go:18:15:18:18 | 1024 | tst.go:18:15:18:18 | After 1024 | | tst.go:18:15:18:18 | After 1024 | tst.go:18:20:18:23 | Before 1024 | @@ -4158,5 +4199,5 @@ | tst.go:28:15:32:1 | block statement | tst.go:29:2:31:2 | expression-switch statement | | tst.go:29:2:31:2 | After expression-switch statement | tst.go:28:15:32:1 | After block statement | | tst.go:29:2:31:2 | expression-switch statement | tst.go:30:2:30:9 | case clause | -| tst.go:30:2:30:9 | case clause | tst.go:29:2:31:2 | After expression-switch statement | -| tst.go:30:2:30:9 | case clause | tst.go:29:9:31:2 | After block statement | +| tst.go:30:2:30:9 | After case clause [match] | tst.go:29:2:31:2 | After expression-switch statement | +| tst.go:30:2:30:9 | case clause | tst.go:30:2:30:9 | After case clause [match] | diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index 329d72e34350..9655f75e0f05 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -663,7 +663,12 @@ module Make0 Ast> { not exists(getChild(n, _)) and not postOrInOrder(n) and not additionalNode(n, _, _) and - not inConditionalContext(n, _) + not inConditionalContext(n, _) and + // A switch is a branching construct with an explicit step from its + // "before" node to its "after" node, so it must keep distinct before and + // after nodes even when it has no children (e.g. an empty `switch {}`). + // Merging them would otherwise turn that step into a spurious self-loop. + not n instanceof Switch } private string loopHeaderTag() { result = "[LoopHeader]" } From 587c4b344a525decb58e6c8c6e521811ec4377aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:02:42 +0000 Subject: [PATCH 4/5] Update SSA expected files for new shared-CFG node locations --- .../semmle/go/dataflow/SSA/DefUse.expected | 76 ++++++------ .../go/dataflow/SSA/SsaDefinition.expected | 90 +++++++-------- .../go/dataflow/SSA/SsaWithFields.expected | 104 ++++++++--------- .../semmle/go/dataflow/SSA/VarDefs.expected | 108 +++++++++--------- .../semmle/go/dataflow/SSA/VarUses.expected | 8 +- 5 files changed, 193 insertions(+), 193 deletions(-) diff --git a/go/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected b/go/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected index 775eff4a49e5..bbeca0d83553 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected @@ -1,42 +1,42 @@ -| main.go:15:12:15:12 | x | main.go:13:6:13:6 | SSA def(x) | main.go:13:6:13:6 | x | -| main.go:15:15:15:15 | y | main.go:14:2:14:2 | SSA def(y) | main.go:14:2:14:2 | y | -| main.go:17:3:17:3 | y | main.go:14:2:14:2 | SSA def(y) | main.go:14:2:14:2 | y | -| main.go:19:12:19:12 | x | main.go:13:6:13:6 | SSA def(x) | main.go:13:6:13:6 | x | -| main.go:19:15:19:15 | y | main.go:19:2:19:10 | SSA phi(y) | main.go:14:2:14:2 | y | -| main.go:21:7:21:7 | y | main.go:19:2:19:10 | SSA phi(y) | main.go:14:2:14:2 | y | -| main.go:23:12:23:12 | x | main.go:23:2:23:10 | SSA phi(x) | main.go:13:6:13:6 | x | -| main.go:23:15:23:15 | y | main.go:19:2:19:10 | SSA phi(y) | main.go:14:2:14:2 | y | -| main.go:27:10:27:10 | x | main.go:26:10:26:10 | SSA def(x) | main.go:26:10:26:10 | x | -| main.go:29:10:29:10 | b | main.go:27:5:27:5 | SSA def(b) | main.go:27:5:27:5 | b | -| main.go:29:13:29:13 | a | main.go:27:2:27:2 | SSA def(a) | main.go:27:2:27:2 | a | -| main.go:31:9:31:9 | a | main.go:31:9:31:9 | SSA phi(a) | main.go:27:2:27:2 | a | -| main.go:31:12:31:12 | b | main.go:31:9:31:9 | SSA phi(b) | main.go:27:5:27:5 | b | -| main.go:35:3:35:3 | x | main.go:34:11:34:11 | SSA def(x) | main.go:34:11:34:11 | x | -| main.go:40:10:40:10 | x | main.go:39:2:39:2 | SSA def(x) | main.go:39:2:39:2 | x | -| main.go:42:8:42:10 | ptr | main.go:40:2:40:4 | SSA def(ptr) | main.go:40:2:40:4 | ptr | -| main.go:44:12:44:12 | x | main.go:39:2:39:2 | SSA def(x) | main.go:39:2:39:2 | x | -| main.go:47:13:47:18 | implicit read of result | main.go:48:2:48:7 | SSA def(result) | main.go:47:13:47:18 | result | -| main.go:52:14:52:19 | implicit read of result | main.go:52:14:52:19 | SSA def(result) | main.go:52:14:52:19 | result | -| main.go:61:12:61:12 | x | main.go:58:6:58:9 | SSA phi(x) | main.go:57:6:57:6 | x | -| main.go:64:16:64:16 | i | main.go:65:6:65:9 | SSA phi(i) | main.go:64:6:64:6 | i | -| main.go:70:12:70:12 | y | main.go:65:6:65:9 | SSA phi(y) | main.go:63:2:63:2 | y | -| main.go:73:16:73:16 | i | main.go:74:3:74:3 | SSA phi(i) | main.go:73:6:73:6 | i | -| main.go:79:12:79:12 | z | main.go:74:3:74:3 | SSA def(z) | main.go:72:2:72:2 | z | -| main.go:82:18:82:18 | implicit read of a | main.go:84:5:84:5 | SSA def(a) | main.go:82:18:82:18 | a | -| main.go:82:25:82:25 | implicit read of b | main.go:82:25:82:25 | SSA def(b) | main.go:82:25:82:25 | b | -| main.go:84:9:84:9 | x | main.go:83:2:83:2 | SSA def(x) | main.go:83:2:83:2 | x | -| main.go:84:15:84:15 | x | main.go:83:2:83:2 | SSA def(x) | main.go:83:2:83:2 | x | -| main.go:97:2:97:8 | wrapper | main.go:95:22:95:28 | SSA def(wrapper) | main.go:95:22:95:28 | wrapper | +| main.go:15:12:15:12 | x | main.go:13:6:13:10 | SSA def(x) | main.go:13:6:13:6 | x | +| main.go:15:15:15:15 | y | main.go:14:2:14:8 | SSA def(y) | main.go:14:2:14:2 | y | +| main.go:17:3:17:3 | y | main.go:14:2:14:8 | SSA def(y) | main.go:14:2:14:2 | y | +| main.go:19:12:19:12 | x | main.go:13:6:13:10 | SSA def(x) | main.go:13:6:13:6 | x | +| main.go:19:15:19:15 | y | main.go:16:2:18:2 | SSA phi(y) | main.go:14:2:14:2 | y | +| main.go:21:7:21:7 | y | main.go:16:2:18:2 | SSA phi(y) | main.go:14:2:14:2 | y | +| main.go:23:12:23:12 | x | main.go:20:2:22:2 | SSA phi(x) | main.go:13:6:13:6 | x | +| main.go:23:15:23:15 | y | main.go:16:2:18:2 | SSA phi(y) | main.go:14:2:14:2 | y | +| main.go:27:10:27:10 | x | main.go:26:28:32:1 | SSA def(x) | main.go:26:10:26:10 | x | +| main.go:29:10:29:10 | b | main.go:27:2:27:13 | SSA def(b) | main.go:27:5:27:5 | b | +| main.go:29:13:29:13 | a | main.go:27:2:27:13 | SSA def(a) | main.go:27:2:27:2 | a | +| main.go:31:9:31:9 | a | main.go:28:2:30:2 | SSA phi(a) | main.go:27:2:27:2 | a | +| main.go:31:12:31:12 | b | main.go:28:2:30:2 | SSA phi(b) | main.go:27:5:27:5 | b | +| main.go:35:3:35:3 | x | main.go:34:19:36:1 | SSA def(x) | main.go:34:11:34:11 | x | +| main.go:40:10:40:10 | x | main.go:39:2:39:8 | SSA def(x) | main.go:39:2:39:2 | x | +| main.go:42:8:42:10 | ptr | main.go:40:2:40:10 | SSA def(ptr) | main.go:40:2:40:4 | ptr | +| main.go:44:12:44:12 | x | main.go:39:2:39:8 | SSA def(x) | main.go:39:2:39:2 | x | +| main.go:47:25:50:1 | result-read:0 block statement | main.go:48:2:48:12 | SSA def(result) | main.go:47:13:47:18 | result | +| main.go:52:26:54:1 | result-read:0 block statement | main.go:52:26:54:1 | SSA def(result) | main.go:52:14:52:19 | result | +| main.go:61:12:61:12 | x | main.go:58:6:58:11 | SSA phi(x) | main.go:57:6:57:6 | x | +| main.go:64:16:64:16 | i | main.go:64:20:69:2 | SSA phi(i) | main.go:64:6:64:6 | i | +| main.go:70:12:70:12 | y | main.go:64:20:69:2 | SSA phi(y) | main.go:63:2:63:2 | y | +| main.go:73:16:73:16 | i | main.go:73:20:78:2 | SSA phi(i) | main.go:73:6:73:6 | i | +| main.go:79:12:79:12 | z | main.go:74:3:74:7 | SSA def(z) | main.go:72:2:72:2 | z | +| main.go:82:36:86:1 | result-read:0 block statement | main.go:84:2:84:15 | SSA def(a) | main.go:82:18:82:18 | a | +| main.go:82:36:86:1 | result-read:1 block statement | main.go:82:36:86:1 | SSA def(b) | main.go:82:25:82:25 | b | +| main.go:84:9:84:9 | x | main.go:83:2:83:8 | SSA def(x) | main.go:83:2:83:2 | x | +| main.go:84:15:84:15 | x | main.go:83:2:83:8 | SSA def(x) | main.go:83:2:83:2 | x | +| main.go:97:2:97:8 | wrapper | main.go:95:47:101:1 | SSA def(wrapper) | main.go:95:22:95:28 | wrapper | | main.go:100:9:100:9 | x | main.go:97:2:99:3 | SSA def(x) | main.go:96:2:96:2 | x | -| main.go:105:2:105:8 | wrapper | main.go:103:20:103:26 | SSA def(wrapper) | main.go:103:20:103:26 | wrapper | +| main.go:105:2:105:8 | wrapper | main.go:103:45:110:1 | SSA def(wrapper) | main.go:103:20:103:26 | wrapper | | main.go:106:8:106:8 | x | main.go:105:16:108:2 | SSA def(x) | main.go:104:2:104:2 | x | -| main.go:107:7:107:7 | y | main.go:106:3:106:3 | SSA def(y) | main.go:106:3:106:3 | y | -| main.go:109:9:109:9 | x | main.go:104:2:104:2 | SSA def(x) | main.go:104:2:104:2 | x | -| main.go:114:2:114:8 | wrapper | main.go:112:29:112:35 | SSA def(wrapper) | main.go:112:29:112:35 | wrapper | +| main.go:107:7:107:7 | y | main.go:106:3:106:8 | SSA def(y) | main.go:106:3:106:3 | y | +| main.go:109:9:109:9 | x | main.go:104:2:104:7 | SSA def(x) | main.go:104:2:104:2 | x | +| main.go:114:2:114:8 | wrapper | main.go:112:54:119:1 | SSA def(wrapper) | main.go:112:29:112:35 | wrapper | | main.go:115:8:115:8 | x | main.go:114:16:117:2 | SSA def(x) | main.go:113:2:113:2 | x | -| main.go:116:7:116:7 | y | main.go:115:3:115:3 | SSA def(y) | main.go:115:3:115:3 | y | +| main.go:116:7:116:7 | y | main.go:115:3:115:12 | SSA def(y) | main.go:115:3:115:3 | y | | main.go:118:9:118:9 | x | main.go:114:2:117:3 | SSA def(x) | main.go:113:2:113:2 | x | -| main.go:135:2:135:2 | p | main.go:135:2:135:2 | SSA phi(p) | main.go:128:6:128:6 | p | -| main.go:137:12:137:12 | p | main.go:135:2:135:2 | SSA phi(p) | main.go:128:6:128:6 | p | -| main.go:137:17:137:17 | p | main.go:135:2:135:2 | SSA phi(p) | main.go:128:6:128:6 | p | -| main.go:137:24:137:24 | p | main.go:135:2:135:2 | SSA phi(p) | main.go:128:6:128:6 | p | +| main.go:135:2:135:2 | p | main.go:129:2:133:2 | SSA phi(p) | main.go:128:6:128:6 | p | +| main.go:137:12:137:12 | p | main.go:129:2:133:2 | SSA phi(p) | main.go:128:6:128:6 | p | +| main.go:137:17:137:17 | p | main.go:129:2:133:2 | SSA phi(p) | main.go:128:6:128:6 | p | +| main.go:137:24:137:24 | p | main.go:129:2:133:2 | SSA phi(p) | main.go:128:6:128:6 | p | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected b/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected index 3ff2faf872c4..3e74f45de694 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected @@ -1,51 +1,51 @@ -| main.go:13:6:13:6 | SSA def(x) | -| main.go:14:2:14:2 | SSA def(y) | -| main.go:17:3:17:3 | SSA def(y) | -| main.go:19:2:19:10 | SSA phi(y) | -| main.go:21:3:21:3 | SSA def(x) | -| main.go:23:2:23:10 | SSA phi(x) | -| main.go:26:10:26:10 | SSA def(x) | -| main.go:27:2:27:2 | SSA def(a) | -| main.go:27:5:27:5 | SSA def(b) | -| main.go:29:3:29:3 | SSA def(a) | -| main.go:29:6:29:6 | SSA def(b) | -| main.go:31:9:31:9 | SSA phi(a) | -| main.go:31:9:31:9 | SSA phi(b) | -| main.go:34:11:34:11 | SSA def(x) | -| main.go:39:2:39:2 | SSA def(x) | -| main.go:40:2:40:4 | SSA def(ptr) | -| main.go:48:2:48:7 | SSA def(result) | -| main.go:52:14:52:19 | SSA def(result) | -| main.go:57:6:57:6 | SSA def(x) | -| main.go:58:6:58:9 | SSA phi(x) | -| main.go:59:3:59:3 | SSA def(x) | -| main.go:63:2:63:2 | SSA def(y) | -| main.go:64:6:64:6 | SSA def(i) | +| main.go:13:6:13:10 | SSA def(x) | +| main.go:14:2:14:8 | SSA def(y) | +| main.go:16:2:18:2 | SSA phi(y) | +| main.go:17:3:17:9 | SSA def(y) | +| main.go:20:2:22:2 | SSA phi(x) | +| main.go:21:3:21:7 | SSA def(x) | +| main.go:26:28:32:1 | SSA def(x) | +| main.go:27:2:27:13 | SSA def(a) | +| main.go:27:2:27:13 | SSA def(b) | +| main.go:28:2:30:2 | SSA phi(a) | +| main.go:28:2:30:2 | SSA phi(b) | +| main.go:29:3:29:13 | SSA def(a) | +| main.go:29:3:29:13 | SSA def(b) | +| main.go:34:19:36:1 | SSA def(x) | +| main.go:39:2:39:8 | SSA def(x) | +| main.go:40:2:40:10 | SSA def(ptr) | +| main.go:48:2:48:12 | SSA def(result) | +| main.go:52:26:54:1 | SSA def(result) | +| main.go:57:6:57:10 | SSA def(x) | +| main.go:58:6:58:11 | SSA phi(x) | +| main.go:59:3:59:7 | SSA def(x) | +| main.go:63:2:63:7 | SSA def(y) | +| main.go:64:6:64:11 | SSA def(i) | | main.go:64:16:64:18 | SSA def(i) | -| main.go:65:6:65:9 | SSA phi(i) | -| main.go:65:6:65:9 | SSA phi(y) | -| main.go:68:3:68:3 | SSA def(y) | -| main.go:73:6:73:6 | SSA def(i) | +| main.go:64:20:69:2 | SSA phi(i) | +| main.go:64:20:69:2 | SSA phi(y) | +| main.go:68:3:68:7 | SSA def(y) | +| main.go:73:6:73:11 | SSA def(i) | | main.go:73:16:73:18 | SSA def(i) | -| main.go:74:3:74:3 | SSA def(z) | -| main.go:74:3:74:3 | SSA phi(i) | -| main.go:82:25:82:25 | SSA def(b) | -| main.go:83:2:83:2 | SSA def(x) | -| main.go:84:5:84:5 | SSA def(a) | -| main.go:95:22:95:28 | SSA def(wrapper) | -| main.go:96:2:96:2 | SSA def(x) | +| main.go:73:20:78:2 | SSA phi(i) | +| main.go:74:3:74:7 | SSA def(z) | +| main.go:82:36:86:1 | SSA def(b) | +| main.go:83:2:83:8 | SSA def(x) | +| main.go:84:2:84:15 | SSA def(a) | +| main.go:95:47:101:1 | SSA def(wrapper) | +| main.go:96:2:96:7 | SSA def(x) | | main.go:97:2:99:3 | SSA def(x) | -| main.go:98:3:98:3 | SSA def(x) | -| main.go:103:20:103:26 | SSA def(wrapper) | -| main.go:104:2:104:2 | SSA def(x) | +| main.go:98:3:98:7 | SSA def(x) | +| main.go:103:45:110:1 | SSA def(wrapper) | +| main.go:104:2:104:7 | SSA def(x) | | main.go:105:16:108:2 | SSA def(x) | -| main.go:106:3:106:3 | SSA def(y) | -| main.go:112:29:112:35 | SSA def(wrapper) | -| main.go:113:2:113:2 | SSA def(x) | +| main.go:106:3:106:8 | SSA def(y) | +| main.go:112:54:119:1 | SSA def(wrapper) | +| main.go:113:2:113:7 | SSA def(x) | | main.go:114:2:117:3 | SSA def(x) | | main.go:114:16:117:2 | SSA def(x) | -| main.go:115:3:115:3 | SSA def(y) | -| main.go:116:3:116:3 | SSA def(x) | -| main.go:130:3:130:3 | SSA def(p) | -| main.go:132:3:132:3 | SSA def(p) | -| main.go:135:2:135:2 | SSA phi(p) | +| main.go:115:3:115:12 | SSA def(y) | +| main.go:116:3:116:7 | SSA def(x) | +| main.go:129:2:133:2 | SSA phi(p) | +| main.go:130:3:130:24 | SSA def(p) | +| main.go:132:3:132:24 | SSA def(p) | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaWithFields.expected b/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaWithFields.expected index 2c43f05257aa..f9fbaada6dfd 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaWithFields.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaWithFields.expected @@ -1,58 +1,58 @@ -| main.go:13:6:13:6 | (SSA def(x)) | x | -| main.go:14:2:14:2 | (SSA def(y)) | y | -| main.go:17:3:17:3 | (SSA def(y)) | y | -| main.go:19:2:19:10 | (SSA phi(y)) | y | -| main.go:21:3:21:3 | (SSA def(x)) | x | -| main.go:23:2:23:10 | (SSA phi(x)) | x | -| main.go:26:10:26:10 | (SSA def(x)) | x | -| main.go:27:2:27:2 | (SSA def(a)) | a | -| main.go:27:5:27:5 | (SSA def(b)) | b | -| main.go:29:3:29:3 | (SSA def(a)) | a | -| main.go:29:6:29:6 | (SSA def(b)) | b | -| main.go:31:9:31:9 | (SSA phi(a)) | a | -| main.go:31:9:31:9 | (SSA phi(b)) | b | -| main.go:34:11:34:11 | (SSA def(x)) | x | -| main.go:39:2:39:2 | (SSA def(x)) | x | -| main.go:40:2:40:4 | (SSA def(ptr)) | ptr | -| main.go:48:2:48:7 | (SSA def(result)) | result | -| main.go:52:14:52:19 | (SSA def(result)) | result | -| main.go:57:6:57:6 | (SSA def(x)) | x | -| main.go:58:6:58:9 | (SSA phi(x)) | x | -| main.go:59:3:59:3 | (SSA def(x)) | x | -| main.go:63:2:63:2 | (SSA def(y)) | y | -| main.go:64:6:64:6 | (SSA def(i)) | i | +| main.go:13:6:13:10 | (SSA def(x)) | x | +| main.go:14:2:14:8 | (SSA def(y)) | y | +| main.go:16:2:18:2 | (SSA phi(y)) | y | +| main.go:17:3:17:9 | (SSA def(y)) | y | +| main.go:20:2:22:2 | (SSA phi(x)) | x | +| main.go:21:3:21:7 | (SSA def(x)) | x | +| main.go:26:28:32:1 | (SSA def(x)) | x | +| main.go:27:2:27:13 | (SSA def(a)) | a | +| main.go:27:2:27:13 | (SSA def(b)) | b | +| main.go:28:2:30:2 | (SSA phi(a)) | a | +| main.go:28:2:30:2 | (SSA phi(b)) | b | +| main.go:29:3:29:13 | (SSA def(a)) | a | +| main.go:29:3:29:13 | (SSA def(b)) | b | +| main.go:34:19:36:1 | (SSA def(x)) | x | +| main.go:39:2:39:8 | (SSA def(x)) | x | +| main.go:40:2:40:10 | (SSA def(ptr)) | ptr | +| main.go:48:2:48:12 | (SSA def(result)) | result | +| main.go:52:26:54:1 | (SSA def(result)) | result | +| main.go:57:6:57:10 | (SSA def(x)) | x | +| main.go:58:6:58:11 | (SSA phi(x)) | x | +| main.go:59:3:59:7 | (SSA def(x)) | x | +| main.go:63:2:63:7 | (SSA def(y)) | y | +| main.go:64:6:64:11 | (SSA def(i)) | i | | main.go:64:16:64:18 | (SSA def(i)) | i | -| main.go:65:6:65:9 | (SSA phi(i)) | i | -| main.go:65:6:65:9 | (SSA phi(y)) | y | -| main.go:68:3:68:3 | (SSA def(y)) | y | -| main.go:73:6:73:6 | (SSA def(i)) | i | +| main.go:64:20:69:2 | (SSA phi(i)) | i | +| main.go:64:20:69:2 | (SSA phi(y)) | y | +| main.go:68:3:68:7 | (SSA def(y)) | y | +| main.go:73:6:73:11 | (SSA def(i)) | i | | main.go:73:16:73:18 | (SSA def(i)) | i | -| main.go:74:3:74:3 | (SSA def(z)) | z | -| main.go:74:3:74:3 | (SSA phi(i)) | i | -| main.go:82:25:82:25 | (SSA def(b)) | b | -| main.go:83:2:83:2 | (SSA def(x)) | x | -| main.go:84:5:84:5 | (SSA def(a)) | a | -| main.go:95:22:95:28 | (SSA def(wrapper)) | wrapper | -| main.go:95:22:95:28 | (SSA def(wrapper)).s | wrapper.s | -| main.go:96:2:96:2 | (SSA def(x)) | x | +| main.go:73:20:78:2 | (SSA phi(i)) | i | +| main.go:74:3:74:7 | (SSA def(z)) | z | +| main.go:82:36:86:1 | (SSA def(b)) | b | +| main.go:83:2:83:8 | (SSA def(x)) | x | +| main.go:84:2:84:15 | (SSA def(a)) | a | +| main.go:95:47:101:1 | (SSA def(wrapper)) | wrapper | +| main.go:95:47:101:1 | (SSA def(wrapper)).s | wrapper.s | +| main.go:96:2:96:7 | (SSA def(x)) | x | | main.go:97:2:99:3 | (SSA def(x)) | x | -| main.go:98:3:98:3 | (SSA def(x)) | x | -| main.go:103:20:103:26 | (SSA def(wrapper)) | wrapper | -| main.go:103:20:103:26 | (SSA def(wrapper)).s | wrapper.s | -| main.go:104:2:104:2 | (SSA def(x)) | x | +| main.go:98:3:98:7 | (SSA def(x)) | x | +| main.go:103:45:110:1 | (SSA def(wrapper)) | wrapper | +| main.go:103:45:110:1 | (SSA def(wrapper)).s | wrapper.s | +| main.go:104:2:104:7 | (SSA def(x)) | x | | main.go:105:16:108:2 | (SSA def(x)) | x | -| main.go:106:3:106:3 | (SSA def(y)) | y | -| main.go:112:29:112:35 | (SSA def(wrapper)) | wrapper | -| main.go:112:29:112:35 | (SSA def(wrapper)).s | wrapper.s | -| main.go:113:2:113:2 | (SSA def(x)) | x | +| main.go:106:3:106:8 | (SSA def(y)) | y | +| main.go:112:54:119:1 | (SSA def(wrapper)) | wrapper | +| main.go:112:54:119:1 | (SSA def(wrapper)).s | wrapper.s | +| main.go:113:2:113:7 | (SSA def(x)) | x | | main.go:114:2:117:3 | (SSA def(x)) | x | | main.go:114:16:117:2 | (SSA def(x)) | x | -| main.go:115:3:115:3 | (SSA def(y)) | y | -| main.go:116:3:116:3 | (SSA def(x)) | x | -| main.go:130:3:130:3 | (SSA def(p)) | p | -| main.go:132:3:132:3 | (SSA def(p)) | p | -| main.go:135:2:135:2 | (SSA phi(p)) | p | -| main.go:135:2:135:2 | (SSA phi(p)).a | p.a | -| main.go:135:2:135:2 | (SSA phi(p)).b | p.b | -| main.go:135:2:135:2 | (SSA phi(p)).b.a | p.b.a | -| main.go:135:2:135:2 | (SSA phi(p)).c | p.c | +| main.go:115:3:115:12 | (SSA def(y)) | y | +| main.go:116:3:116:7 | (SSA def(x)) | x | +| main.go:129:2:133:2 | (SSA phi(p)) | p | +| main.go:129:2:133:2 | (SSA phi(p)).a | p.a | +| main.go:129:2:133:2 | (SSA phi(p)).b | p.b | +| main.go:129:2:133:2 | (SSA phi(p)).b.a | p.b.a | +| main.go:129:2:133:2 | (SSA phi(p)).c | p.c | +| main.go:130:3:130:24 | (SSA def(p)) | p | +| main.go:132:3:132:24 | (SSA def(p)) | p | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected b/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected index 6149ddfbb54a..5fc981c8cb08 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected @@ -1,54 +1,54 @@ -| main.go:13:6:13:6 | assignment to x | main.go:13:6:13:6 | x | main.go:13:6:13:6 | zero value for x | -| main.go:14:2:14:2 | assignment to y | main.go:14:2:14:2 | y | main.go:14:7:14:8 | 23 | -| main.go:17:3:17:3 | assignment to y | main.go:14:2:14:2 | y | main.go:17:3:17:9 | ... += ... | -| main.go:21:3:21:3 | assignment to x | main.go:13:6:13:6 | x | main.go:21:7:21:7 | y | -| main.go:26:10:26:10 | initialization of x | main.go:26:10:26:10 | x | main.go:26:10:26:10 | argument corresponding to x | -| main.go:27:2:27:2 | assignment to a | main.go:27:2:27:2 | a | main.go:27:10:27:10 | x | -| main.go:27:5:27:5 | assignment to b | main.go:27:5:27:5 | b | main.go:27:13:27:13 | 0 | -| main.go:29:3:29:3 | assignment to a | main.go:27:2:27:2 | a | main.go:29:10:29:10 | b | -| main.go:29:6:29:6 | assignment to b | main.go:27:5:27:5 | b | main.go:29:13:29:13 | a | -| main.go:34:11:34:11 | initialization of x | main.go:34:11:34:11 | x | main.go:34:11:34:11 | argument corresponding to x | -| main.go:39:2:39:2 | assignment to x | main.go:39:2:39:2 | x | main.go:39:7:39:8 | 23 | -| main.go:40:2:40:4 | assignment to ptr | main.go:40:2:40:4 | ptr | main.go:40:9:40:10 | &... | -| main.go:47:13:47:18 | initialization of result | main.go:47:13:47:18 | result | main.go:47:13:47:18 | zero value for result | -| main.go:48:2:48:7 | assignment to result | main.go:47:13:47:18 | result | main.go:48:11:48:12 | 42 | -| main.go:52:14:52:19 | initialization of result | main.go:52:14:52:19 | result | main.go:52:14:52:19 | zero value for result | -| main.go:57:6:57:6 | assignment to x | main.go:57:6:57:6 | x | main.go:57:6:57:6 | zero value for x | -| main.go:59:3:59:3 | assignment to x | main.go:57:6:57:6 | x | main.go:59:7:59:7 | 2 | -| main.go:63:2:63:2 | assignment to y | main.go:63:2:63:2 | y | main.go:63:7:63:7 | 1 | -| main.go:64:6:64:6 | assignment to i | main.go:64:6:64:6 | i | main.go:64:11:64:11 | 0 | -| main.go:64:16:64:18 | increment statement | main.go:64:6:64:6 | i | main.go:64:16:64:18 | rhs of increment statement | -| main.go:68:3:68:3 | assignment to y | main.go:63:2:63:2 | y | main.go:68:7:68:7 | 2 | -| main.go:72:2:72:2 | assignment to z | main.go:72:2:72:2 | z | main.go:72:7:72:7 | 1 | -| main.go:73:6:73:6 | assignment to i | main.go:73:6:73:6 | i | main.go:73:11:73:11 | 0 | -| main.go:73:16:73:18 | increment statement | main.go:73:6:73:6 | i | main.go:73:16:73:18 | rhs of increment statement | -| main.go:74:3:74:3 | assignment to z | main.go:72:2:72:2 | z | main.go:74:7:74:7 | 2 | -| main.go:82:18:82:18 | initialization of a | main.go:82:18:82:18 | a | main.go:82:18:82:18 | zero value for a | -| main.go:82:25:82:25 | initialization of b | main.go:82:25:82:25 | b | main.go:82:25:82:25 | zero value for b | -| main.go:83:2:83:2 | assignment to x | main.go:83:2:83:2 | x | main.go:83:7:83:8 | 23 | -| main.go:84:2:84:2 | assignment to x | main.go:83:2:83:2 | x | main.go:84:9:84:12 | ...+... | -| main.go:84:5:84:5 | assignment to a | main.go:82:18:82:18 | a | main.go:84:15:84:15 | x | -| main.go:93:15:93:16 | initialization of cb | main.go:93:15:93:16 | cb | main.go:93:15:93:16 | argument corresponding to cb | -| main.go:95:22:95:28 | initialization of wrapper | main.go:95:22:95:28 | wrapper | main.go:95:22:95:28 | argument corresponding to wrapper | -| main.go:96:2:96:2 | assignment to x | main.go:96:2:96:2 | x | main.go:96:7:96:7 | 0 | -| main.go:98:3:98:3 | assignment to x | main.go:96:2:96:2 | x | main.go:98:7:98:7 | 1 | -| main.go:103:20:103:26 | initialization of wrapper | main.go:103:20:103:26 | wrapper | main.go:103:20:103:26 | argument corresponding to wrapper | -| main.go:104:2:104:2 | assignment to x | main.go:104:2:104:2 | x | main.go:104:7:104:7 | 0 | -| main.go:106:3:106:3 | assignment to y | main.go:106:3:106:3 | y | main.go:106:8:106:8 | x | -| main.go:112:29:112:35 | initialization of wrapper | main.go:112:29:112:35 | wrapper | main.go:112:29:112:35 | argument corresponding to wrapper | -| main.go:113:2:113:2 | assignment to x | main.go:113:2:113:2 | x | main.go:113:7:113:7 | 0 | -| main.go:115:3:115:3 | assignment to y | main.go:115:3:115:3 | y | main.go:115:8:115:12 | ...+... | -| main.go:116:3:116:3 | assignment to x | main.go:113:2:113:2 | x | main.go:116:7:116:7 | y | -| main.go:128:6:128:6 | assignment to p | main.go:128:6:128:6 | p | main.go:128:6:128:6 | zero value for p | -| main.go:130:3:130:3 | assignment to p | main.go:128:6:128:6 | p | main.go:130:7:130:24 | struct literal | -| main.go:130:9:130:9 | init of 2 | main.go:122:2:122:2 | a | main.go:130:9:130:9 | 2 | -| main.go:130:12:130:18 | init of struct literal | main.go:123:2:123:2 | b | main.go:130:12:130:18 | struct literal | -| main.go:130:14:130:14 | init of 1 | main.go:89:2:89:2 | a | main.go:130:14:130:14 | 1 | -| main.go:130:17:130:17 | init of 5 | main.go:90:2:90:2 | b | main.go:130:17:130:17 | 5 | -| main.go:130:21:130:23 | init of 'n' | main.go:124:2:124:2 | c | main.go:130:21:130:23 | 'n' | -| main.go:132:3:132:3 | assignment to p | main.go:128:6:128:6 | p | main.go:132:7:132:24 | struct literal | -| main.go:132:9:132:9 | init of 3 | main.go:122:2:122:2 | a | main.go:132:9:132:9 | 3 | -| main.go:132:12:132:18 | init of struct literal | main.go:123:2:123:2 | b | main.go:132:12:132:18 | struct literal | -| main.go:132:14:132:14 | init of 4 | main.go:89:2:89:2 | a | main.go:132:14:132:14 | 4 | -| main.go:132:17:132:17 | init of 5 | main.go:90:2:90:2 | b | main.go:132:17:132:17 | 5 | -| main.go:132:21:132:23 | init of '2' | main.go:124:2:124:2 | c | main.go:132:21:132:23 | '2' | +| main.go:13:6:13:10 | assign:0 value declaration specifier | main.go:13:6:13:6 | x | main.go:13:6:13:10 | zero-init:0 value declaration specifier | +| main.go:14:2:14:8 | assign:0 ... := ... | main.go:14:2:14:2 | y | main.go:14:7:14:8 | 23 | +| main.go:17:3:17:9 | assign:0 ... += ... | main.go:14:2:14:2 | y | main.go:17:3:17:9 | compound-rhs ... += ... | +| main.go:21:3:21:7 | assign:0 ... = ... | main.go:13:6:13:6 | x | main.go:21:7:21:7 | y | +| main.go:26:28:32:1 | param-init:0 block statement | main.go:26:10:26:10 | x | main.go:26:28:32:1 | arg:0 block statement | +| main.go:27:2:27:13 | assign:0 ... := ... | main.go:27:2:27:2 | a | main.go:27:10:27:10 | x | +| main.go:27:2:27:13 | assign:1 ... := ... | main.go:27:5:27:5 | b | main.go:27:13:27:13 | 0 | +| main.go:29:3:29:13 | assign:0 ... = ... | main.go:27:2:27:2 | a | main.go:29:10:29:10 | b | +| main.go:29:3:29:13 | assign:1 ... = ... | main.go:27:5:27:5 | b | main.go:29:13:29:13 | a | +| main.go:34:19:36:1 | param-init:0 block statement | main.go:34:11:34:11 | x | main.go:34:19:36:1 | arg:0 block statement | +| main.go:39:2:39:8 | assign:0 ... := ... | main.go:39:2:39:2 | x | main.go:39:7:39:8 | 23 | +| main.go:40:2:40:10 | assign:0 ... := ... | main.go:40:2:40:4 | ptr | main.go:40:9:40:10 | &... | +| main.go:47:25:50:1 | result-init:0 block statement | main.go:47:13:47:18 | result | main.go:47:25:50:1 | result-zero-init:0 block statement | +| main.go:48:2:48:12 | assign:0 ... = ... | main.go:47:13:47:18 | result | main.go:48:11:48:12 | 42 | +| main.go:52:26:54:1 | result-init:0 block statement | main.go:52:14:52:19 | result | main.go:52:26:54:1 | result-zero-init:0 block statement | +| main.go:57:6:57:10 | assign:0 value declaration specifier | main.go:57:6:57:6 | x | main.go:57:6:57:10 | zero-init:0 value declaration specifier | +| main.go:59:3:59:7 | assign:0 ... = ... | main.go:57:6:57:6 | x | main.go:59:7:59:7 | 2 | +| main.go:63:2:63:7 | assign:0 ... := ... | main.go:63:2:63:2 | y | main.go:63:7:63:7 | 1 | +| main.go:64:6:64:11 | assign:0 ... := ... | main.go:64:6:64:6 | i | main.go:64:11:64:11 | 0 | +| main.go:64:16:64:18 | increment statement | main.go:64:6:64:6 | i | main.go:64:16:64:18 | incdec-rhs increment statement | +| main.go:68:3:68:7 | assign:0 ... = ... | main.go:63:2:63:2 | y | main.go:68:7:68:7 | 2 | +| main.go:72:2:72:7 | assign:0 ... := ... | main.go:72:2:72:2 | z | main.go:72:7:72:7 | 1 | +| main.go:73:6:73:11 | assign:0 ... := ... | main.go:73:6:73:6 | i | main.go:73:11:73:11 | 0 | +| main.go:73:16:73:18 | increment statement | main.go:73:6:73:6 | i | main.go:73:16:73:18 | incdec-rhs increment statement | +| main.go:74:3:74:7 | assign:0 ... = ... | main.go:72:2:72:2 | z | main.go:74:7:74:7 | 2 | +| main.go:82:36:86:1 | result-init:0 block statement | main.go:82:18:82:18 | a | main.go:82:36:86:1 | result-zero-init:0 block statement | +| main.go:82:36:86:1 | result-init:1 block statement | main.go:82:25:82:25 | b | main.go:82:36:86:1 | result-zero-init:1 block statement | +| main.go:83:2:83:8 | assign:0 ... := ... | main.go:83:2:83:2 | x | main.go:83:7:83:8 | 23 | +| main.go:84:2:84:15 | assign:0 ... = ... | main.go:83:2:83:2 | x | main.go:84:9:84:12 | ...+... | +| main.go:84:2:84:15 | assign:1 ... = ... | main.go:82:18:82:18 | a | main.go:84:15:84:15 | x | +| main.go:93:26:93:27 | param-init:0 block statement | main.go:93:15:93:16 | cb | main.go:93:26:93:27 | arg:0 block statement | +| main.go:95:47:101:1 | param-init:0 block statement | main.go:95:22:95:28 | wrapper | main.go:95:47:101:1 | arg:0 block statement | +| main.go:96:2:96:7 | assign:0 ... := ... | main.go:96:2:96:2 | x | main.go:96:7:96:7 | 0 | +| main.go:98:3:98:7 | assign:0 ... = ... | main.go:96:2:96:2 | x | main.go:98:7:98:7 | 1 | +| main.go:103:45:110:1 | param-init:0 block statement | main.go:103:20:103:26 | wrapper | main.go:103:45:110:1 | arg:0 block statement | +| main.go:104:2:104:7 | assign:0 ... := ... | main.go:104:2:104:2 | x | main.go:104:7:104:7 | 0 | +| main.go:106:3:106:8 | assign:0 ... := ... | main.go:106:3:106:3 | y | main.go:106:8:106:8 | x | +| main.go:112:54:119:1 | param-init:0 block statement | main.go:112:29:112:35 | wrapper | main.go:112:54:119:1 | arg:0 block statement | +| main.go:113:2:113:7 | assign:0 ... := ... | main.go:113:2:113:2 | x | main.go:113:7:113:7 | 0 | +| main.go:115:3:115:12 | assign:0 ... := ... | main.go:115:3:115:3 | y | main.go:115:8:115:12 | ...+... | +| main.go:116:3:116:7 | assign:0 ... = ... | main.go:113:2:113:2 | x | main.go:116:7:116:7 | y | +| main.go:128:6:128:8 | assign:0 value declaration specifier | main.go:128:6:128:6 | p | main.go:128:6:128:8 | zero-init:0 value declaration specifier | +| main.go:130:3:130:24 | assign:0 ... = ... | main.go:128:6:128:6 | p | main.go:130:7:130:24 | struct literal | +| main.go:130:9:130:9 | lit-init 2 | main.go:122:2:122:2 | a | main.go:130:9:130:9 | 2 | +| main.go:130:12:130:18 | lit-init struct literal | main.go:123:2:123:2 | b | main.go:130:12:130:18 | struct literal | +| main.go:130:14:130:14 | lit-init 1 | main.go:89:2:89:2 | a | main.go:130:14:130:14 | 1 | +| main.go:130:17:130:17 | lit-init 5 | main.go:90:2:90:2 | b | main.go:130:17:130:17 | 5 | +| main.go:130:21:130:23 | lit-init 'n' | main.go:124:2:124:2 | c | main.go:130:21:130:23 | 'n' | +| main.go:132:3:132:24 | assign:0 ... = ... | main.go:128:6:128:6 | p | main.go:132:7:132:24 | struct literal | +| main.go:132:9:132:9 | lit-init 3 | main.go:122:2:122:2 | a | main.go:132:9:132:9 | 3 | +| main.go:132:12:132:18 | lit-init struct literal | main.go:123:2:123:2 | b | main.go:132:12:132:18 | struct literal | +| main.go:132:14:132:14 | lit-init 4 | main.go:89:2:89:2 | a | main.go:132:14:132:14 | 4 | +| main.go:132:17:132:17 | lit-init 5 | main.go:90:2:90:2 | b | main.go:132:17:132:17 | 5 | +| main.go:132:21:132:23 | lit-init '2' | main.go:124:2:124:2 | c | main.go:132:21:132:23 | '2' | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected b/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected index 2e6b3c855c36..5a37307af191 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected @@ -15,15 +15,15 @@ | main.go:40:10:40:10 | x | main.go:39:2:39:2 | x | | main.go:42:8:42:10 | ptr | main.go:40:2:40:4 | ptr | | main.go:44:12:44:12 | x | main.go:39:2:39:2 | x | -| main.go:47:13:47:18 | implicit read of result | main.go:47:13:47:18 | result | -| main.go:52:14:52:19 | implicit read of result | main.go:52:14:52:19 | result | +| main.go:47:25:50:1 | result-read:0 block statement | main.go:47:13:47:18 | result | +| main.go:52:26:54:1 | result-read:0 block statement | main.go:52:14:52:19 | result | | main.go:61:12:61:12 | x | main.go:57:6:57:6 | x | | main.go:64:16:64:16 | i | main.go:64:6:64:6 | i | | main.go:70:12:70:12 | y | main.go:63:2:63:2 | y | | main.go:73:16:73:16 | i | main.go:73:6:73:6 | i | | main.go:79:12:79:12 | z | main.go:72:2:72:2 | z | -| main.go:82:18:82:18 | implicit read of a | main.go:82:18:82:18 | a | -| main.go:82:25:82:25 | implicit read of b | main.go:82:25:82:25 | b | +| main.go:82:36:86:1 | result-read:0 block statement | main.go:82:18:82:18 | a | +| main.go:82:36:86:1 | result-read:1 block statement | main.go:82:25:82:25 | b | | main.go:84:9:84:9 | x | main.go:83:2:83:2 | x | | main.go:84:15:84:15 | x | main.go:83:2:83:2 | x | | main.go:97:2:97:8 | wrapper | main.go:95:22:95:28 | wrapper | From 66eaeebd1e690f2cbff2166f464d5ccb825acf87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 21:58:49 +0000 Subject: [PATCH 5/5] Migrate Go type switches to shared CFG switch model (Option B) --- .../go/controlflow/ControlFlowGraphShared.qll | 210 ++++++------------ go/ql/lib/semmle/go/controlflow/IR.qll | 22 +- .../ControlFlowNode_getASuccessor.expected | 73 +++--- 3 files changed, 128 insertions(+), 177 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 186e06df8954..386557172105 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -68,12 +68,19 @@ module GoCfg { or e.getParent*() = any(Go::ArrayTypeExpr ate).getLength() or - // The body block of an expression switch is transparent: the shared - // switch model wires control flow directly from the switch to its case - // clauses (in control-flow order) and between cases, so the enclosing - // block must not introduce its own nodes or default left-to-right - // sequencing of the case clauses. - e = any(Go::ExpressionSwitchStmt sw).getBody() + // The body block of a switch (expression or type) is transparent: the + // shared switch model wires control flow directly from the switch to its + // case clauses (in control-flow order) and between cases, so the + // enclosing block must not introduce its own nodes or default + // left-to-right sequencing of the case clauses. + e = any(Go::SwitchStmt sw).getBody() + or + // The test statement of a type switch (`y := x.(type)` or the bare + // `x.(type)` expression statement) is transparent: the shared switch + // model evaluates the underlying type-assertion expression directly as + // the switch expression (see `Switch.getExpr`), so the wrapping + // statement must not introduce its own assignment or expression nodes. + e = any(Go::TypeSwitchStmt ts).getTest() } AstNode getChild(AstNode n, int index) { @@ -82,13 +89,18 @@ module GoCfg { not skipCfg(n) and result = n.getChild(index) or - // The body block of an expression switch is transparent (see `skipCfg`), - // so it is not itself a child and contributes no children. Expose the - // case clauses directly as children of the switch instead, so that the - // AST child chain stays connected for abrupt-completion propagation - // (e.g. a panicking call in a case body reaching the enclosing - // function's exceptional exit). - result = n.(Go::ExpressionSwitchStmt).getBody().getChild(index) + // The body block of a switch (expression or type) is transparent (see + // `skipCfg`), so it is not itself a child and contributes no children. + // Expose the case clauses directly as children of the switch instead, + // so that the AST child chain stays connected for abrupt-completion + // propagation (e.g. a panicking call in a case body reaching the + // enclosing function's exceptional exit). + result = n.(Go::SwitchStmt).getBody().getChild(index) + or + // The type-switch test statement is transparent (see `skipCfg`), so + // expose the underlying type-assertion expression directly as a child + // of the type switch, keeping the AST child chain connected. + result = n.(Go::TypeSwitchStmt).getExpr() and index = 1 ) and not skipCfg(result) } @@ -141,7 +153,17 @@ module GoCfg { } } - class ExprStmt = Go::ExprStmt; + class ExprStmt extends Stmt instanceof Go::ExprStmt { + // The `x.(type)` test statement of a type switch is transparent (see + // `skipCfg`): the shared switch model evaluates the underlying + // type-assertion expression directly as the switch expression. It must + // therefore not be treated as an ordinary expression statement, whose + // value would otherwise be propagated from the expression to the + // statement (creating a spurious flow into the transparent wrapper). + ExprStmt() { not this = any(Go::TypeSwitchStmt ts).getTest() } + + Expr getExpr() { result = Go::ExprStmt.super.getExpr() } + } class IfStmt extends Stmt { IfStmt() { this instanceof Go::IfStmt } @@ -234,11 +256,19 @@ module GoCfg { } class Switch extends AstNode { - Switch() { this instanceof Go::ExpressionSwitchStmt } + Switch() { this instanceof Go::SwitchStmt } - Expr getExpr() { result = this.(Go::ExpressionSwitchStmt).getExpr() } + Expr getExpr() { + result = this.(Go::ExpressionSwitchStmt).getExpr() + or + // For a type switch the "switch expression" is the type-assertion + // expression `x.(type)`; evaluating it directly (rather than the + // wrapping `y := x.(type)` statement) lets the shared switch model + // drive the per-case type tests. + result = this.(Go::TypeSwitchStmt).getExpr() + } - Case getCase(int index) { result = this.(Go::ExpressionSwitchStmt).getCase(index) } + Case getCase(int index) { result = this.(Go::SwitchStmt).getCase(index) } Stmt getStmt(int index) { // Go nests each case clause's body statements under the clause rather @@ -256,7 +286,7 @@ module GoCfg { } class Case extends AstNode { - Case() { this = any(Go::ExpressionSwitchStmt sw).getACase() } + Case() { this = any(Go::SwitchStmt sw).getACase() } AstNode getPattern(int index) { result = this.(Go::CaseClause).getExpr(index) } @@ -270,7 +300,7 @@ module GoCfg { } /** Gets the initializer of `switch` statement `switch`, if any. */ - AstNode getSwitchInit(Switch switch) { result = switch.(Go::ExpressionSwitchStmt).getInit() } + AstNode getSwitchInit(Switch switch) { result = switch.(Go::SwitchStmt).getInit() } /** * Go has no implicit fall-through between case clauses; a case that runs to @@ -287,12 +317,10 @@ module GoCfg { /** * Holds if `s` is the flattened body element at position (`caseIdx`, - * `inner`) of expression switch `sw`: either the `caseIdx`-th case clause - * itself (with `inner` = -1) or its `inner`-th body statement. + * `inner`) of switch `sw`: either the `caseIdx`-th case clause itself (with + * `inner` = -1) or its `inner`-th body statement. */ - private predicate switchFlatItem( - Go::ExpressionSwitchStmt sw, Go::Stmt s, int caseIdx, int inner - ) { + private predicate switchFlatItem(Go::SwitchStmt sw, Go::Stmt s, int caseIdx, int inner) { s = sw.getCase(caseIdx) and inner = -1 or s = sw.getCase(caseIdx).getStmt(inner) @@ -469,6 +497,11 @@ module GoCfg { or notBlankIdent(n.(Go::RangeStmt).getValue()) and i = 1 ) and + // The `y := x.(type)` test statement of a type switch is transparent + // (see `skipCfg`): the per-case implicit variables are written at the + // case match nodes (see `IR::TypeSwitchImplicitVariableInstruction`), + // so the guard itself emits no assignment write node. + not n = any(Go::TypeSwitchStmt ts).getTest() and tag = "assign:" + i.toString() ) or @@ -559,14 +592,6 @@ module GoCfg { not exists(n.(Go::SliceExpr).getMax()) and tag = "implicit-max" or - // Type switch implicit variable - exists(Go::TypeSwitchStmt ts, Go::DefineStmt ds | - ds = ts.getAssign() and - n.(Go::CaseClause) = ts.getACase() and - exists(n.(Go::CaseClause).getImplicitlyDeclaredVariable()) and - tag = "type-switch-var" - ) - or // Literal element initialization n = any(Go::CompositeLit lit).getAnElement() and tag = "lit-init" @@ -943,7 +968,6 @@ module GoCfg { predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { rangeLoop(n1, n2) or - switchStmt(n1, n2) or selectStmt(n1, n2) or deferStmt(n1, n2) or goStmtStep(n1, n2) or @@ -1007,7 +1031,12 @@ module GoCfg { assgn instanceof Go::Assignment and not assgn instanceof Go::RecvStmt or assgn instanceof Go::ValueSpec - ) + ) and + // The `y := x.(type)` test statement of a type switch is transparent + // (see `skipCfg`); the shared switch model evaluates the underlying + // type-assertion expression directly, so this statement has no + // assignment flow of its own. + not assgn = any(Go::TypeSwitchStmt ts).getTest() | // Route through children (LHS names, RHS expressions) childSequenceStep(assgn, n1, n2) @@ -1429,119 +1458,6 @@ module GoCfg { ) } - private predicate switchStmt(PreControlFlowNode n1, PreControlFlowNode n2) { - typeSwitch(n1, n2) or typeCaseClause(n1, n2) - } - - private predicate switchCasesStartOrAfter(Go::TypeSwitchStmt sw, PreControlFlowNode n) { - n.isBefore(sw.getNonDefaultCase(0)) - or - not exists(sw.getANonDefaultCase()) and n.isBefore(sw.getDefault()) - or - not exists(sw.getACase()) and n.isAfter(sw) - } - - private predicate typeSwitch(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Go::TypeSwitchStmt sw | - n1.isBefore(sw) and - ( - n2.isBefore(sw.getInit()) - or - not exists(sw.getInit()) and n2.isBefore(sw.getTest()) - ) - or - n1.isAfter(sw.getInit()) and n2.isBefore(sw.getTest()) - or - n1.isAfter(sw.getTest()) and switchCasesStartOrAfter(sw, n2) - ) - } - - /** - * Holds if `cc` is a case clause of a type switch with an assignment that - * implicitly declares a variable whose type narrows to the case type. In - * this situation the CFG inserts a `type-switch-var` additional node - * between the case test and the case body, on which the IR layer - * materialises the implicit assignment to that variable. - */ - private predicate hasTypeSwitchVar(Go::CaseClause cc) { - exists(Go::TypeSwitchStmt ts | - ts.getACase() = cc and - exists(ts.getAssign()) and - exists(cc.getImplicitlyDeclaredVariable()) - ) - } - - private predicate typeCaseClause(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Go::TypeSwitchStmt sw, Go::CaseClause cc, int i | cc = sw.getNonDefaultCase(i) | - n1.isBefore(cc) and n2.isBefore(cc.getExpr(0)) - or - // A type switch is not boolean, so each case type test has a single - // "after" node from which control flows both to the case body (on a - // match) and on to the next test (on a mismatch). - exists(int j | n1.isAfter(cc.getExpr(j)) and n2.isBefore(cc.getExpr(j + 1))) - or - exists(int last | last = max(int j | exists(cc.getExpr(j))) | - n1.isAfter(cc.getExpr(last)) and - ( - hasTypeSwitchVar(cc) and n2.isAdditional(cc, "type-switch-var") - or - not hasTypeSwitchVar(cc) and - ( - n2.isBefore(cc.getStmt(0)) - or - not exists(cc.getStmt(0)) and n2.isAfter(sw) - ) - ) - or - n1.isAfter(cc.getExpr(last)) and - ( - n2.isBefore(sw.getNonDefaultCase(i + 1)) - or - not exists(sw.getNonDefaultCase(i + 1)) and n2.isBefore(sw.getDefault()) - or - not exists(sw.getNonDefaultCase(i + 1)) and - not exists(sw.getDefault()) and - n2.isAfter(sw) - ) - ) - ) - or - exists(Go::TypeSwitchStmt sw, Go::CaseClause def | def = sw.getDefault() | - n1.isBefore(def) and - ( - hasTypeSwitchVar(def) and n2.isAdditional(def, "type-switch-var") - or - not hasTypeSwitchVar(def) and - ( - n2.isBefore(def.getStmt(0)) - or - not exists(def.getStmt(0)) and n2.isAfter(sw) - ) - ) - ) - or - exists(Go::TypeSwitchStmt sw, Go::CaseClause cc | - sw.getACase() = cc and - hasTypeSwitchVar(cc) and - n1.isAdditional(cc, "type-switch-var") and - ( - n2.isBefore(cc.getStmt(0)) - or - not exists(cc.getStmt(0)) and n2.isAfter(sw) - ) - ) - or - exists(Go::TypeSwitchStmt sw, Go::CaseClause cc | cc = sw.getACase() | - exists(int j | n1.isAfter(cc.getStmt(j)) and n2.isBefore(cc.getStmt(j + 1))) - or - exists(int last | - last = max(int j | exists(cc.getStmt(j))) and - n1.isAfter(cc.getStmt(last)) and - n2.isAfter(sw) - ) - ) - } - private predicate commClauseBodyStart( Go::SelectStmt sel, Go::CommClause cc, PreControlFlowNode n ) { diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index bcac56c62534..9e024ecc3778 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -14,6 +14,7 @@ module; import go private import ControlFlowGraphShared +private import codeql.controlflow.SuccessorType /** Provides predicates and classes for working with IR constructs. */ module IR { @@ -43,6 +44,20 @@ module IR { e = any(ParenExpr pe | isInBooleanCondContext(pe)).getExpr() } + /** + * Holds if `n` is the control-flow node representing a successful match of + * the type-switch case clause `cc` that implicitly declares a variable. + * + * This node dominates the case body and is where the implicit per-case + * variable declaration/assignment is materialised (see + * `TypeSwitchImplicitVariableInstruction`). + */ + private predicate typeSwitchCaseMatch(ControlFlow::Node n, CaseClause cc) { + cc = any(TypeSwitchStmt ts).getACase() and + exists(cc.getImplicitlyDeclaredVariable()) and + n.isAfterValue(cc, any(MatchingSuccessor t | t.getValue() = true)) + } + /** * An IR instruction. */ @@ -56,6 +71,11 @@ module IR { or this.isAfterFalse(_) or + // The successful-match node of a type-switch case that binds an implicit + // variable hosts that variable's declaration/assignment (see + // `TypeSwitchImplicitVariableInstruction`). + typeSwitchCaseMatch(this, _) + or // `NotExpr` and `LogicalBinaryExpr` are not in `postOrInOrder`, so they // have no `isIn` node. When such an expression is not in a conditional // context (so it has a single combined after-node rather than per-branch @@ -1182,7 +1202,7 @@ module IR { class TypeSwitchImplicitVariableInstruction extends Instruction { CaseClause cc; - TypeSwitchImplicitVariableInstruction() { this.isAdditional(cc, "type-switch-var") } + TypeSwitchImplicitVariableInstruction() { typeSwitchCaseMatch(this, cc) } override predicate writes(ValueEntity v, Instruction rhs) { v = cc.getImplicitlyDeclaredVariable() and 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 444e9e83bcb1..0edb9a5426a6 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 @@ -3779,21 +3779,24 @@ | stmts.go:112:27:137:1 | block statement | stmts.go:112:27:137:1 | arg:0 block statement | | stmts.go:112:27:137:1 | param-init:0 block statement | stmts.go:113:2:121:2 | type-switch statement | | stmts.go:113:2:121:2 | After type-switch statement | stmts.go:123:2:131:2 | type-switch statement | -| stmts.go:113:2:121:2 | type-switch statement | stmts.go:113:9:113:21 | ... := ... | -| stmts.go:113:9:113:21 | ... := ... | stmts.go:113:14:113:21 | Before type assertion | -| stmts.go:113:9:113:21 | After ... := ... | stmts.go:114:2:115:16 | case clause | -| stmts.go:113:9:113:21 | assign:0 ... := ... | stmts.go:113:9:113:21 | After ... := ... | +| stmts.go:113:2:121:2 | type-switch statement | stmts.go:113:14:113:21 | Before type assertion | | stmts.go:113:14:113:14 | After x | stmts.go:113:14:113:21 | type assertion | | stmts.go:113:14:113:14 | Before x | stmts.go:113:14:113:14 | x | | stmts.go:113:14:113:14 | x | stmts.go:113:14:113:14 | After x | -| stmts.go:113:14:113:21 | After type assertion | stmts.go:113:9:113:21 | assign:0 ... := ... | +| stmts.go:113:14:113:21 | After type assertion | stmts.go:114:2:115:16 | case clause | | stmts.go:113:14:113:21 | Before type assertion | stmts.go:113:14:113:14 | Before x | | stmts.go:113:14:113:21 | type assertion | stmts.go:113:14:113:21 | After type assertion | +| stmts.go:114:2:115:16 | After case clause [match] | stmts.go:115:3:115:16 | expression statement | +| stmts.go:114:2:115:16 | After case clause [no-match] | stmts.go:116:2:118:14 | case clause | | stmts.go:114:2:115:16 | case clause | stmts.go:114:7:114:11 | error | -| stmts.go:114:2:115:16 | type-switch-var case clause | stmts.go:115:3:115:16 | expression statement | -| stmts.go:114:7:114:11 | error | stmts.go:114:14:114:19 | string | -| stmts.go:114:14:114:19 | string | stmts.go:114:2:115:16 | type-switch-var case clause | -| stmts.go:114:14:114:19 | string | stmts.go:116:2:118:14 | case clause | +| stmts.go:114:7:114:11 | After error [match] | stmts.go:114:2:115:16 | After case clause [match] | +| stmts.go:114:7:114:11 | After error [no-match] | stmts.go:114:14:114:19 | string | +| stmts.go:114:7:114:11 | error | stmts.go:114:7:114:11 | After error [match] | +| stmts.go:114:7:114:11 | error | stmts.go:114:7:114:11 | After error [no-match] | +| stmts.go:114:14:114:19 | After string [match] | stmts.go:114:2:115:16 | After case clause [match] | +| stmts.go:114:14:114:19 | After string [no-match] | stmts.go:114:2:115:16 | After case clause [no-match] | +| stmts.go:114:14:114:19 | string | stmts.go:114:14:114:19 | After string [match] | +| stmts.go:114:14:114:19 | string | stmts.go:114:14:114:19 | After string [no-match] | | stmts.go:115:3:115:13 | After selection of Println | stmts.go:115:15:115:15 | Before y | | stmts.go:115:3:115:13 | Before selection of Println | stmts.go:115:3:115:13 | selection of Println | | stmts.go:115:3:115:13 | selection of Println | stmts.go:115:3:115:13 | After selection of Println | @@ -3806,10 +3809,13 @@ | stmts.go:115:15:115:15 | After y | stmts.go:115:3:115:16 | call to Println | | stmts.go:115:15:115:15 | Before y | stmts.go:115:15:115:15 | y | | stmts.go:115:15:115:15 | y | stmts.go:115:15:115:15 | After y | +| stmts.go:116:2:118:14 | After case clause [match] | stmts.go:117:3:117:13 | expression statement | +| stmts.go:116:2:118:14 | After case clause [no-match] | stmts.go:119:2:120:7 | case clause | | stmts.go:116:2:118:14 | case clause | stmts.go:116:7:116:13 | float32 | -| stmts.go:116:2:118:14 | type-switch-var case clause | stmts.go:117:3:117:13 | expression statement | -| stmts.go:116:7:116:13 | float32 | stmts.go:116:2:118:14 | type-switch-var case clause | -| stmts.go:116:7:116:13 | float32 | stmts.go:119:2:120:7 | case clause | +| stmts.go:116:7:116:13 | After float32 [match] | stmts.go:116:2:118:14 | After case clause [match] | +| stmts.go:116:7:116:13 | After float32 [no-match] | stmts.go:116:2:118:14 | After case clause [no-match] | +| stmts.go:116:7:116:13 | float32 | stmts.go:116:7:116:13 | After float32 [match] | +| stmts.go:116:7:116:13 | float32 | stmts.go:116:7:116:13 | After float32 [no-match] | | stmts.go:117:3:117:7 | After test5 | stmts.go:117:9:117:12 | Before true | | stmts.go:117:3:117:7 | Before test5 | stmts.go:117:3:117:7 | test5 | | stmts.go:117:3:117:7 | test5 | stmts.go:117:3:117:7 | After test5 | @@ -3834,8 +3840,8 @@ | stmts.go:118:9:118:13 | After false | stmts.go:118:3:118:14 | call to test5 | | stmts.go:118:9:118:13 | Before false | stmts.go:118:9:118:13 | false | | stmts.go:118:9:118:13 | false | stmts.go:118:9:118:13 | After false | -| stmts.go:119:2:120:7 | case clause | stmts.go:119:2:120:7 | type-switch-var case clause | -| stmts.go:119:2:120:7 | type-switch-var case clause | stmts.go:120:3:120:7 | ... = ... | +| stmts.go:119:2:120:7 | After case clause [match] | stmts.go:120:3:120:7 | ... = ... | +| stmts.go:119:2:120:7 | case clause | stmts.go:119:2:120:7 | After case clause [match] | | stmts.go:120:3:120:7 | ... = ... | stmts.go:120:7:120:7 | Before y | | stmts.go:120:3:120:7 | After ... = ... | stmts.go:113:2:121:2 | After type-switch statement | | stmts.go:120:7:120:7 | After y | stmts.go:120:3:120:7 | After ... = ... | @@ -3844,7 +3850,7 @@ | stmts.go:123:2:131:2 | After type-switch statement | stmts.go:133:2:136:2 | type-switch statement | | stmts.go:123:2:131:2 | type-switch statement | stmts.go:123:9:123:14 | ... := ... | | stmts.go:123:9:123:14 | ... := ... | stmts.go:123:14:123:14 | Before x | -| stmts.go:123:9:123:14 | After ... := ... | stmts.go:123:17:123:24 | expression statement | +| stmts.go:123:9:123:14 | After ... := ... | stmts.go:123:17:123:24 | Before type assertion | | stmts.go:123:9:123:14 | assign:0 ... := ... | stmts.go:123:9:123:14 | After ... := ... | | stmts.go:123:14:123:14 | After x | stmts.go:123:9:123:14 | assign:0 ... := ... | | stmts.go:123:14:123:14 | Before x | stmts.go:123:14:123:14 | x | @@ -3852,15 +3858,20 @@ | stmts.go:123:17:123:17 | After y | stmts.go:123:17:123:24 | type assertion | | stmts.go:123:17:123:17 | Before y | stmts.go:123:17:123:17 | y | | stmts.go:123:17:123:17 | y | stmts.go:123:17:123:17 | After y | -| stmts.go:123:17:123:24 | After expression statement | stmts.go:124:2:125:16 | case clause | -| stmts.go:123:17:123:24 | After type assertion | stmts.go:123:17:123:24 | After expression statement | +| stmts.go:123:17:123:24 | After type assertion | stmts.go:124:2:125:16 | case clause | | stmts.go:123:17:123:24 | Before type assertion | stmts.go:123:17:123:17 | Before y | -| stmts.go:123:17:123:24 | expression statement | stmts.go:123:17:123:24 | Before type assertion | | stmts.go:123:17:123:24 | type assertion | stmts.go:123:17:123:24 | After type assertion | +| stmts.go:124:2:125:16 | After case clause [match] | stmts.go:125:3:125:16 | expression statement | +| stmts.go:124:2:125:16 | After case clause [no-match] | stmts.go:126:2:128:14 | case clause | | stmts.go:124:2:125:16 | case clause | stmts.go:124:7:124:11 | error | -| stmts.go:124:7:124:11 | error | stmts.go:124:14:124:19 | string | -| stmts.go:124:14:124:19 | string | stmts.go:125:3:125:16 | expression statement | -| stmts.go:124:14:124:19 | string | stmts.go:126:2:128:14 | case clause | +| stmts.go:124:7:124:11 | After error [match] | stmts.go:124:2:125:16 | After case clause [match] | +| stmts.go:124:7:124:11 | After error [no-match] | stmts.go:124:14:124:19 | string | +| stmts.go:124:7:124:11 | error | stmts.go:124:7:124:11 | After error [match] | +| stmts.go:124:7:124:11 | error | stmts.go:124:7:124:11 | After error [no-match] | +| stmts.go:124:14:124:19 | After string [match] | stmts.go:124:2:125:16 | After case clause [match] | +| stmts.go:124:14:124:19 | After string [no-match] | stmts.go:124:2:125:16 | After case clause [no-match] | +| stmts.go:124:14:124:19 | string | stmts.go:124:14:124:19 | After string [match] | +| stmts.go:124:14:124:19 | string | stmts.go:124:14:124:19 | After string [no-match] | | stmts.go:125:3:125:13 | After selection of Println | stmts.go:125:15:125:15 | Before y | | stmts.go:125:3:125:13 | Before selection of Println | stmts.go:125:3:125:13 | selection of Println | | stmts.go:125:3:125:13 | selection of Println | stmts.go:125:3:125:13 | After selection of Println | @@ -3873,9 +3884,13 @@ | stmts.go:125:15:125:15 | After y | stmts.go:125:3:125:16 | call to Println | | stmts.go:125:15:125:15 | Before y | stmts.go:125:15:125:15 | y | | stmts.go:125:15:125:15 | y | stmts.go:125:15:125:15 | After y | +| stmts.go:126:2:128:14 | After case clause [match] | stmts.go:127:3:127:13 | expression statement | +| stmts.go:126:2:128:14 | After case clause [no-match] | stmts.go:129:2:130:7 | case clause | | stmts.go:126:2:128:14 | case clause | stmts.go:126:7:126:13 | float32 | -| stmts.go:126:7:126:13 | float32 | stmts.go:127:3:127:13 | expression statement | -| stmts.go:126:7:126:13 | float32 | stmts.go:129:2:130:7 | case clause | +| stmts.go:126:7:126:13 | After float32 [match] | stmts.go:126:2:128:14 | After case clause [match] | +| stmts.go:126:7:126:13 | After float32 [no-match] | stmts.go:126:2:128:14 | After case clause [no-match] | +| stmts.go:126:7:126:13 | float32 | stmts.go:126:7:126:13 | After float32 [match] | +| stmts.go:126:7:126:13 | float32 | stmts.go:126:7:126:13 | After float32 [no-match] | | stmts.go:127:3:127:7 | After test5 | stmts.go:127:9:127:12 | Before true | | stmts.go:127:3:127:7 | Before test5 | stmts.go:127:3:127:7 | test5 | | stmts.go:127:3:127:7 | test5 | stmts.go:127:3:127:7 | After test5 | @@ -3900,7 +3915,8 @@ | stmts.go:128:9:128:13 | After false | stmts.go:128:3:128:14 | call to test5 | | stmts.go:128:9:128:13 | Before false | stmts.go:128:9:128:13 | false | | stmts.go:128:9:128:13 | false | stmts.go:128:9:128:13 | After false | -| stmts.go:129:2:130:7 | case clause | stmts.go:130:3:130:7 | ... = ... | +| stmts.go:129:2:130:7 | After case clause [match] | stmts.go:130:3:130:7 | ... = ... | +| stmts.go:129:2:130:7 | case clause | stmts.go:129:2:130:7 | After case clause [match] | | stmts.go:130:3:130:7 | ... = ... | stmts.go:130:7:130:7 | Before y | | stmts.go:130:3:130:7 | After ... = ... | stmts.go:123:2:131:2 | After type-switch statement | | stmts.go:130:7:130:7 | After y | stmts.go:130:3:130:7 | After ... = ... | @@ -3909,7 +3925,7 @@ | stmts.go:133:2:136:2 | After type-switch statement | stmts.go:112:27:137:1 | After block statement | | stmts.go:133:2:136:2 | type-switch statement | stmts.go:133:9:133:14 | ... := ... | | stmts.go:133:9:133:14 | ... := ... | stmts.go:133:14:133:14 | Before x | -| stmts.go:133:9:133:14 | After ... := ... | stmts.go:133:17:133:24 | expression statement | +| stmts.go:133:9:133:14 | After ... := ... | stmts.go:133:17:133:24 | Before type assertion | | stmts.go:133:9:133:14 | assign:0 ... := ... | stmts.go:133:9:133:14 | After ... := ... | | stmts.go:133:14:133:14 | After x | stmts.go:133:9:133:14 | assign:0 ... := ... | | stmts.go:133:14:133:14 | Before x | stmts.go:133:14:133:14 | x | @@ -3917,12 +3933,11 @@ | stmts.go:133:17:133:17 | After y | stmts.go:133:17:133:24 | type assertion | | stmts.go:133:17:133:17 | Before y | stmts.go:133:17:133:17 | y | | stmts.go:133:17:133:17 | y | stmts.go:133:17:133:17 | After y | -| stmts.go:133:17:133:24 | After expression statement | stmts.go:134:2:135:14 | case clause | -| stmts.go:133:17:133:24 | After type assertion | stmts.go:133:17:133:24 | After expression statement | +| stmts.go:133:17:133:24 | After type assertion | stmts.go:134:2:135:14 | case clause | | stmts.go:133:17:133:24 | Before type assertion | stmts.go:133:17:133:17 | Before y | -| stmts.go:133:17:133:24 | expression statement | stmts.go:133:17:133:24 | Before type assertion | | stmts.go:133:17:133:24 | type assertion | stmts.go:133:17:133:24 | After type assertion | -| stmts.go:134:2:135:14 | case clause | stmts.go:135:3:135:14 | expression statement | +| stmts.go:134:2:135:14 | After case clause [match] | stmts.go:135:3:135:14 | expression statement | +| stmts.go:134:2:135:14 | case clause | stmts.go:134:2:135:14 | After case clause [match] | | stmts.go:135:3:135:7 | After test5 | stmts.go:135:9:135:13 | Before false | | stmts.go:135:3:135:7 | Before test5 | stmts.go:135:3:135:7 | test5 | | stmts.go:135:3:135:7 | test5 | stmts.go:135:3:135:7 | After test5 |