Skip to content

Commit 291e278

Browse files
committed
Java: Adopt shared SSA data-flow integration (WIP)
1 parent dfda6e8 commit 291e278

File tree

20 files changed

+228
-946
lines changed

20 files changed

+228
-946
lines changed

java/ql/lib/semmle/code/java/dataflow/SSA.qll

+51-817
Large diffs are not rendered by default.

java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class BaseSsaSourceVariable extends TBaseSsaSourceVariable {
5555
}
5656

5757
cached
58-
private module SsaImpl {
58+
private module BaseSsaImpl {
5959
/** Gets the destination variable of an update of a tracked variable. */
6060
cached
6161
BaseSsaSourceVariable getDestVar(VariableUpdate upd) {
@@ -436,7 +436,7 @@ private module SsaImpl {
436436
}
437437
}
438438

439-
private import SsaImpl
439+
private import BaseSsaImpl
440440
private import SsaDefReaches
441441
import SsaPublic
442442

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll

+44-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import FlowSummaryImpl as FlowSummaryImpl
1010
private import DataFlowImplCommon as DataFlowImplCommon
1111
private import semmle.code.java.controlflow.Guards
1212
private import semmle.code.java.dataflow.RangeUtils
13+
private import SsaImpl as SsaImpl
1314

1415
/** Gets a string for approximating the name of a field. */
1516
string approximateFieldContent(FieldContent fc) { result = fc.getField().getName().prefix(1) }
@@ -21,6 +22,32 @@ private predicate deadcode(Expr e) {
2122
)
2223
}
2324

25+
module SsaFlow {
26+
module Impl = SsaImpl::DataFlowIntegration;
27+
28+
Impl::Node asNode(Node n) {
29+
n = TSsaNode(result)
30+
or
31+
result.(Impl::ExprNode).getExpr() = n.asExpr()
32+
or
33+
result.(Impl::ExprPostUpdateNode).getExpr() = n.(PostUpdateNode).getPreUpdateNode().asExpr()
34+
or
35+
TExplicitParameterNode(result.(Impl::ParameterNode).getParameter()) = n
36+
}
37+
38+
predicate localFlowStep(
39+
SsaImpl::Impl::DefinitionExt def, Node nodeFrom, Node nodeTo, boolean isUseStep
40+
) {
41+
not def instanceof SsaImpl::UntrackedDef and
42+
Impl::localFlowStep(def, asNode(nodeFrom), asNode(nodeTo), isUseStep)
43+
}
44+
45+
predicate localMustFlowStep(SsaImpl::Impl::DefinitionExt def, Node nodeFrom, Node nodeTo) {
46+
not def instanceof SsaImpl::UntrackedDef and
47+
Impl::localMustFlowStep(def, asNode(nodeFrom), asNode(nodeTo))
48+
}
49+
}
50+
2451
cached
2552
private module Cached {
2653
cached
@@ -31,6 +58,7 @@ private module Cached {
3158
not e.getType() instanceof VoidType and
3259
not e.getParent*() instanceof Annotation
3360
} or
61+
TSsaNode(SsaFlow::Impl::SsaNode node) or
3462
TExplicitParameterNode(Parameter p) { exists(p.getCallable().getBody()) } or
3563
TImplicitVarargsArray(Call c) {
3664
c.getCallee().isVarargs() and
@@ -137,6 +165,8 @@ module Public {
137165
result = this.(FieldValueNode).getField().getType()
138166
or
139167
result instanceof TypeObject and this instanceof AdditionalNode
168+
or
169+
result = this.(SsaNode).getDefinitionExt().getSourceVariable().getType()
140170
}
141171

142172
/** Gets the callable in which this node occurs. */
@@ -198,6 +228,18 @@ module Public {
198228
abstract predicate isParameterOf(DataFlowCallable c, int pos);
199229
}
200230

231+
class SsaNode extends Node, TSsaNode {
232+
private SsaFlow::Impl::SsaNode node;
233+
234+
SsaNode() { this = TSsaNode(node) }
235+
236+
SsaImpl::Impl::DefinitionExt getDefinitionExt() { result = node.getDefinitionExt() }
237+
238+
override Location getLocation() { result = node.getLocation() }
239+
240+
override string toString() { result = node.toString() }
241+
}
242+
201243
/**
202244
* A parameter, viewed as a node in a data flow graph.
203245
*/
@@ -398,7 +440,8 @@ module Private {
398440
result.asSummarizedCallable() = n.(FlowSummaryNode).getSummarizedCallable() or
399441
result.asCallable() = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() or
400442
result.asFieldScope() = n.(FieldValueNode).getField() or
401-
result.asCallable() = any(Expr e | n.(AdditionalNode).nodeAt(e, _)).getEnclosingCallable()
443+
result.asCallable() = any(Expr e | n.(AdditionalNode).nodeAt(e, _)).getEnclosingCallable() or
444+
result.asCallable() = n.(SsaNode).getDefinitionExt().getBasicBlock().getEnclosingCallable()
402445
}
403446

404447
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll

+5-1
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,11 @@ predicate forceHighPrecision(Content c) {
610610
}
611611

612612
/** Holds if `n` should be hidden from path explanations. */
613-
predicate nodeIsHidden(Node n) { n instanceof FlowSummaryNode }
613+
predicate nodeIsHidden(Node n) {
614+
n instanceof FlowSummaryNode
615+
or
616+
n instanceof SsaNode
617+
}
614618

615619
class LambdaCallKind = Method; // the "apply" method in the functional interface
616620

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll

+33-44
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ private import semmle.code.java.dataflow.FlowSummary
1212
private import semmle.code.java.dataflow.InstanceAccess
1313
private import FlowSummaryImpl as FlowSummaryImpl
1414
private import TaintTrackingUtil as TaintTrackingUtil
15+
private import SsaImpl as SsaImpl
1516
private import DataFlowNodes
1617
import DataFlowNodes::Public
1718

@@ -99,6 +100,10 @@ predicate hasNonlocalValue(FieldRead fr) {
99100
)
100101
}
101102

103+
private predicate capturedVariableRead(Node n) {
104+
n.asExpr().(VarRead).getVariable() instanceof CapturedVariable
105+
}
106+
102107
cached
103108
private module Cached {
104109
/**
@@ -108,7 +113,7 @@ private module Cached {
108113
predicate localFlowStep(Node node1, Node node2) {
109114
simpleLocalFlowStep0(node1, node2, _)
110115
or
111-
adjacentUseUse(node1.asExpr(), node2.asExpr())
116+
SsaFlow::localFlowStep(_, node1, node2, _)
112117
or
113118
// Simple flow through library code is included in the exposed local
114119
// step relation, even though flow is technically inter-procedural
@@ -125,13 +130,31 @@ private module Cached {
125130
predicate simpleLocalFlowStep(Node node1, Node node2, string model) {
126131
simpleLocalFlowStep0(node1, node2, model)
127132
or
133+
exists(boolean isUseStep |
134+
SsaFlow::localFlowStep(_, node1, node2, isUseStep) and
135+
not capturedVariableRead(node2) and
136+
model = ""
137+
|
138+
isUseStep = false
139+
or
140+
not exists(FieldRead fr |
141+
hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr()
142+
) and
143+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1, _)
144+
)
145+
or
128146
any(AdditionalValueStep a).step(node1, node2) and
129147
pragma[only_bind_out](node1.getEnclosingCallable()) =
130148
pragma[only_bind_out](node2.getEnclosingCallable()) and
131149
model = "AdditionalValueStep" and
132150
// prevent recursive call
133151
(any(AdditionalValueStep a).step(_, _) implies any())
134152
}
153+
154+
cached
155+
Node getABarrierNode(Guard guard, SsaVariable def, boolean branch) {
156+
SsaFlow::asNode(result) = SsaImpl::DataFlowIntegration::getABarrierNode(guard, def, branch)
157+
}
135158
}
136159

137160
/**
@@ -147,14 +170,7 @@ predicate localMustFlowStep(Node node1, Node node2) {
147170
node2.(ImplicitInstanceAccess).getInstanceAccess().(OwnInstanceAccess).getEnclosingCallable()
148171
)
149172
or
150-
exists(SsaImplicitInit init |
151-
init.isParameterDefinition(node1.asParameter()) and init.getAUse() = node2.asExpr()
152-
)
153-
or
154-
exists(SsaExplicitUpdate upd |
155-
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() and
156-
upd.getAUse() = node2.asExpr()
157-
)
173+
SsaFlow::localMustFlowStep(_, node1, node2)
158174
or
159175
node2.asExpr().(CastingExpr).getExpr() = node1.asExpr()
160176
or
@@ -169,10 +185,6 @@ predicate localMustFlowStep(Node node1, Node node2) {
169185

170186
import Cached
171187

172-
private predicate capturedVariableRead(Node n) {
173-
n.asExpr().(VarRead).getVariable() instanceof CapturedVariable
174-
}
175-
176188
/**
177189
* Holds if there is a data flow step from `e1` to `e2` that only steps from
178190
* child to parent in the AST.
@@ -214,34 +226,8 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) {
214226
private predicate simpleLocalFlowStep0(Node node1, Node node2, string model) {
215227
(
216228
TaintTrackingUtil::forceCachingInSameStage() and
217-
// Variable flow steps through adjacent def-use and use-use pairs.
218-
exists(SsaExplicitUpdate upd |
219-
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or
220-
upd.getDefiningExpr().(AssignOp) = node1.asExpr() or
221-
upd.getDefiningExpr().(RecordBindingVariableExpr) = node1.asExpr()
222-
|
223-
node2.asExpr() = upd.getAFirstUse() and
224-
not capturedVariableRead(node2)
225-
)
226-
or
227-
exists(SsaImplicitInit init |
228-
init.isParameterDefinition(node1.asParameter()) and
229-
node2.asExpr() = init.getAFirstUse() and
230-
not capturedVariableRead(node2)
231-
)
232-
or
233-
adjacentUseUse(node1.asExpr(), node2.asExpr()) and
234-
not exists(FieldRead fr |
235-
hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr()
236-
) and
237-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1, _) and
238-
not capturedVariableRead(node2)
239-
or
240229
ThisFlow::adjacentThisRefs(node1, node2)
241230
or
242-
adjacentUseUse(node1.(PostUpdateNode).getPreUpdateNode().asExpr(), node2.asExpr()) and
243-
not capturedVariableRead(node2)
244-
or
245231
ThisFlow::adjacentThisRefs(node1.(PostUpdateNode).getPreUpdateNode(), node2)
246232
or
247233
simpleAstFlowStep(node1.asExpr(), node2.asExpr())
@@ -401,13 +387,16 @@ signature predicate guardChecksSig(Guard g, Expr e, boolean branch);
401387
* in data flow and taint tracking.
402388
*/
403389
module BarrierGuard<guardChecksSig/3 guardChecks> {
390+
pragma[nomagic]
391+
private predicate guardChecksSsaDef(Guard g, SsaVariable v, boolean branch) {
392+
guardChecks(g, v.getAUse(), branch)
393+
}
394+
404395
/** Gets a node that is safely guarded by the given guard check. */
405396
Node getABarrierNode() {
406-
exists(Guard g, SsaVariable v, boolean branch, VarRead use |
407-
guardChecks(g, v.getAUse(), branch) and
408-
use = v.getAUse() and
409-
g.controls(use.getBasicBlock(), branch) and
410-
result.asExpr() = use
397+
exists(Guard g, SsaVariable v, boolean branch |
398+
guardChecksSsaDef(g, v, branch) and
399+
result = getABarrierNode(g, v, branch)
411400
)
412401
}
413402
}

java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll

+22-32
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,36 @@ import java
66
import semmle.code.java.dataflow.SSA
77
private import semmle.code.java.frameworks.Assertions
88

9-
private predicate emptyDecl(SsaExplicitUpdate ssa) {
10-
exists(LocalVariableDeclExpr decl |
11-
decl = ssa.getDefiningExpr() and
12-
not exists(decl.getInit()) and
13-
not exists(EnhancedForStmt for | for.getVariable() = decl)
14-
)
9+
private predicate emptyDecl(LocalVariableDeclExpr decl) {
10+
not exists(decl.getInit()) and
11+
not exists(EnhancedForStmt for | for.getVariable() = decl)
1512
}
1613

17-
/**
18-
* A dead SSA variable. Excludes parameters, and phi nodes are never dead, so only includes `VariableUpdate`s.
19-
*/
20-
predicate deadLocal(SsaExplicitUpdate ssa) {
21-
ssa.getSourceVariable().getVariable() instanceof LocalScopeVariable and
22-
not exists(ssa.getAUse()) and
23-
not exists(SsaPhiNode phi | phi.getAPhiInput() = ssa) and
24-
not exists(SsaImplicitInit init | init.captures(ssa)) and
25-
not emptyDecl(ssa) and
26-
not readImplicitly(ssa, _)
14+
/** A dead variable update. */
15+
predicate deadLocal(VariableUpdate upd) {
16+
upd.getDestVar() instanceof LocalScopeVariable and
17+
not exists(SsaExplicitUpdate ssa | upd = ssa.getDefiningExpr()) and
18+
not emptyDecl(upd) and
19+
not readImplicitly(upd, _)
2720
}
2821

2922
/**
30-
* A dead SSA variable that is expected to be dead as indicated by an assertion.
23+
* A dead variable update that is expected to be dead as indicated by an assertion.
3124
*/
32-
predicate expectedDead(SsaExplicitUpdate ssa) {
33-
deadLocal(ssa) and
34-
assertFail(ssa.getBasicBlock(), _)
35-
}
25+
predicate expectedDead(VariableUpdate upd) { assertFail(upd.getBasicBlock(), _) }
3626

3727
/**
38-
* A dead SSA variable that is overwritten by a live SSA definition.
28+
* A dead update that is overwritten by a live update.
3929
*/
40-
predicate overwritten(SsaExplicitUpdate ssa) {
41-
deadLocal(ssa) and
42-
exists(SsaExplicitUpdate overwrite |
43-
overwrite.getSourceVariable() = ssa.getSourceVariable() and
30+
predicate overwritten(VariableUpdate upd) {
31+
deadLocal(upd) and
32+
exists(VariableUpdate overwrite |
33+
overwrite.getDestVar() = upd.getDestVar() and
4434
not deadLocal(overwrite) and
45-
not overwrite.getDefiningExpr() instanceof LocalVariableDeclExpr and
35+
not overwrite instanceof LocalVariableDeclExpr and
4636
exists(BasicBlock bb1, BasicBlock bb2, int i, int j |
47-
bb1.getNode(i) = ssa.getCfgNode() and
48-
bb2.getNode(j) = overwrite.getCfgNode()
37+
bb1.getNode(i) = upd.getControlFlowNode() and
38+
bb2.getNode(j) = overwrite.getControlFlowNode()
4939
|
5040
bb1.getABBSuccessor+() = bb2
5141
or
@@ -63,9 +53,9 @@ predicate read(LocalScopeVariable v) {
6353
readImplicitly(_, v)
6454
}
6555

66-
private predicate readImplicitly(SsaExplicitUpdate ssa, LocalScopeVariable v) {
67-
v = ssa.getSourceVariable().getVariable() and
68-
exists(TryStmt try | try.getAResourceVariable() = ssa.getDefiningExpr().getDestVar())
56+
predicate readImplicitly(VariableUpdate upd, LocalScopeVariable v) {
57+
v = upd.getDestVar() and
58+
exists(TryStmt try | try.getAResourceVariable() = upd.getDestVar())
6959
}
7060

7161
/**

java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql

+5-6
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,12 @@ predicate excludedInit(Type t, Expr decl) {
4040
)
4141
}
4242

43-
from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa
43+
from VariableUpdate def, LocalScopeVariable v
4444
where
45-
def = ssa.getDefiningExpr() and
46-
v = ssa.getSourceVariable().getVariable() and
47-
deadLocal(ssa) and
48-
not expectedDead(ssa) and
49-
overwritten(ssa) and
45+
def.getDestVar() = v and
46+
deadLocal(def) and
47+
not expectedDead(def) and
48+
overwritten(def) and
5049
not exists(LocalVariableDeclExpr decl | def = decl |
5150
excludedInit(decl.getVariable().getType(), decl.getInit())
5251
)

java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
import java
1212
import DeadLocals
1313

14-
from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa
14+
from VariableUpdate def, LocalScopeVariable v
1515
where
16-
def = ssa.getDefiningExpr() and
17-
v = ssa.getSourceVariable().getVariable() and
18-
deadLocal(ssa) and
19-
not expectedDead(ssa) and
20-
not overwritten(ssa) and
16+
def.getDestVar() = v and
17+
deadLocal(def) and
18+
not expectedDead(def) and
19+
not overwritten(def) and
2120
read(v) and
2221
not def.(AssignExpr).getSource() instanceof NullLiteral and
2322
(def instanceof Assignment or def.(UnaryAssignExpr).getParent() instanceof ExprStmt)
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
failures
21
testFailures
2+
failures

0 commit comments

Comments
 (0)