Merge pull request #13982 from aschackmull/dataflow/typeflow-calledge-pruning

Dataflow: Add type-based call-edge pruning.
This commit is contained in:
Anders Schack-Mulligen
2023-09-21 13:33:08 +02:00
committed by GitHub
19 changed files with 919 additions and 195 deletions

View File

@@ -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
}
}
/**

View File

@@ -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

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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.*"))

View 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
}
}

View File

@@ -0,0 +1,2 @@
import TestUtilities.InlineFlowTest
import DefaultFlowTest