Repository navigation
Expand file tree
/
Copy pathObjFlow.qll
More file actions
296 lines (266 loc) · 8.83 KB
/
Copy pathObjFlow.qll
File metadata and controls
296 lines (266 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/**
* Provides a dispatch relation `viableImpl_out` that reduces the set of
* dispatch targets for `Object.toString()` calls relative to the input
* dispatch relation `viableImpl_inp`.
*
* The set of dispatch targets for `Object.toString()` calls are reduced based
* on possible data flow from objects of more specific types to the qualifier.
*/
import java
private import VirtualDispatch
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.internal.BaseSSA as Base
private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.internal.DataFlowPrivate
private import semmle.code.java.dataflow.internal.ContainerFlow
private import semmle.code.java.dataflow.InstanceAccess
private import semmle.code.java.dispatch.internal.Unification
/**
* Gets a viable dispatch target for `ma`. This is the input dispatch relation.
*/
private Method viableImpl_inp(MethodCall ma) { result = viableImpl_v3(ma) }
private Callable dispatchCand(Call c) {
c instanceof ConstructorCall and result = c.getCallee().getSourceDeclaration()
or
result = viableImpl_inp(c)
}
/**
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
* The instance parameter is considered to have index `-1`.
*/
pragma[nomagic]
private predicate viableParam(Call call, int i, ParameterNode p) {
exists(DataFlowCallable callable |
callable.asCallable() = dispatchCand(call) and
p.isParameterOf(callable, i)
)
}
/**
* Holds if `arg` is a possible argument to `p` taking virtual dispatch into account.
*/
private predicate viableArgParam(ArgumentNode arg, ParameterNode p) {
exists(int i, DataFlowCall call |
viableParam(call.asCall(), i, p) and
arg.argumentOf(call, i)
)
}
private predicate returnStep(Node n1, Node n2) {
exists(ReturnStmt ret, Method m |
ret.getEnclosingCallable() = m and
ret.getExpr() = n1.asExpr() and
pragma[only_bind_out](m) = dispatchCand(n2.asExpr())
)
}
/**
* Holds if data may flow from `n1` to `n2` in a single step through a call or a return.
*/
private predicate callFlowStep(Node n1, Node n2) {
returnStep(n1, n2) or
viableArgParam(n1, n2)
}
/**
* Holds if data may flow from `n1` to `n2` in a single step through local
* flow, calls, returns, fields, array reads or writes, or container taint steps.
*/
private predicate step(Node n1, Node n2) {
exists(Base::SsaDefinition v, Base::SsaDefinition def |
def.(Base::SsaExplicitWrite).getDefiningExpr().(VariableAssign).getSource() = n1.asExpr()
or
def.(Base::SsaParameterInit).getParameter() = n1.asParameter()
or
exists(EnhancedForStmt for |
for.getVariable() = def.(Base::SsaExplicitWrite).getDefiningExpr() and
for.getExpr() = n1.asExpr()
)
|
(
v.(Base::SsaCapturedDefinition).getAnUltimateCapturedDefinition() = def or
v.getAnUltimateDefinition() = def
) and
v.getARead() = n2.asExpr()
)
or
Base::baseSsaAdjacentUseUse(n1.asExpr(), n2.asExpr())
or
exists(Callable c | n1.(InstanceParameterNode).getCallable() = c |
exists(InstanceAccess ia |
ia = n2.asExpr() and ia.getEnclosingCallable() = c and ia.isOwnInstanceAccess()
)
or
n2.(ImplicitInstanceAccess).getInstanceAccess().(OwnInstanceAccess).getEnclosingCallable() = c
)
or
n2.(FieldValueNode).getField().getAnAssignedValue() = n1.asExpr()
or
n2.asExpr().(FieldRead).getField() = n1.(FieldValueNode).getField()
or
n2.asExpr().(CastingExpr).getExpr() = n1.asExpr()
or
n2.asExpr().(ChooseExpr).getAResultExpr() = n1.asExpr()
or
n2.asExpr().(AssignExpr).getSource() = n1.asExpr()
or
n2.asExpr().(ArrayInit).getAnInit() = n1.asExpr()
or
n2.asExpr().(ArrayCreationExpr).getInit() = n1.asExpr()
or
n2.asExpr().(ArrayAccess).getArray() = n1.asExpr()
or
exists(Argument arg |
n1.asExpr() = arg and arg.isVararg() and n2.(ImplicitVarargsArray).getCall() = arg.getCall()
)
or
exists(AssignExpr a, Field v |
a.getSource() = n1.asExpr() and
a.getDest().(ArrayAccess).getArray() = v.getAnAccess() and
n2.asExpr() = v.getAnAccess().(VarRead)
)
or
exists(AssignExpr a |
a.getSource() = n1.asExpr() and
a.getDest().(ArrayAccess).getArray() = n2.asExpr()
)
or
callFlowStep(n1, n2) and not containerStep(_, n2.asExpr())
or
containerStep(n1.asExpr(), n2.asExpr())
or
exists(Field v |
containerStep(n1.asExpr(), v.getAnAccess()) and
n2.(FieldValueNode).getField() = v
)
}
/**
* Gets the type contained by `t` in the sense of element types of arrays and
* collections and upper bounds of type variables and wildcards.
*
* For example, given `Collection<? extends Foo[]>` the type `Foo` is returned.
*/
private RefType getContainedType(RefType t) {
not t instanceof Array and
not t instanceof ContainerType and
not t instanceof BoundedType and
result = t
or
result = getContainedType(t.(Array).getElementType())
or
result = getContainedType(t.(ContainerType).getElementType())
or
result = getContainedType(t.(BoundedType).getFirstUpperBoundType())
}
/**
* Holds if `t` can carry a value for which `t` reveals no type information,
* possibly nested in an array or container.
*/
private predicate containsObj(RefType t) {
t instanceof TypeObject
or
exists(RefType r | containsObj(r) and t = r.getASourceSupertype())
or
containsObj(getContainedType(t))
}
/**
* A node that can carry a value for which the static type reveals no type
* information other than `Object`.
*/
private class ObjNode extends Node {
ObjNode() { containsObj(this.getTypeBound()) }
}
private predicate objStep(ObjNode n1, ObjNode n2) { step(n1, n2) }
/**
* Holds if `n` has discarded the type information `t`.
*/
private predicate source(RefType t, ObjNode n) {
exists(Node n1, RefType nt |
not n1 instanceof ObjNode and
nt = n1.getTypeBound() and
step(n1, n) and
t = getContainedType(nt)
)
}
/**
* Holds if `n` is the qualifier of an `Object.toString()` call.
*/
private predicate sink(ObjNode n) {
exists(MethodCall toString |
toString.getQualifier() = n.asExpr() and
toString.getMethod() instanceof ToStringMethod
) and
n.getTypeBound().getErasure() instanceof TypeObject
}
private predicate relevantNodeBack(ObjNode n) {
sink(n)
or
exists(ObjNode mid | objStep(n, mid) and relevantNodeBack(mid))
}
private predicate relevantNode(ObjNode n) {
source(_, n) and relevantNodeBack(n)
or
exists(ObjNode mid | relevantNode(mid) and objStep(mid, n) and relevantNodeBack(n))
}
private newtype TObjFlowNode =
TObjNode(ObjNode n) { relevantNode(n) } or
TObjType(RefType t) { source(t, _) }
private predicate objStepPruned(TObjFlowNode node1, TObjFlowNode node2) {
exists(ObjNode n1, ObjNode n2 |
node1 = TObjNode(n1) and
node2 = TObjNode(n2) and
objStep(n1, n2)
)
or
exists(RefType t, ObjNode n |
node1 = TObjType(t) and
node2 = TObjNode(n) and
source(t, n)
)
}
private predicate flowSrc(TObjFlowNode src) { src instanceof TObjType }
private predicate flowSink(TObjFlowNode sink) { exists(ObjNode n | sink = TObjNode(n) and sink(n)) }
private predicate stepPlus(TObjFlowNode n1, TObjFlowNode n2) =
doublyBoundedFastTC(objStepPruned/2, flowSrc/1, flowSink/1)(n1, n2)
/**
* Holds if the qualifier `n` of an `Object.toString()` call might have type `t`.
*/
private predicate objType(ObjNode n, RefType t) { stepPlus(TObjType(t), TObjNode(n)) }
private VirtualMethodCall objectToString(ObjNode n) {
result.getQualifier() = n.asExpr() and sink(n)
}
/**
* Holds if `ma` is an `Object.toString()` call taking possibly improved type
* bounds into account.
*/
predicate objectToStringCall(VirtualMethodCall ma) { ma = objectToString(_) }
/**
* Holds if the qualifier of the `Object.toString()` call `ma` might have type `t`.
*/
private predicate objectToStringQualType(MethodCall ma, RefType t) {
exists(ObjNode n | ma = objectToString(n) and objType(n, t))
}
private Method viableImplObjectToString(MethodCall ma) {
exists(Method def, RefType t |
objectToStringQualType(ma, t) and
def = ma.getMethod() and
exists(RefType t2 |
result = viableMethodImpl(def, t.getSourceDeclaration(), t2) and
not Unification::failsUnification(t, t2)
)
)
}
/**
* Gets a viable dispatch target for `ma`. This is the output dispatch relation.
*
* The set of dispatch targets for `Object.toString()` calls are reduced based
* on possible data flow from objects of more specific types to the qualifier.
*/
Method viableImpl_out(MethodCall ma) {
result = viableImpl_inp(ma) and
(
result = viableImplObjectToString(ma) or
not ma = objectToString(_)
)
}
private predicate unificationTargetLeft(ParameterizedType t1) { objectToStringQualType(_, t1) }
private predicate unificationTargetRight(ParameterizedType t2) {
exists(viableMethodImpl(_, _, t2))
}
private module Unification = MkUnification<unificationTargetLeft/1, unificationTargetRight/1>;