mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Java: Add support for callback-based library models.
This commit is contained in:
@@ -7,10 +7,10 @@ private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch
|
||||
|
||||
private module DispatchImpl {
|
||||
/** Gets a viable implementation of the target of the given `Call`. */
|
||||
Callable viableCallable(Call c) {
|
||||
result = VirtualDispatch::viableCallable(c)
|
||||
DataFlowCallable viableCallable(DataFlowCall c) {
|
||||
result = VirtualDispatch::viableCallable(c.asCall())
|
||||
or
|
||||
result.(SummarizedCallable) = c.getCallee().getSourceDeclaration()
|
||||
result.(SummarizedCallable) = c.asCall().getCallee().getSourceDeclaration()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ private module DispatchImpl {
|
||||
private predicate relevantContext(Call ctx, int i) {
|
||||
exists(Callable c |
|
||||
mayBenefitFromCallContext(_, c, i) and
|
||||
c = viableCallable(ctx)
|
||||
c = VirtualDispatch::viableCallable(ctx)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,24 +88,25 @@ private module DispatchImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the set of viable implementations that can be called by `ma`
|
||||
* Holds if the set of viable implementations that can be called by `call`
|
||||
* might be improved by knowing the call context. This is the case if the
|
||||
* qualifier is a parameter of the enclosing callable `c`.
|
||||
*/
|
||||
predicate mayBenefitFromCallContext(MethodAccess ma, Callable c) {
|
||||
mayBenefitFromCallContext(ma, c, _)
|
||||
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
|
||||
mayBenefitFromCallContext(call.asCall(), c, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
|
||||
* restricted to those `ma`s for which a context might make a difference.
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
|
||||
result = viableCallable(ma) and
|
||||
exists(int i, Callable c, Method def, RefType t, boolean exact |
|
||||
Method viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableCallable(call) and
|
||||
exists(int i, Callable c, Method def, RefType t, boolean exact, MethodAccess ma |
|
||||
ma = call.asCall() and
|
||||
mayBenefitFromCallContext(ma, c, i) and
|
||||
c = viableCallable(ctx) and
|
||||
contextArgHasType(ctx, i, t, exact) and
|
||||
contextArgHasType(ctx.asCall(), i, t, exact) and
|
||||
ma.getMethod().getSourceDeclaration() = def
|
||||
|
|
||||
exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())
|
||||
|
||||
@@ -326,12 +326,14 @@ module Private {
|
||||
* The instance argument is considered to have index `-1`.
|
||||
*/
|
||||
predicate argumentOf(DataFlowCall call, int pos) {
|
||||
exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
|
||||
exists(Argument arg | this.asExpr() = arg |
|
||||
call.asCall() = arg.getCall() and pos = arg.getPosition()
|
||||
)
|
||||
or
|
||||
call = this.(ImplicitVarargsArray).getCall() and
|
||||
pos = call.getCallee().getNumberOfParameters() - 1
|
||||
call.asCall() = this.(ImplicitVarargsArray).getCall() and
|
||||
pos = call.asCall().getCallee().getNumberOfParameters() - 1
|
||||
or
|
||||
pos = -1 and this = getInstanceArgument(call)
|
||||
pos = -1 and this = getInstanceArgument(call.asCall())
|
||||
or
|
||||
this.(SummaryNode).isArgumentOf(call, pos)
|
||||
}
|
||||
@@ -361,7 +363,7 @@ module Private {
|
||||
|
||||
/** Gets the underlying call. */
|
||||
DataFlowCall getCall() {
|
||||
result = this.asExpr()
|
||||
result.asCall() = this.asExpr()
|
||||
or
|
||||
this.(SummaryNode).isOut(result)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ private import DataFlowDispatch
|
||||
private import semmle.code.java.controlflow.Guards
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
private import ContainerFlow
|
||||
private import semmle.code.java.dataflow.FlowSummary
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
import DataFlowNodes::Private
|
||||
|
||||
@@ -24,7 +25,7 @@ class ReturnKind extends TReturnKind {
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
result = call.getNode() and
|
||||
result.getCall() = call and
|
||||
kind = TNormalReturnKind()
|
||||
}
|
||||
|
||||
@@ -214,9 +215,55 @@ class DataFlowExpr = Expr;
|
||||
|
||||
class DataFlowType = RefType;
|
||||
|
||||
class DataFlowCall extends Call {
|
||||
/** Gets the data flow node corresponding to this call. */
|
||||
ExprNode getNode() { result.getExpr() = this }
|
||||
private newtype TDataFlowCall =
|
||||
TCall(Call c) or
|
||||
TSummaryCall(SummarizedCallable c, Node receiver) {
|
||||
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
||||
}
|
||||
|
||||
/** A call relevant for data flow. Includes both source calls and synthesized calls. */
|
||||
class DataFlowCall extends TDataFlowCall {
|
||||
/** Gets the source (non-synthesized) call this corresponds to, if any. */
|
||||
Call asCall() { this = TCall(result) }
|
||||
|
||||
/** Gets the enclosing callable of this call. */
|
||||
abstract DataFlowCallable getEnclosingCallable();
|
||||
|
||||
/** Gets a textual representation of this call. */
|
||||
abstract string toString();
|
||||
|
||||
/** Gets the location of this call. */
|
||||
abstract Location getLocation();
|
||||
}
|
||||
|
||||
/** A source call, that is, a `Call`. */
|
||||
class SrcCall extends DataFlowCall, TCall {
|
||||
Call call;
|
||||
|
||||
SrcCall() { this = TCall(call) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result = call.getEnclosingCallable() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/** A synthesized call inside a `SummarizedCallable`. */
|
||||
class SummaryCall extends DataFlowCall, TSummaryCall {
|
||||
private SummarizedCallable c;
|
||||
private Node receiver;
|
||||
|
||||
SummaryCall() { this = TSummaryCall(c, receiver) }
|
||||
|
||||
/** Gets the data flow node that this call targets. */
|
||||
Node getReceiver() { result = receiver }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result = c }
|
||||
|
||||
override string toString() { result = "[summary] call to " + receiver + " in " + c }
|
||||
|
||||
override Location getLocation() { result = c.getLocation() }
|
||||
}
|
||||
|
||||
/** Holds if `e` is an expression that always has the same Boolean value `val`. */
|
||||
@@ -275,13 +322,23 @@ predicate isImmutableOrUnobservable(Node n) {
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { n instanceof SummaryNode }
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
class LambdaCallKind = Method; // the "apply" method in the functional interface
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
||||
exists(FunctionalExpr func, FunctionalInterface interface |
|
||||
creation.asExpr() = func and
|
||||
func.asMethod() = c and
|
||||
func.getType().(RefType).getSourceDeclaration() = interface and
|
||||
kind = interface.getRunMethod()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
receiver = call.(SummaryCall).getReceiver() and
|
||||
getNodeDataFlowType(receiver).getSourceDeclaration().(FunctionalInterface).getRunMethod() = kind
|
||||
}
|
||||
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
|
||||
@@ -151,7 +151,7 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
|
||||
ma.getCallee().getSourceDeclaration() = m and m.returnsValue(argNo)
|
||||
|
|
||||
node2.asExpr() = ma and
|
||||
node1.(ArgumentNode).argumentOf(ma, argNo)
|
||||
node1.(ArgumentNode).argumentOf(any(DataFlowCall c | c.asCall() = ma), argNo)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1, node2, true)
|
||||
|
||||
@@ -20,7 +20,7 @@ predicate parameterPosition(int i) { i in [-1 .. any(Parameter p).getPosition()]
|
||||
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSummaryNode(c, state) }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
DataFlowCall summaryDataFlowCall(Node receiver) { none() }
|
||||
DataFlowCall summaryDataFlowCall(Node receiver) { result.(SummaryCall).getReceiver() = receiver }
|
||||
|
||||
/** Gets the type of content `c`. */
|
||||
DataFlowType getContentType(Content c) { result = c.getType() }
|
||||
@@ -35,13 +35,18 @@ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
|
||||
* Gets the type of the `i`th parameter in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, int i) { none() }
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, int i) {
|
||||
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getParameterType(i))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the return type of kind `rk` in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { none() }
|
||||
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
|
||||
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getReturnType()) and
|
||||
exists(rk)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an external flow summary exists for `c` with input specification
|
||||
@@ -106,13 +111,13 @@ class InterpretNode extends TInterpretNode {
|
||||
Node asNode() { this = TNode(result) }
|
||||
|
||||
/** Gets the call that this node corresponds to, if any. */
|
||||
DataFlowCall asCall() { result = this.asElement() }
|
||||
DataFlowCall asCall() { result.asCall() = this.asElement() }
|
||||
|
||||
/** Gets the callable that this node corresponds to, if any. */
|
||||
DataFlowCallable asCallable() { result = this.asElement() }
|
||||
|
||||
/** Gets the target of this call, if any. */
|
||||
Callable getCallTarget() { result = this.asCall().getCallee().getSourceDeclaration() }
|
||||
Callable getCallTarget() { result = this.asCall().asCall().getCallee().getSourceDeclaration() }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
string toString() {
|
||||
|
||||
@@ -142,8 +142,8 @@ private predicate viableParamCand(Call call, int i, ParameterNode p) {
|
||||
* Holds if `arg` is a possible argument to `p` taking virtual dispatch into account.
|
||||
*/
|
||||
private predicate viableArgParamCand(ArgumentNode arg, ParameterNode p) {
|
||||
exists(int i, Call call |
|
||||
viableParamCand(call, i, p) and
|
||||
exists(int i, DataFlowCall call |
|
||||
viableParamCand(call.asCall(), i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ private predicate viableParam(Call call, int i, ParameterNode p) {
|
||||
* Holds if `arg` is a possible argument to `p` taking virtual dispatch into account.
|
||||
*/
|
||||
private predicate viableArgParam(ArgumentNode arg, ParameterNode p) {
|
||||
exists(int i, Call call |
|
||||
viableParam(call, i, p) and
|
||||
exists(int i, DataFlowCall call |
|
||||
viableParam(call.asCall(), i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
100
java/ql/test/library-tests/dataflow/callback-dispatch/A.java
Normal file
100
java/ql/test/library-tests/dataflow/callback-dispatch/A.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package my.callback.qltest;
|
||||
|
||||
public class A {
|
||||
public interface Consumer1 {
|
||||
void eat(Object o);
|
||||
}
|
||||
|
||||
public interface Consumer2 {
|
||||
void eat(Object o);
|
||||
}
|
||||
|
||||
public interface Consumer3<T> {
|
||||
void eat(T o);
|
||||
}
|
||||
|
||||
static void applyConsumer1(Object x, Consumer1 con) {
|
||||
// summary:
|
||||
// con.eat(x);
|
||||
}
|
||||
|
||||
static void applyConsumer2(Object x, Consumer2 con) {
|
||||
// summary:
|
||||
// con.eat(x);
|
||||
}
|
||||
|
||||
static <T> void applyConsumer3(T x, Consumer3<T> con) {
|
||||
// summary:
|
||||
// con.eat(x);
|
||||
}
|
||||
|
||||
public interface Producer1<T> {
|
||||
T make();
|
||||
}
|
||||
|
||||
static <T> T applyProducer1(Producer1<T> prod) {
|
||||
// summary:
|
||||
// return prod.make();
|
||||
return null;
|
||||
}
|
||||
|
||||
public interface Converter1<T1,T2> {
|
||||
T2 conv(T1 x);
|
||||
}
|
||||
|
||||
static <T1,T2> T2 applyConverter1(T1 x, Converter1<T1,T2> con) {
|
||||
// summary:
|
||||
// return con.conv(x);
|
||||
return null;
|
||||
}
|
||||
|
||||
static Object source(int i) { return null; }
|
||||
|
||||
static void sink(Object o) { }
|
||||
|
||||
void foo(boolean b1, boolean b2) {
|
||||
applyConsumer1(source(1), p -> {
|
||||
sink(p); // $ flow=1
|
||||
});
|
||||
|
||||
Object handler;
|
||||
if (b1) {
|
||||
handler = (Consumer1)(p -> { sink(p); }); // $ flow=2
|
||||
} else {
|
||||
handler = (Consumer2)(p -> { sink(p); }); // $ flow=3
|
||||
}
|
||||
if (b2) {
|
||||
applyConsumer1(source(2), (Consumer1)handler);
|
||||
} else {
|
||||
applyConsumer2(source(3), (Consumer2)handler);
|
||||
}
|
||||
|
||||
applyConsumer1(source(4), new Consumer1() {
|
||||
@Override public void eat(Object o) {
|
||||
sink(o); // $ MISSING: flow=4
|
||||
}
|
||||
});
|
||||
|
||||
applyConsumer1(source(5), A::sink); // $ flow=5
|
||||
|
||||
Consumer2 c = new MyConsumer2();
|
||||
applyConsumer2(source(6), c);
|
||||
}
|
||||
|
||||
static class MyConsumer2 implements Consumer2 {
|
||||
@Override public void eat(Object o) {
|
||||
sink(o); // $ MISSING: flow=6
|
||||
}
|
||||
}
|
||||
|
||||
void foo2() {
|
||||
Consumer3<Integer> c = i -> sink(i); // $ flow=7
|
||||
applyConsumer3((Integer)source(7), c);
|
||||
|
||||
sink(applyProducer1(() -> (Integer)source(8))); // $ flow=8
|
||||
|
||||
sink(applyConverter1((Integer)source(9), i -> i)); // $ flow=9
|
||||
|
||||
sink(applyConverter1((Integer)source(10), i -> new int[]{i})[0]); // $ flow=10
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
class SummaryModelTest extends SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"my.callback.qltest;A;false;applyConsumer1;(Object,Consumer1);;Argument[0];Parameter[0] of Argument[1];value",
|
||||
"my.callback.qltest;A;false;applyConsumer2;(Object,Consumer2);;Argument[0];Parameter[0] of Argument[1];value",
|
||||
"my.callback.qltest;A;false;applyConsumer3;(Object,Consumer3);;Argument[0];Parameter[0] of Argument[1];value",
|
||||
"my.callback.qltest;A;false;applyProducer1;(Producer1);;ReturnValue of Argument[0];ReturnValue;value",
|
||||
"my.callback.qltest;A;false;applyConverter1;(Object,Converter1);;Argument[0];Parameter[0] of Argument[1];value",
|
||||
"my.callback.qltest;A;false;applyConverter1;(Object,Converter1);;ReturnValue of Argument[1];ReturnValue;value"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class Conf extends DataFlow::Configuration {
|
||||
Conf() { this = "qltest:callback-dispatch" }
|
||||
|
||||
override predicate isSource(DataFlow::Node n) {
|
||||
n.asExpr().(MethodAccess).getMethod().hasName("source")
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node n) {
|
||||
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
|
||||
}
|
||||
}
|
||||
|
||||
class HasFlowTest extends InlineExpectationsTest {
|
||||
HasFlowTest() { this = "HasFlowTest" }
|
||||
|
||||
override string getARelevantTag() { result = "flow" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "flow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
value = src.asExpr().(MethodAccess).getAnArgument().toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user