Prune unreachable paths in the Java dataflow library based on call context.

We now detect patterns like
f(bool cond){
       if(cond)
        then A
        else B
and prune branches for calls like f(true) or f(false).
This pruning is done both in the local (bigstep) flow graph
as well as in the inter-procedural dataflow graph.
This commit is contained in:
Cornelius Riemenschneider
2019-09-10 14:31:10 +02:00
committed by Anders Schack-Mulligen
parent dba93b30e7
commit d79eaffd3a
4 changed files with 145 additions and 58 deletions

View File

@@ -905,8 +905,10 @@ private predicate localFlowExit(Node node, Configuration config) {
*/
pragma[nomagic]
private predicate localFlowStepPlus(
Node node1, Node node2, boolean preservesValue, Configuration config
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc
) {
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
(
localFlowEntry(node1, config) and
(
localFlowStep(node1, node2, config) and preservesValue = true
@@ -914,22 +916,24 @@ private predicate localFlowStepPlus(
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
) and
node1 != node2 and
cc.validFor(node1) and
nodeCand(node2, unbind(config))
or
exists(Node mid |
localFlowStepPlus(node1, mid, preservesValue, config) and
localFlowStepPlus(node1, mid, preservesValue, config, cc) and
localFlowStep(mid, node2, config) and
not mid instanceof CastNode and
nodeCand(node2, unbind(config))
)
or
exists(Node mid |
localFlowStepPlus(node1, mid, _, config) and
localFlowStepPlus(node1, mid, _, config, cc) and
additionalLocalFlowStep(mid, node2, config) and
not mid instanceof CastNode and
preservesValue = false and
nodeCand(node2, unbind(config))
)
)
}
/**
@@ -938,9 +942,9 @@ private predicate localFlowStepPlus(
*/
pragma[noinline]
private predicate localFlowBigStep(
Node node1, Node node2, boolean preservesValue, Configuration config
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext
) {
localFlowStepPlus(node1, node2, preservesValue, config) and
localFlowStepPlus(node1, node2, preservesValue, config, callContext) and
localFlowExit(node2, config)
}
@@ -1000,7 +1004,7 @@ private class AccessPathFrontNilNode extends Node {
(
any(Configuration c).isSource(this)
or
localFlowBigStep(_, this, false, _)
localFlowBigStep(_, this, false, _, _)
or
additionalJumpStep(_, this, _)
)
@@ -1023,12 +1027,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
(
exists(Node mid |
flowCandFwd(mid, fromArg, apf, config) and
localFlowBigStep(mid, node, true, config)
localFlowBigStep(mid, node, true, config, _)
)
or
exists(Node mid, AccessPathFrontNil nil |
flowCandFwd(mid, fromArg, nil, config) and
localFlowBigStep(mid, node, false, config) and
localFlowBigStep(mid, node, false, config, _) and
apf = node.(AccessPathFrontNilNode).getApf()
)
or
@@ -1122,13 +1126,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
apf instanceof AccessPathFrontNil
or
exists(Node mid |
localFlowBigStep(node, mid, true, config) and
localFlowBigStep(node, mid, true, config, _) and
flowCand(mid, toReturn, apf, config)
)
or
exists(Node mid, AccessPathFrontNil nil |
flowCandFwd(node, _, apf, config) and
localFlowBigStep(node, mid, false, config) and
localFlowBigStep(node, mid, false, config, _) and
flowCand(mid, toReturn, nil, config) and
apf instanceof AccessPathFrontNil
)
@@ -1363,12 +1367,12 @@ private predicate flowFwd0(
(
exists(Node mid |
flowFwd(mid, fromArg, apf, ap, config) and
localFlowBigStep(mid, node, true, config)
localFlowBigStep(mid, node, true, config, _)
)
or
exists(Node mid, AccessPathNil nil |
flowFwd(mid, fromArg, _, nil, config) and
localFlowBigStep(mid, node, false, config) and
localFlowBigStep(mid, node, false, config, _) and
ap = node.(AccessPathNilNode).getAp() and
apf = ap.(AccessPathNil).getFront()
)
@@ -1472,13 +1476,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio
ap instanceof AccessPathNil
or
exists(Node mid |
localFlowBigStep(node, mid, true, config) and
localFlowBigStep(node, mid, true, config, _) and
flow(mid, toReturn, ap, config)
)
or
exists(Node mid, AccessPathNil nil |
flowFwd(node, _, _, ap, config) and
localFlowBigStep(node, mid, false, config) and
localFlowBigStep(node, mid, false, config, _) and
flow(mid, toReturn, nil, config) and
ap instanceof AccessPathNil
)
@@ -1664,8 +1668,11 @@ module PathGraph {
*/
private class PathNodeMid extends PathNode, TPathNodeMid {
Node node;
CallContext cc;
AccessPath ap;
Configuration config;
PathNodeMid() { this = TPathNodeMid(node, cc, ap, config) }
@@ -1711,6 +1718,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
*/
private class PathNodeSink extends PathNode, TPathNodeSink {
Node node;
Configuration config;
PathNodeSink() { this = TPathNodeSink(node, config) }
@@ -1729,15 +1737,18 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
* a callable is recorded by `cc`.
*/
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) {
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and
exists(LocalCallContext localCC | localCC.matchesCallContext(cc) |
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration(), localCC) and
cc = mid.getCallContext() and
ap = mid.getAp()
or
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration(), localCC) and
cc = mid.getCallContext() and
mid.getAp() instanceof AccessPathNil and
ap = node.(AccessPathNilNode).getAp()
or
) or
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
(
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
cc instanceof CallContextAny and
ap = mid.getAp()
@@ -1760,6 +1771,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
pathThroughCallable(mid, node, cc, ap)
or
valuePathThroughCallable(mid, node, cc) and ap = mid.getAp()
)
}
pragma[noinline]
@@ -1880,7 +1892,7 @@ private predicate pathIntoCallable(
pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and
p.isParameterOf(callable, i)
|
if reducedViableImplInCallContext(_, callable, call)
if recordDataFlowCallSite(call, callable)
then innercc = TSpecificCall(call, i, emptyAp)
else innercc = TSomeCall(p, emptyAp)
)
@@ -2180,8 +2192,11 @@ private module FlowExploration {
private class PartialPathNodePriv extends PartialPathNode {
Node node;
CallContext cc;
PartialAccessPath ap;
Configuration config;
PartialPathNodePriv() { this = TPartialPathNodeMk(node, cc, ap, config) }
@@ -2378,7 +2393,7 @@ private module FlowExploration {
partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and
p.isParameterOf(callable, i)
|
if reducedViableImplInCallContext(_, callable, call)
if recordDataFlowCallSite(call, callable)
then innercc = TSpecificCall(call, i, emptyAp)
else innercc = TSomeCall(p, emptyAp)
)
@@ -2446,7 +2461,6 @@ private module FlowExploration {
)
}
}
import FlowExploration
private predicate partialFlow(

View File

@@ -125,7 +125,7 @@ private module ImplCommon {
outercc = TSomeCall(getAParameter(c), _)
or
exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) |
reducedViableImplInCallContext(_, c, other)
recordDataFlowCallSite(other, c)
)
)
}
@@ -152,7 +152,7 @@ private module ImplCommon {
exists(int i, DataFlowCallable callable |
viableParamArg1(p, callable, i, arg, outercc, call)
|
if reducedViableImplInCallContext(_, callable, call)
if recordDataFlowCallSite(call, callable)
then innercc = TSpecificCall(call, i, true)
else innercc = TSomeCall(p, true)
)
@@ -164,7 +164,7 @@ private module ImplCommon {
exists(DataFlowCall call, int i, DataFlowCallable callable |
result = TSpecificCall(call, i, _) and
p.isParameterOf(callable, i) and
reducedViableImplInCallContext(_, callable, call)
recordDataFlowCallSite(call, callable)
)
}
@@ -575,11 +575,21 @@ private module ImplCommon {
exists(ArgumentNode arg | arg.argumentOf(call, -1))
}
/**
* Record a call site in the dataflow graph if it either improves
* virtual dispatch or if we can remove unreachable edges by recoring this call site
*/
cached
predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
reducedViableImplInCallContext(_, callable, call) or
exists(Node n | n.getEnclosingCallable() = callable | isUnreachableInCall(n, call))
}
cached
newtype TCallContext =
TAnyCallContext() or
TSpecificCall(DataFlowCall call, int i, boolean emptyAp) {
reducedViableImplInCallContext(_, _, call) and
recordDataFlowCallSite(call, _) and
(emptyAp = true or emptyAp = false) and
(
exists(call.getArgument(i))
@@ -593,6 +603,11 @@ private module ImplCommon {
cached
newtype TReturnPosition =
TReturnPosition0(DataFlowCallable c, ReturnKind kind) { returnPosition(_, c, kind) }
cached
newtype TLocalFlowCallContext =
TAnyLocalCall() or
TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) }
}
pragma[noinline]
@@ -609,7 +624,8 @@ private module ImplCommon {
* - `TAnyCallContext()` : No restrictions on method flow.
* - `TSpecificCall(DataFlowCall call, int i)` : Flow entered through the `i`th
* parameter at the given `call`. This call improves the set of viable
* dispatch targets for at least one method call in the current callable.
* dispatch targets for at least one method call in the current callable
* or helps pruning unreachable nodes from the data flow graph.
* - `TSomeCall(ParameterNode p)` : Flow entered through parameter `p`. The
* originating call does not improve the set of dispatch targets for any
* method call in the current callable and was therefore not recorded.
@@ -633,6 +649,8 @@ private module ImplCommon {
result = "CcCall(" + call + ", " + i + ")"
)
}
DataFlowCall getCall() { this = TSpecificCall(result, _, _) }
}
class CallContextSomeCall extends CallContextCall, TSomeCall {
@@ -645,9 +663,53 @@ private module ImplCommon {
}
}
/**
* A call context which is used to restrict local data flow nodes
* to nodes which are actually reachable in a call context.
*/
abstract class LocalCallContext extends TLocalFlowCallContext {
abstract string toString();
abstract predicate matchesCallContext(CallContext ctx);
abstract predicate validFor(Node n);
}
class LocalCallContextAny extends LocalCallContext, TAnyLocalCall {
override string toString() { result = "LocalCcAny" }
override predicate matchesCallContext(CallContext ctx) {
not ctx instanceof CallContextSpecificCall or
not exists(TSpecificLocalCall(ctx.(CallContextSpecificCall).getCall()))
}
override predicate validFor(Node n) { any() }
}
class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall {
LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) }
DataFlowCall call;
DataFlowCall getCall() { result = call }
override string toString() { result = "LocalCcCall(" + call + ")" }
override predicate matchesCallContext(CallContext ctx) {
ctx.(CallContextSpecificCall).getCall() = call
}
override predicate validFor(Node n) {
exists(Node n2 |
isUnreachableInCall(n2, call) and n2.getEnclosingCallable() = n.getEnclosingCallable()
)
}
}
/** A callable tagged with a relevant return kind. */
class ReturnPosition extends TReturnPosition0 {
private DataFlowCallable c;
private ReturnKind kind;
ReturnPosition() { this = TReturnPosition0(c, kind) }

View File

@@ -1,7 +1,10 @@
private import java
private import DataFlowUtil
private import DataFlowImplCommon
private import DataFlowDispatch
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.dataflow.DefUse
private import semmle.code.java.dataflow.TypeFlow
private newtype TReturnKind = TNormalReturnKind()
@@ -283,3 +286,44 @@ class DataFlowCall extends Call {
/** Gets the data flow node corresponding to this call. */
ExprNode getNode() { result.getExpr() = this }
}
/** An expression that always has the same boolean value. */
private predicate constantBooleanExpr(Expr e, boolean val) {
e.(CompileTimeConstantExpr).getBooleanValue() = val
or
exists(SsaExplicitUpdate v, Expr src |
e = v.getAUse() and
src = v.getDefiningExpr().(VariableAssign).getSource() and
constantBooleanExpr(src, val)
)
}
/** An expression that always has the same boolean value. */
class ConstantBooleanExprNode extends ArgumentNode, ExprNode {
ConstantBooleanExprNode() { constantBooleanExpr(this.getExpr(), _) }
/** Gets the boolean value of this expression. */
boolean getBooleanValue() { constantBooleanExpr(this.getExpr(), result) }
}
/**
* holds if the node `n` is unreachable when called from `call`
*/
cached
predicate isUnreachableInCall(Node n, DataFlowCall call) {
exists(
ExplicitParameterNode paramNode, ConstantBooleanExprNode arg, BasicBlock bb,
SsaImplicitInit varInit, Guard guard
|
// get argument and parameter for this call
viableParamArg(call, paramNode, arg) and
// get the ssa variable definition for this parameter
varInit.isParameterDefinition(paramNode.getParameter()) and
// which is used in a guard
varInit.getAUse() = guard and
// which controls that bb is not active
guard.controls(bb, arg.getBooleanValue().booleanNot()) and
// and the node we pass in is in this bb
bb.getANode() = n.asExpr()
)
}