Skip to content

Commit d83455d

Browse files
authored
Migrate Go expression switch CFG to shared library; handle fallthrough via fallsThrough
1 parent 464857c commit d83455d

2 files changed

Lines changed: 101 additions & 130 deletions

File tree

go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll

Lines changed: 75 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ module GoCfg {
6767
e = any(Go::ImportSpec is).getPathExpr()
6868
or
6969
e.getParent*() = any(Go::ArrayTypeExpr ate).getLength()
70+
or
71+
// The body block of an expression switch is transparent: the shared
72+
// switch model wires control flow directly from the switch to its case
73+
// clauses (in control-flow order) and between cases, so the enclosing
74+
// block must not introduce its own nodes or default left-to-right
75+
// sequencing of the case clauses.
76+
e = any(Go::ExpressionSwitchStmt sw).getBody()
7077
}
7178

7279
AstNode getChild(AstNode n, int index) {
@@ -217,27 +224,68 @@ module GoCfg {
217224
}
218225

219226
class Switch extends AstNode {
220-
Switch() { none() }
221-
222-
Expr getExpr() { none() }
223-
224-
Case getCase(int index) { none() }
225-
226-
Stmt getStmt(int index) { none() }
227+
Switch() { this instanceof Go::ExpressionSwitchStmt }
228+
229+
Expr getExpr() { result = this.(Go::ExpressionSwitchStmt).getExpr() }
230+
231+
Case getCase(int index) { result = this.(Go::ExpressionSwitchStmt).getCase(index) }
232+
233+
Stmt getStmt(int index) {
234+
// Go nests each case clause's body statements under the clause rather
235+
// than in a flat list, so we expose a flattened view in which every
236+
// case clause is immediately followed by its own body statements. This
237+
// lets the shared library compute the body of a case as the statements
238+
// between it and the next clause.
239+
result =
240+
rank[index + 1](Go::Stmt s, int caseIdx, int inner |
241+
switchFlatItem(this, s, caseIdx, inner)
242+
|
243+
s order by caseIdx, inner
244+
)
245+
}
227246
}
228247

229248
class Case extends AstNode {
230-
Case() { none() }
249+
Case() { this = any(Go::ExpressionSwitchStmt sw).getACase() }
231250

232-
AstNode getPattern(int index) { none() }
251+
AstNode getPattern(int index) { result = this.(Go::CaseClause).getExpr(index) }
233252

234253
Expr getGuard() { none() }
235254

236255
AstNode getBody() { none() }
237256
}
238257

239258
class DefaultCase extends Case {
240-
DefaultCase() { none() }
259+
DefaultCase() { not exists(this.(Go::CaseClause).getExpr(_)) }
260+
}
261+
262+
/** Gets the initializer of `switch` statement `switch`, if any. */
263+
AstNode getSwitchInit(Switch switch) { result = switch.(Go::ExpressionSwitchStmt).getInit() }
264+
265+
/**
266+
* Go has no implicit fall-through between case clauses; a case that runs to
267+
* the end of its body breaks out of the switch. Fall-through only happens
268+
* when the case body ends with an explicit `fallthrough` statement, in
269+
* which case control transfers to the next case clause's body (in source
270+
* order). The shared library models this by chaining the body of such a
271+
* case to the body of the following case.
272+
*/
273+
predicate fallsThrough(Case c) {
274+
c.(Go::CaseClause).getStmt(max(int i | exists(c.(Go::CaseClause).getStmt(i)))) instanceof
275+
Go::FallthroughStmt
276+
}
277+
278+
/**
279+
* Holds if `s` is the flattened body element at position (`caseIdx`,
280+
* `inner`) of expression switch `sw`: either the `caseIdx`-th case clause
281+
* itself (with `inner` = -1) or its `inner`-th body statement.
282+
*/
283+
private predicate switchFlatItem(
284+
Go::ExpressionSwitchStmt sw, Go::Stmt s, int caseIdx, int inner
285+
) {
286+
s = sw.getCase(caseIdx) and inner = -1
287+
or
288+
s = sw.getCase(caseIdx).getStmt(inner)
241289
}
242290

243291
class ConditionalExpr extends Expr {
@@ -315,16 +363,10 @@ module GoCfg {
315363

316364
class CallableContext = Void;
317365

318-
private newtype TLabel =
319-
TGoLabel(string l) { exists(Go::LabeledStmt ls | l = ls.getLabel()) } or
320-
TFallthrough()
366+
private newtype TLabel = TGoLabel(string l) { exists(Go::LabeledStmt ls | l = ls.getLabel()) }
321367

322368
class Label extends TLabel {
323-
string toString() {
324-
exists(string l | this = TGoLabel(l) and result = l)
325-
or
326-
this = TFallthrough() and result = "fallthrough"
327-
}
369+
string toString() { exists(string l | this = TGoLabel(l) and result = l) }
328370
}
329371

330372
private Label getLabelOfStmt(Go::Stmt s) {
@@ -339,19 +381,11 @@ module GoCfg {
339381
l = TGoLabel(n.(Go::BreakStmt).getLabel())
340382
or
341383
l = TGoLabel(n.(Go::ContinueStmt).getLabel())
342-
or
343-
l = TFallthrough() and n instanceof Go::FallthroughStmt
344384
}
345385

346386
predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) {
347387
kind.isBoolean() and
348-
(
349-
n = any(Go::ForStmt fs).getCond()
350-
or
351-
exists(Go::ExpressionSwitchStmt ess |
352-
not exists(ess.getExpr()) and n = ess.getACase().(Go::CaseClause).getExpr(_)
353-
)
354-
)
388+
n = any(Go::ForStmt fs).getCond()
355389
}
356390

357391
predicate preOrderExpr(Ast::Expr e) {
@@ -515,17 +549,6 @@ module GoCfg {
515549
not exists(n.(Go::SliceExpr).getMax()) and
516550
tag = "implicit-max"
517551
or
518-
// Implicit true in tagless switch
519-
n instanceof Go::ExpressionSwitchStmt and
520-
not exists(n.(Go::ExpressionSwitchStmt).getExpr()) and
521-
tag = "implicit-true"
522-
or
523-
// Case check nodes
524-
exists(int i |
525-
exists(n.(Go::CaseClause).getExpr(i)) and
526-
tag = "case-check:" + i.toString()
527-
)
528-
or
529552
// Type switch implicit variable
530553
exists(Go::TypeSwitchStmt ts, Go::DefineStmt ds |
531554
ds = ts.getAssign() and
@@ -709,12 +732,6 @@ module GoCfg {
709732
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
710733
always = false
711734
or
712-
ast instanceof Go::FallthroughStmt and
713-
n.injects(ast) and
714-
c.getSuccessorType() instanceof BreakSuccessor and
715-
c.hasLabel(TFallthrough()) and
716-
always = true
717-
or
718735
ast instanceof Go::GotoStmt and
719736
n.injects(ast) and
720737
c.getSuccessorType() instanceof GotoSuccessor and
@@ -1403,42 +1420,17 @@ module GoCfg {
14031420
}
14041421

14051422
private predicate switchStmt(PreControlFlowNode n1, PreControlFlowNode n2) {
1406-
exprSwitch(n1, n2) or typeSwitch(n1, n2) or caseClause(n1, n2)
1423+
typeSwitch(n1, n2) or typeCaseClause(n1, n2)
14071424
}
14081425

1409-
private predicate switchCasesStartOrAfter(Go::SwitchStmt sw, PreControlFlowNode n) {
1426+
private predicate switchCasesStartOrAfter(Go::TypeSwitchStmt sw, PreControlFlowNode n) {
14101427
n.isBefore(sw.getNonDefaultCase(0))
14111428
or
14121429
not exists(sw.getANonDefaultCase()) and n.isBefore(sw.getDefault())
14131430
or
14141431
not exists(sw.getACase()) and n.isAfter(sw)
14151432
}
14161433

1417-
private predicate exprSwitch(PreControlFlowNode n1, PreControlFlowNode n2) {
1418-
exists(Go::ExpressionSwitchStmt sw |
1419-
n1.isBefore(sw) and
1420-
(
1421-
n2.isBefore(sw.getInit())
1422-
or
1423-
not exists(sw.getInit()) and
1424-
(
1425-
n2.isBefore(sw.getExpr())
1426-
or
1427-
not exists(sw.getExpr()) and switchCasesStartOrAfter(sw, n2)
1428-
)
1429-
)
1430-
or
1431-
n1.isAfter(sw.getInit()) and
1432-
(
1433-
n2.isBefore(sw.getExpr())
1434-
or
1435-
not exists(sw.getExpr()) and switchCasesStartOrAfter(sw, n2)
1436-
)
1437-
or
1438-
n1.isAfter(sw.getExpr()) and switchCasesStartOrAfter(sw, n2)
1439-
)
1440-
}
1441-
14421434
private predicate typeSwitch(PreControlFlowNode n1, PreControlFlowNode n2) {
14431435
exists(Go::TypeSwitchStmt sw |
14441436
n1.isBefore(sw) and
@@ -1454,46 +1446,6 @@ module GoCfg {
14541446
)
14551447
}
14561448

1457-
/**
1458-
* Holds if `sw` is a tagless expression switch, in which case the case
1459-
* expressions are themselves the boolean conditions being tested and are
1460-
* therefore in a boolean conditional context.
1461-
*/
1462-
private predicate isBooleanSwitch(Go::SwitchStmt sw) {
1463-
sw instanceof Go::ExpressionSwitchStmt and
1464-
not exists(sw.(Go::ExpressionSwitchStmt).getExpr())
1465-
}
1466-
1467-
/**
1468-
* Holds if `n` is the control-flow node immediately after evaluating case
1469-
* expression `caseExpr` of switch `sw` on the branch where `caseExpr`
1470-
* matches.
1471-
*/
1472-
private predicate afterCaseExprMatch(Go::SwitchStmt sw, Go::Expr caseExpr, PreControlFlowNode n) {
1473-
caseExpr = sw.getACase().(Go::CaseClause).getAnExpr() and
1474-
(
1475-
isBooleanSwitch(sw) and n.isAfterTrue(caseExpr)
1476-
or
1477-
not isBooleanSwitch(sw) and n.isAfter(caseExpr)
1478-
)
1479-
}
1480-
1481-
/**
1482-
* Holds if `n` is the control-flow node immediately after evaluating case
1483-
* expression `caseExpr` of switch `sw` on the branch where `caseExpr`
1484-
* does not match.
1485-
*/
1486-
private predicate afterCaseExprNoMatch(
1487-
Go::SwitchStmt sw, Go::Expr caseExpr, PreControlFlowNode n
1488-
) {
1489-
caseExpr = sw.getACase().(Go::CaseClause).getAnExpr() and
1490-
(
1491-
isBooleanSwitch(sw) and n.isAfterFalse(caseExpr)
1492-
or
1493-
not isBooleanSwitch(sw) and n.isAfter(caseExpr)
1494-
)
1495-
}
1496-
14971449
/**
14981450
* Holds if `cc` is a case clause of a type switch with an assignment that
14991451
* implicitly declares a variable whose type narrows to the case type. In
@@ -1509,19 +1461,17 @@ module GoCfg {
15091461
)
15101462
}
15111463

1512-
private predicate caseClause(PreControlFlowNode n1, PreControlFlowNode n2) {
1513-
exists(Go::SwitchStmt sw, Go::CaseClause cc, int i | cc = sw.getNonDefaultCase(i) |
1464+
private predicate typeCaseClause(PreControlFlowNode n1, PreControlFlowNode n2) {
1465+
exists(Go::TypeSwitchStmt sw, Go::CaseClause cc, int i | cc = sw.getNonDefaultCase(i) |
15141466
n1.isBefore(cc) and n2.isBefore(cc.getExpr(0))
15151467
or
1516-
// For a tagless expression switch the case expressions are themselves
1517-
// booleans in a conditional context, so we only fall through to the
1518-
// next case expression on the false branch.
1519-
exists(int j |
1520-
afterCaseExprNoMatch(sw, cc.getExpr(j), n1) and n2.isBefore(cc.getExpr(j + 1))
1521-
)
1468+
// A type switch is not boolean, so each case type test has a single
1469+
// "after" node from which control flows both to the case body (on a
1470+
// match) and on to the next test (on a mismatch).
1471+
exists(int j | n1.isAfter(cc.getExpr(j)) and n2.isBefore(cc.getExpr(j + 1)))
15221472
or
15231473
exists(int last | last = max(int j | exists(cc.getExpr(j))) |
1524-
afterCaseExprMatch(sw, cc.getExpr(last), n1) and
1474+
n1.isAfter(cc.getExpr(last)) and
15251475
(
15261476
hasTypeSwitchVar(cc) and n2.isAdditional(cc, "type-switch-var")
15271477
or
@@ -1533,7 +1483,7 @@ module GoCfg {
15331483
)
15341484
)
15351485
or
1536-
afterCaseExprNoMatch(sw, cc.getExpr(last), n1) and
1486+
n1.isAfter(cc.getExpr(last)) and
15371487
(
15381488
n2.isBefore(sw.getNonDefaultCase(i + 1))
15391489
or
@@ -1546,7 +1496,7 @@ module GoCfg {
15461496
)
15471497
)
15481498
or
1549-
exists(Go::SwitchStmt sw, Go::CaseClause def | def = sw.getDefault() |
1499+
exists(Go::TypeSwitchStmt sw, Go::CaseClause def | def = sw.getDefault() |
15501500
n1.isBefore(def) and
15511501
(
15521502
hasTypeSwitchVar(def) and n2.isAdditional(def, "type-switch-var")
@@ -1560,7 +1510,7 @@ module GoCfg {
15601510
)
15611511
)
15621512
or
1563-
exists(Go::SwitchStmt sw, Go::CaseClause cc |
1513+
exists(Go::TypeSwitchStmt sw, Go::CaseClause cc |
15641514
sw.getACase() = cc and
15651515
hasTypeSwitchVar(cc) and
15661516
n1.isAdditional(cc, "type-switch-var") and
@@ -1571,11 +1521,10 @@ module GoCfg {
15711521
)
15721522
)
15731523
or
1574-
exists(Go::CaseClause cc |
1524+
exists(Go::TypeSwitchStmt sw, Go::CaseClause cc | cc = sw.getACase() |
15751525
exists(int j | n1.isAfter(cc.getStmt(j)) and n2.isBefore(cc.getStmt(j + 1)))
15761526
or
1577-
exists(Go::SwitchStmt sw, int last |
1578-
sw.getACase() = cc and
1527+
exists(int last |
15791528
last = max(int j | exists(cc.getStmt(j))) and
15801529
n1.isAfter(cc.getStmt(last)) and
15811530
n2.isAfter(sw)

shared/controlflow/codeql/controlflow/ControlFlowGraph.qll

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,14 @@ signature module AstSig<LocationSig Location> {
266266
Stmt getStmt(int index);
267267
}
268268

269+
/**
270+
* Gets the initializer of `switch` statement `switch`, if any.
271+
*
272+
* Only some languages (e.g. Go) support an initializer that is evaluated
273+
* before the switch expression.
274+
*/
275+
default AstNode getSwitchInit(Switch switch) { none() }
276+
269277
/** A case in a switch. */
270278
class Case extends AstNode {
271279
/** Gets the pattern being matched by this case at the specified (zero-based) `index`. */
@@ -1866,11 +1874,25 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
18661874
not exists(getRankedCaseCfgOrder(switch, _)) and firstCase.isAfter(switch)
18671875
|
18681876
n1.isBefore(switch) and
1869-
n2.isBefore(switch.getExpr())
1877+
(
1878+
n2.isBefore(getSwitchInit(switch))
1879+
or
1880+
not exists(getSwitchInit(switch)) and
1881+
(
1882+
n2.isBefore(switch.getExpr())
1883+
or
1884+
not exists(switch.getExpr()) and
1885+
n2 = firstCase
1886+
)
1887+
)
18701888
or
1871-
n1.isBefore(switch) and
1872-
not exists(switch.getExpr()) and
1873-
n2 = firstCase
1889+
n1.isAfter(getSwitchInit(switch)) and
1890+
(
1891+
n2.isBefore(switch.getExpr())
1892+
or
1893+
not exists(switch.getExpr()) and
1894+
n2 = firstCase
1895+
)
18741896
or
18751897
n1.isAfter(switch.getExpr()) and
18761898
n2 = firstCase

0 commit comments

Comments
 (0)