mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Merge pull request #13982 from aschackmull/dataflow/typeflow-calledge-pruning
Dataflow: Add type-based call-edge pruning.
This commit is contained in:
@@ -1676,13 +1676,25 @@ abstract class InstanceAccess extends Expr {
|
||||
/** Holds if this instance access is to an enclosing instance of type `t`. */
|
||||
predicate isEnclosingInstanceAccess(RefType t) {
|
||||
t = this.getQualifier().getType().(RefType).getSourceDeclaration() and
|
||||
t != this.getEnclosingCallable().getDeclaringType()
|
||||
t != this.getEnclosingCallable().getDeclaringType() and
|
||||
not this.isSuperInterfaceAccess()
|
||||
or
|
||||
not exists(this.getQualifier()) and
|
||||
(not exists(this.getQualifier()) or this.isSuperInterfaceAccess()) and
|
||||
exists(LambdaExpr lam | lam.asMethod() = this.getEnclosingCallable() |
|
||||
t = lam.getAnonymousClass().getEnclosingType()
|
||||
)
|
||||
}
|
||||
|
||||
// A default method on an interface, `I`, may be invoked using `I.super.m()`.
|
||||
// This always refers to the implemented interfaces of `this`. This form of
|
||||
// qualified `super` cannot be combined with accessing an enclosing instance.
|
||||
// JLS 15.11.2. "Accessing Superclass Members using super"
|
||||
// JLS 15.12. "Method Invocation Expressions"
|
||||
// JLS 15.12.1. "Compile-Time Step 1: Determine Type to Search"
|
||||
private predicate isSuperInterfaceAccess() {
|
||||
this instanceof SuperAccess and
|
||||
this.getQualifier().getType().(RefType).getSourceDeclaration() instanceof Interface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -440,6 +440,18 @@ predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `t` is the type of the `this` value corresponding to the the
|
||||
* `SuperAccess`. As the `SuperAccess` expression has the type of the supertype,
|
||||
* the type `t` is a stronger type bound.
|
||||
*/
|
||||
private predicate superAccess(SuperAccess sup, RefType t) {
|
||||
sup.isEnclosingInstanceAccess(t)
|
||||
or
|
||||
sup.isOwnInstanceAccess() and
|
||||
t = sup.getEnclosingCallable().getDeclaringType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` has type `t` and this information is discarded, such that `t`
|
||||
* might be a better type bound for nodes where `n` flows to. This might include
|
||||
@@ -452,7 +464,8 @@ private predicate typeFlowBaseCand(TypeFlowNode n, RefType t) {
|
||||
downcastSuccessor(n.asExpr(), srctype) or
|
||||
instanceOfGuarded(n.asExpr(), srctype) or
|
||||
arrayInstanceOfGuarded(n.asExpr(), srctype) or
|
||||
n.asExpr().(FunctionalExpr).getConstructedType() = srctype
|
||||
n.asExpr().(FunctionalExpr).getConstructedType() = srctype or
|
||||
superAccess(n.asExpr(), srctype)
|
||||
|
|
||||
t = srctype.(BoundedType).getAnUltimateUpperBoundType()
|
||||
or
|
||||
|
||||
@@ -326,13 +326,18 @@ string ppReprType(DataFlowType t) {
|
||||
else result = t.toString()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate compatibleTypes0(DataFlowType t1, DataFlowType t2) {
|
||||
erasedHaveIntersection(t1, t2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
|
||||
* a node of type `t1` to a node of type `t2`.
|
||||
*/
|
||||
bindingset[t1, t2]
|
||||
pragma[inline_late]
|
||||
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { erasedHaveIntersection(t1, t2) }
|
||||
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { compatibleTypes0(t1, t2) }
|
||||
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends ExprNode {
|
||||
|
||||
@@ -133,6 +133,39 @@ private module Cached {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the value of `node2` is given by `node1`.
|
||||
*/
|
||||
predicate localMustFlowStep(Node node1, Node node2) {
|
||||
exists(Callable c | node1.(InstanceParameterNode).getCallable() = c |
|
||||
exists(InstanceAccess ia |
|
||||
ia = node2.asExpr() and ia.getEnclosingCallable() = c and ia.isOwnInstanceAccess()
|
||||
)
|
||||
or
|
||||
c =
|
||||
node2.(ImplicitInstanceAccess).getInstanceAccess().(OwnInstanceAccess).getEnclosingCallable()
|
||||
)
|
||||
or
|
||||
exists(SsaImplicitInit init |
|
||||
init.isParameterDefinition(node1.asParameter()) and init.getAUse() = node2.asExpr()
|
||||
)
|
||||
or
|
||||
exists(SsaExplicitUpdate upd |
|
||||
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() and
|
||||
upd.getAUse() = node2.asExpr()
|
||||
)
|
||||
or
|
||||
node2.asExpr().(CastingExpr).getExpr() = node1.asExpr()
|
||||
or
|
||||
node2.asExpr().(AssignExpr).getSource() = node1.asExpr()
|
||||
or
|
||||
node1 =
|
||||
unique(FlowSummaryNode n1 |
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(n1.getSummaryNode(),
|
||||
node2.(FlowSummaryNode).getSummaryNode(), true)
|
||||
)
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
private predicate capturedVariableRead(Node n) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java
|
||||
* A file detected as generated by the Apache Thrift Compiler.
|
||||
*/
|
||||
class ThriftGeneratedFile extends GeneratedFile {
|
||||
cached
|
||||
ThriftGeneratedFile() {
|
||||
exists(JavadocElement t | t.getFile() = this |
|
||||
exists(string msg | msg = t.getText() | msg.regexpMatch("(?i).*\\bAutogenerated by Thrift.*"))
|
||||
|
||||
60
java/ql/test/library-tests/dataflow/typeflow-dispatch/A.java
Normal file
60
java/ql/test/library-tests/dataflow/typeflow-dispatch/A.java
Normal file
@@ -0,0 +1,60 @@
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
|
||||
public class A {
|
||||
static String source(String tag) { return null; }
|
||||
|
||||
static void sink(Object o) { }
|
||||
|
||||
interface MyConsumer {
|
||||
void run(Object o);
|
||||
}
|
||||
|
||||
void apply(MyConsumer f, Object x) {
|
||||
f.run(x);
|
||||
}
|
||||
|
||||
void apply_wrap(MyConsumer f, Object x) {
|
||||
apply(f, x);
|
||||
}
|
||||
|
||||
void testLambdaDispatch1() {
|
||||
apply_wrap(x -> { sink(x); }, source("A")); // $ hasValueFlow=A
|
||||
apply_wrap(x -> { sink(x); }, null); // no flow
|
||||
apply_wrap(x -> { }, source("B"));
|
||||
apply_wrap(x -> { }, null);
|
||||
}
|
||||
|
||||
void forEach_wrap(List<Object> l, Consumer<Object> f) {
|
||||
l.forEach(f);
|
||||
}
|
||||
|
||||
void testLambdaDispatch2() {
|
||||
List<Object> tainted = new ArrayList<>();
|
||||
tainted.add(source("L"));
|
||||
List<Object> safe = new ArrayList<>();
|
||||
forEach_wrap(safe, x -> { sink(x); }); // no flow
|
||||
forEach_wrap(tainted, x -> { sink(x); }); // $ hasValueFlow=L
|
||||
}
|
||||
|
||||
static class TaintedClass {
|
||||
public String toString() { return source("TaintedClass"); }
|
||||
}
|
||||
|
||||
static class SafeClass {
|
||||
public String toString() { return "safe"; }
|
||||
}
|
||||
|
||||
String convertToString(Object o) {
|
||||
return o.toString();
|
||||
}
|
||||
|
||||
String convertToString_wrap(Object o) {
|
||||
return convertToString(o);
|
||||
}
|
||||
|
||||
void testToString1() {
|
||||
String unused = convertToString_wrap(new TaintedClass());
|
||||
sink(convertToString_wrap(new SafeClass())); // no flow
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import TestUtilities.InlineFlowTest
|
||||
import DefaultFlowTest
|
||||
Reference in New Issue
Block a user