Skip to content

Commit d4bb4d4

Browse files
authored
Merge pull request #16120 from owen-mc/go/fix/type-switch-control-flow
Go: Fix data flow through variable defined in type switch guard
2 parents 1e8315d + 322d9fe commit d4bb4d4

File tree

12 files changed

+321
-1602
lines changed

12 files changed

+321
-1602
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Data flow through variables declared in statements of the form `x := y.(type)` at the beginning of type switches has been fixed, which may result in more alerts.

go/ql/lib/semmle/go/Scopes.qll

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ class Entity extends @object {
122122
/**
123123
* Gets the declaring identifier for this entity, if any.
124124
*
125-
* Note that type switch statements which define a new variable in the guard
125+
* Note that type switch statements which declare a new variable in the guard
126126
* actually have a new variable (of the right type) implicitly declared at
127-
* the beginning of each case clause, and these do not have a declaration.
127+
* the beginning of each case clause, and these do not have a syntactic
128+
* declaration.
128129
*/
129130
Ident getDeclaration() { result.declares(this) }
130131

@@ -137,6 +138,15 @@ class Entity extends @object {
137138
/** Gets a textual representation of this entity. */
138139
string toString() { result = this.getName() }
139140

141+
private predicate hasRealLocationInfo(
142+
string filepath, int startline, int startcolumn, int endline, int endcolumn
143+
) {
144+
// take the location of the declaration if there is one
145+
this.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or
146+
any(CaseClause cc | this = cc.getImplicitlyDeclaredVariable())
147+
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
148+
}
149+
140150
/**
141151
* Holds if this element is at the specified location.
142152
* The location spans column `startcolumn` of line `startline` to
@@ -148,15 +158,16 @@ class Entity extends @object {
148158
string filepath, int startline, int startcolumn, int endline, int endcolumn
149159
) {
150160
// take the location of the declaration if there is one
151-
this.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
152-
or
153-
// otherwise fall back on dummy location
154-
not exists(this.getDeclaration()) and
155-
filepath = "" and
156-
startline = 0 and
157-
startcolumn = 0 and
158-
endline = 0 and
159-
endcolumn = 0
161+
if this.hasRealLocationInfo(_, _, _, _, _)
162+
then this.hasRealLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
163+
else (
164+
// otherwise fall back on dummy location
165+
filepath = "" and
166+
startline = 0 and
167+
startcolumn = 0 and
168+
endline = 0 and
169+
endcolumn = 0
170+
)
160171
}
161172
}
162173

go/ql/lib/semmle/go/Stmt.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,16 @@ class CaseClause extends @caseclause, Stmt, ScopeNode {
777777
/** Gets the number of statements of this `case` clause. */
778778
int getNumStmt() { result = this.getNumChildStmt() }
779779

780+
/**
781+
* Gets the implicitly declared variable for this `case` clause, if any.
782+
*
783+
* This exists for case clauses in type switch statements which declare a
784+
* variable in the guard.
785+
*/
786+
LocalVariable getImplicitlyDeclaredVariable() {
787+
not exists(result.getDeclaration()) and result.getScope().(LocalScope).getNode() = this
788+
}
789+
780790
override predicate mayHaveSideEffects() {
781791
this.getAnExpr().mayHaveSideEffects() or
782792
this.getAStmt().mayHaveSideEffects()

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,19 @@ newtype TControlFlowNode =
306306
* the `i`th expression of a case clause `cc`.
307307
*/
308308
MkCaseCheckNode(CaseClause cc, int i) { exists(cc.getExpr(i)) } or
309+
/**
310+
* A control-flow node that represents the implicit declaration of the
311+
* variable `lv` in case clause `cc` and its assignment of the value
312+
* `switchExpr` from the guard. This only occurs in case clauses in a type
313+
* switch statement which declares a variable in its guard.
314+
*/
315+
MkTypeSwitchImplicitVariable(CaseClause cc, LocalVariable lv, Expr switchExpr) {
316+
exists(TypeSwitchStmt ts, DefineStmt ds | ds = ts.getAssign() |
317+
cc = ts.getACase() and
318+
lv = cc.getImplicitlyDeclaredVariable() and
319+
switchExpr = ds.getRhs().(TypeAssertExpr).getExpr()
320+
)
321+
} or
309322
/**
310323
* A control-flow node that represents the implicit lower bound of a slice expression.
311324
*/
@@ -1099,6 +1112,10 @@ module CFG {
10991112
first = this.getExprStart(0)
11001113
or
11011114
not exists(this.getAnExpr()) and
1115+
first = MkTypeSwitchImplicitVariable(this, _, _)
1116+
or
1117+
not exists(this.getAnExpr()) and
1118+
not exists(MkTypeSwitchImplicitVariable(this, _, _)) and
11021119
first = this.getBodyStart()
11031120
}
11041121

@@ -1119,6 +1136,9 @@ module CFG {
11191136
override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) {
11201137
ControlFlowTree.super.succ0(pred, succ)
11211138
or
1139+
pred = MkTypeSwitchImplicitVariable(this, _, _) and
1140+
succ = this.getBodyStart()
1141+
or
11221142
exists(int i |
11231143
lastNode(this.getExpr(i), pred, normalCompletion()) and
11241144
succ = MkCaseCheckNode(this, i)
@@ -1137,8 +1157,13 @@ module CFG {
11371157

11381158
predicate isPassingEdge(int i, ControlFlow::Node pred, ControlFlow::Node succ, Expr testExpr) {
11391159
pred = this.getExprEnd(i, true) and
1140-
succ = this.getBodyStart() and
1141-
testExpr = this.getExpr(i)
1160+
testExpr = this.getExpr(i) and
1161+
(
1162+
succ = MkTypeSwitchImplicitVariable(this, _, _)
1163+
or
1164+
not exists(MkTypeSwitchImplicitVariable(this, _, _)) and
1165+
succ = this.getBodyStart()
1166+
)
11421167
}
11431168

11441169
override ControlFlowTree getChildTree(int i) { result = this.getStmt(i) }

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ module IR {
4545
this instanceof MkNextNode or
4646
this instanceof MkImplicitTrue or
4747
this instanceof MkCaseCheckNode or
48+
this instanceof MkTypeSwitchImplicitVariable or
4849
this instanceof MkImplicitLowerSliceBound or
4950
this instanceof MkImplicitUpperSliceBound or
5051
this instanceof MkImplicitMaxSliceBound or
@@ -167,6 +168,9 @@ module IR {
167168
or
168169
this instanceof MkCaseCheckNode and result = "case"
169170
or
171+
this instanceof MkTypeSwitchImplicitVariable and
172+
result = "type switch implicit variable declaration"
173+
or
170174
this instanceof MkImplicitLowerSliceBound and result = "implicit lower bound"
171175
or
172176
this instanceof MkImplicitUpperSliceBound and result = "implicit upper bound"
@@ -1269,6 +1273,52 @@ module IR {
12691273
}
12701274
}
12711275

1276+
/**
1277+
* An instruction corresponding to the implicit declaration of the variable
1278+
* `lv` in case clause `cc` and its assignment of the value `switchExpr` from
1279+
* the guard. This only occurs in case clauses in a type switch statement
1280+
* which declares a variable in its guard.
1281+
*
1282+
* For example, consider this type switch statement:
1283+
*
1284+
* ```go
1285+
* switch y := x.(type) {
1286+
* case Type1:
1287+
* f(y)
1288+
* ...
1289+
* }
1290+
* ```
1291+
*
1292+
* The `y` inside the case clause is actually a local variable with type
1293+
* `Type1` that is implicitly declared at the top of the case clause. In
1294+
* default clauses and case clauses which list more than one type, the type
1295+
* of the implicitly declared variable is the type of `switchExpr`.
1296+
*/
1297+
class TypeSwitchImplicitVariableInstruction extends Instruction, MkTypeSwitchImplicitVariable {
1298+
CaseClause cc;
1299+
LocalVariable lv;
1300+
Expr switchExpr;
1301+
1302+
TypeSwitchImplicitVariableInstruction() {
1303+
this = MkTypeSwitchImplicitVariable(cc, lv, switchExpr)
1304+
}
1305+
1306+
override predicate writes(ValueEntity v, Instruction rhs) {
1307+
v = lv and
1308+
rhs = evalExprInstruction(switchExpr)
1309+
}
1310+
1311+
override ControlFlow::Root getRoot() { result.isRootOf(cc) }
1312+
1313+
override string toString() { result = "implicit type switch variable declaration" }
1314+
1315+
override predicate hasLocationInfo(
1316+
string filepath, int startline, int startcolumn, int endline, int endcolumn
1317+
) {
1318+
cc.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
1319+
}
1320+
}
1321+
12721322
/**
12731323
* An instruction computing the implicit lower slice bound of zero in a slice expression without
12741324
* an explicit lower bound.

0 commit comments

Comments
 (0)