Skip to content

Commit 7b764ae

Browse files
authored
Merge pull request github#6682 from aschackmull/java/callbacks
Java: Add support for callback-based library models.
2 parents 47b5165 + ab86227 commit 7b764ae

File tree

13 files changed

+255
-41
lines changed

13 files changed

+255
-41
lines changed

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch
77

88
private module DispatchImpl {
99
/** Gets a viable implementation of the target of the given `Call`. */
10-
Callable viableCallable(Call c) {
11-
result = VirtualDispatch::viableCallable(c)
10+
DataFlowCallable viableCallable(DataFlowCall c) {
11+
result = VirtualDispatch::viableCallable(c.asCall())
1212
or
13-
result.(SummarizedCallable) = c.getCallee().getSourceDeclaration()
13+
result.(SummarizedCallable) = c.asCall().getCallee().getSourceDeclaration()
1414
}
1515

1616
/**
@@ -45,7 +45,7 @@ private module DispatchImpl {
4545
private predicate relevantContext(Call ctx, int i) {
4646
exists(Callable c |
4747
mayBenefitFromCallContext(_, c, i) and
48-
c = viableCallable(ctx)
48+
c = VirtualDispatch::viableCallable(ctx)
4949
)
5050
}
5151

@@ -88,24 +88,25 @@ private module DispatchImpl {
8888
}
8989

9090
/**
91-
* Holds if the set of viable implementations that can be called by `ma`
91+
* Holds if the set of viable implementations that can be called by `call`
9292
* might be improved by knowing the call context. This is the case if the
9393
* qualifier is a parameter of the enclosing callable `c`.
9494
*/
95-
predicate mayBenefitFromCallContext(MethodAccess ma, Callable c) {
96-
mayBenefitFromCallContext(ma, c, _)
95+
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
96+
mayBenefitFromCallContext(call.asCall(), c, _)
9797
}
9898

9999
/**
100-
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
101-
* restricted to those `ma`s for which a context might make a difference.
100+
* Gets a viable dispatch target of `call` in the context `ctx`. This is
101+
* restricted to those `call`s for which a context might make a difference.
102102
*/
103-
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
104-
result = viableCallable(ma) and
105-
exists(int i, Callable c, Method def, RefType t, boolean exact |
103+
Method viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
104+
result = viableCallable(call) and
105+
exists(int i, Callable c, Method def, RefType t, boolean exact, MethodAccess ma |
106+
ma = call.asCall() and
106107
mayBenefitFromCallContext(ma, c, i) and
107108
c = viableCallable(ctx) and
108-
contextArgHasType(ctx, i, t, exact) and
109+
contextArgHasType(ctx.asCall(), i, t, exact) and
109110
ma.getMethod().getSourceDeclaration() = def
110111
|
111112
exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,14 @@ module Private {
326326
* The instance argument is considered to have index `-1`.
327327
*/
328328
predicate argumentOf(DataFlowCall call, int pos) {
329-
exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
329+
exists(Argument arg | this.asExpr() = arg |
330+
call.asCall() = arg.getCall() and pos = arg.getPosition()
331+
)
330332
or
331-
call = this.(ImplicitVarargsArray).getCall() and
332-
pos = call.getCallee().getNumberOfParameters() - 1
333+
call.asCall() = this.(ImplicitVarargsArray).getCall() and
334+
pos = call.asCall().getCallee().getNumberOfParameters() - 1
333335
or
334-
pos = -1 and this = getInstanceArgument(call)
336+
pos = -1 and this = getInstanceArgument(call.asCall())
335337
or
336338
this.(SummaryNode).isArgumentOf(call, pos)
337339
}
@@ -361,7 +363,7 @@ module Private {
361363

362364
/** Gets the underlying call. */
363365
DataFlowCall getCall() {
364-
result = this.asExpr()
366+
result.asCall() = this.asExpr()
365367
or
366368
this.(SummaryNode).isOut(result)
367369
}

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

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ private import DataFlowDispatch
55
private import semmle.code.java.controlflow.Guards
66
private import semmle.code.java.dataflow.SSA
77
private import ContainerFlow
8+
private import semmle.code.java.dataflow.FlowSummary
89
private import FlowSummaryImpl as FlowSummaryImpl
910
import DataFlowNodes::Private
1011

@@ -24,7 +25,7 @@ class ReturnKind extends TReturnKind {
2425
* `kind`.
2526
*/
2627
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
27-
result = call.getNode() and
28+
result.getCall() = call and
2829
kind = TNormalReturnKind()
2930
}
3031

@@ -214,9 +215,55 @@ class DataFlowExpr = Expr;
214215

215216
class DataFlowType = RefType;
216217

217-
class DataFlowCall extends Call {
218-
/** Gets the data flow node corresponding to this call. */
219-
ExprNode getNode() { result.getExpr() = this }
218+
private newtype TDataFlowCall =
219+
TCall(Call c) or
220+
TSummaryCall(SummarizedCallable c, Node receiver) {
221+
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
222+
}
223+
224+
/** A call relevant for data flow. Includes both source calls and synthesized calls. */
225+
class DataFlowCall extends TDataFlowCall {
226+
/** Gets the source (non-synthesized) call this corresponds to, if any. */
227+
Call asCall() { this = TCall(result) }
228+
229+
/** Gets the enclosing callable of this call. */
230+
abstract DataFlowCallable getEnclosingCallable();
231+
232+
/** Gets a textual representation of this call. */
233+
abstract string toString();
234+
235+
/** Gets the location of this call. */
236+
abstract Location getLocation();
237+
}
238+
239+
/** A source call, that is, a `Call`. */
240+
class SrcCall extends DataFlowCall, TCall {
241+
Call call;
242+
243+
SrcCall() { this = TCall(call) }
244+
245+
override DataFlowCallable getEnclosingCallable() { result = call.getEnclosingCallable() }
246+
247+
override string toString() { result = call.toString() }
248+
249+
override Location getLocation() { result = call.getLocation() }
250+
}
251+
252+
/** A synthesized call inside a `SummarizedCallable`. */
253+
class SummaryCall extends DataFlowCall, TSummaryCall {
254+
private SummarizedCallable c;
255+
private Node receiver;
256+
257+
SummaryCall() { this = TSummaryCall(c, receiver) }
258+
259+
/** Gets the data flow node that this call targets. */
260+
Node getReceiver() { result = receiver }
261+
262+
override DataFlowCallable getEnclosingCallable() { result = c }
263+
264+
override string toString() { result = "[summary] call to " + receiver + " in " + c }
265+
266+
override Location getLocation() { result = c.getLocation() }
220267
}
221268

222269
/** Holds if `e` is an expression that always has the same Boolean value `val`. */
@@ -275,13 +322,23 @@ predicate isImmutableOrUnobservable(Node n) {
275322
/** Holds if `n` should be hidden from path explanations. */
276323
predicate nodeIsHidden(Node n) { n instanceof SummaryNode }
277324

278-
class LambdaCallKind = Unit;
325+
class LambdaCallKind = Method; // the "apply" method in the functional interface
279326

280327
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
281-
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
328+
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
329+
exists(FunctionalExpr func, FunctionalInterface interface |
330+
creation.asExpr() = func and
331+
func.asMethod() = c and
332+
func.getType().(RefType).getSourceDeclaration() = interface and
333+
kind = interface.getRunMethod()
334+
)
335+
}
282336

283337
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
284-
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
338+
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
339+
receiver = call.(SummaryCall).getReceiver() and
340+
getNodeDataFlowType(receiver).getSourceDeclaration().(FunctionalInterface).getRunMethod() = kind
341+
}
285342

286343
/** Extra data-flow steps needed for lambda flow analysis. */
287344
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
151151
ma.getCallee().getSourceDeclaration() = m and m.returnsValue(argNo)
152152
|
153153
node2.asExpr() = ma and
154-
node1.(ArgumentNode).argumentOf(ma, argNo)
154+
node1.(ArgumentNode).argumentOf(any(DataFlowCall c | c.asCall() = ma), argNo)
155155
)
156156
or
157157
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1, node2, true)

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ predicate parameterPosition(int i) { i in [-1 .. any(Parameter p).getPosition()]
2020
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSummaryNode(c, state) }
2121

2222
/** Gets the synthesized data-flow call for `receiver`. */
23-
DataFlowCall summaryDataFlowCall(Node receiver) { none() }
23+
SummaryCall summaryDataFlowCall(Node receiver) { result.getReceiver() = receiver }
2424

2525
/** Gets the type of content `c`. */
2626
DataFlowType getContentType(Content c) { result = c.getType() }
@@ -35,13 +35,18 @@ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
3535
* Gets the type of the `i`th parameter in a synthesized call that targets a
3636
* callback of type `t`.
3737
*/
38-
DataFlowType getCallbackParameterType(DataFlowType t, int i) { none() }
38+
DataFlowType getCallbackParameterType(DataFlowType t, int i) {
39+
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getParameterType(i))
40+
}
3941

4042
/**
4143
* Gets the return type of kind `rk` in a synthesized call that targets a
4244
* callback of type `t`.
4345
*/
44-
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { none() }
46+
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
47+
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getReturnType()) and
48+
exists(rk)
49+
}
4550

4651
/**
4752
* Holds if an external flow summary exists for `c` with input specification
@@ -106,13 +111,13 @@ class InterpretNode extends TInterpretNode {
106111
Node asNode() { this = TNode(result) }
107112

108113
/** Gets the call that this node corresponds to, if any. */
109-
DataFlowCall asCall() { result = this.asElement() }
114+
DataFlowCall asCall() { result.asCall() = this.asElement() }
110115

111116
/** Gets the callable that this node corresponds to, if any. */
112117
DataFlowCallable asCallable() { result = this.asElement() }
113118

114119
/** Gets the target of this call, if any. */
115-
Callable getCallTarget() { result = this.asCall().getCallee().getSourceDeclaration() }
120+
Callable getCallTarget() { result = this.asCall().asCall().getCallee().getSourceDeclaration() }
116121

117122
/** Gets a textual representation of this node. */
118123
string toString() {

java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ private predicate viableParamCand(Call call, int i, ParameterNode p) {
142142
* Holds if `arg` is a possible argument to `p` taking virtual dispatch into account.
143143
*/
144144
private predicate viableArgParamCand(ArgumentNode arg, ParameterNode p) {
145-
exists(int i, Call call |
146-
viableParamCand(call, i, p) and
145+
exists(int i, DataFlowCall call |
146+
viableParamCand(call.asCall(), i, p) and
147147
arg.argumentOf(call, i)
148148
)
149149
}

java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ private predicate viableParam(Call call, int i, ParameterNode p) {
4343
* Holds if `arg` is a possible argument to `p` taking virtual dispatch into account.
4444
*/
4545
private predicate viableArgParam(ArgumentNode arg, ParameterNode p) {
46-
exists(int i, Call call |
47-
viableParam(call, i, p) and
46+
exists(int i, DataFlowCall call |
47+
viableParam(call.asCall(), i, p) and
4848
arg.argumentOf(call, i)
4949
)
5050
}

java/ql/src/Telemetry/ExternalAPI.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ class ExternalAPI extends Callable {
4848
private DataFlow::Node getAnInput() {
4949
exists(Call call | call.getCallee().getSourceDeclaration() = this |
5050
result.asExpr().(Argument).getCall() = call or
51-
result.(ArgumentNode).getCall() = call
51+
result.(ArgumentNode).getCall().asCall() = call
5252
)
5353
}
5454

5555
/** Gets a node that is an output from a call to this API. */
5656
private DataFlow::Node getAnOutput() {
5757
exists(Call call | call.getCallee().getSourceDeclaration() = this |
5858
result.asExpr() = call or
59-
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall() = call
59+
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call
6060
)
6161
}
6262

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package my.callback.qltest;
2+
3+
public class A {
4+
public interface Consumer1 {
5+
void eat(Object o);
6+
}
7+
8+
public interface Consumer2 {
9+
void eat(Object o);
10+
}
11+
12+
public interface Consumer3<T> {
13+
void eat(T o);
14+
}
15+
16+
static void applyConsumer1(Object x, Consumer1 con) {
17+
// summary:
18+
// con.eat(x);
19+
}
20+
21+
static void applyConsumer2(Object x, Consumer2 con) {
22+
// summary:
23+
// con.eat(x);
24+
}
25+
26+
static <T> void applyConsumer3(T x, Consumer3<T> con) {
27+
// summary:
28+
// con.eat(x);
29+
}
30+
31+
public interface Producer1<T> {
32+
T make();
33+
}
34+
35+
static <T> T applyProducer1(Producer1<T> prod) {
36+
// summary:
37+
// return prod.make();
38+
return null;
39+
}
40+
41+
public interface Converter1<T1,T2> {
42+
T2 conv(T1 x);
43+
}
44+
45+
static <T1,T2> T2 applyConverter1(T1 x, Converter1<T1,T2> con) {
46+
// summary:
47+
// return con.conv(x);
48+
return null;
49+
}
50+
51+
static Object source(int i) { return null; }
52+
53+
static void sink(Object o) { }
54+
55+
void foo(boolean b1, boolean b2) {
56+
applyConsumer1(source(1), p -> {
57+
sink(p); // $ flow=1
58+
});
59+
60+
Object handler;
61+
if (b1) {
62+
handler = (Consumer1)(p -> { sink(p); }); // $ flow=2
63+
} else {
64+
handler = (Consumer2)(p -> { sink(p); }); // $ flow=3
65+
}
66+
if (b2) {
67+
applyConsumer1(source(2), (Consumer1)handler);
68+
} else {
69+
applyConsumer2(source(3), (Consumer2)handler);
70+
}
71+
72+
applyConsumer1(source(4), new Consumer1() {
73+
@Override public void eat(Object o) {
74+
sink(o); // $ MISSING: flow=4
75+
}
76+
});
77+
78+
applyConsumer1(source(5), A::sink); // $ flow=5
79+
80+
Consumer2 c = new MyConsumer2();
81+
applyConsumer2(source(6), c);
82+
}
83+
84+
static class MyConsumer2 implements Consumer2 {
85+
@Override public void eat(Object o) {
86+
sink(o); // $ MISSING: flow=6
87+
}
88+
}
89+
90+
void foo2() {
91+
Consumer3<Integer> c = i -> sink(i); // $ flow=7
92+
applyConsumer3((Integer)source(7), c);
93+
94+
sink(applyProducer1(() -> (Integer)source(8))); // $ flow=8
95+
96+
sink(applyConverter1((Integer)source(9), i -> i)); // $ flow=9
97+
98+
sink(applyConverter1((Integer)source(10), i -> new int[]{i})[0]); // $ flow=10
99+
}
100+
}

java/ql/test/library-tests/dataflow/callback-dispatch/test.expected

Whitespace-only changes.

0 commit comments

Comments
 (0)