mirror of
https://github.com/github/codeql.git
synced 2026-02-28 21:03:50 +01:00
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:
committed by
Anders Schack-Mulligen
parent
dba93b30e7
commit
d79eaffd3a
@@ -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(
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user