diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll index 90983888acb..54598d108c3 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll @@ -1,6 +1,21 @@ private import java +private import DataFlowPrivate import semmle.code.java.dispatch.VirtualDispatch +/** + * A return position. A return position describes how a value can return + * from a given callable. For Java, this is simply a method return. + */ +class ReturnPosition extends Method { + ReturnPosition() { exists(ReturnNode ret | ret.getEnclosingCallable() = this) } + + /** Gets the callable that a value can be returned from. */ + Method getCallable() { result = this } + + /** Gets a return node that can return a value at this position. */ + ReturnNode getAReturnNode() { result.getEnclosingCallable() = this } +} + cached private module DispatchImpl { /** @@ -186,5 +201,17 @@ private module DispatchImpl { result = viableImplInCallContext(ma, ctx) and reducedViableImplInReturn(result, ma) } + + /** + * Gets a node that can read the value returned at position `pos` for the + * call `call`. + */ + cached + OutNode getAnOutputAtCall(DataFlowCall call, ReturnPosition pos) { + exists(Method m | pos.getCallable() = m | + m = viableCallable(call) and + result = call.getNode() + ) + } } import DispatchImpl diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 9d28e37cc8a..518bd18ee57 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1,16 +1,15 @@ /** * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from `DataFlowUtil` - * and adds a global analysis, mainly exposed through the `Configuration` class. - * This file exists in several identical copies, allowing queries to use - * multiple `Configuration` classes that depend on each other without - * introducing mutual recursion among those configurations. + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. */ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public /** * A configuration of interprocedural data flow analysis. This defines @@ -95,7 +94,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowToExpr(Expr sink) { hasFlowTo(exprNode(sink)) } + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -114,7 +113,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi pragma[noinline] private predicate isAdditionalFlowStep( - Node node1, Node node2, Callable callable1, Callable callable2, Configuration config + Node node1, Node node2, DataFlowCallable callable1, DataFlowCallable callable2, + Configuration config ) { config.isAdditionalFlowStep(node1, node2) and callable1 = node1.getEnclosingCallable() and @@ -125,7 +125,7 @@ private predicate isAdditionalFlowStep( * Holds if the additional step from `node1` to `node2` does not jump between callables. */ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { - exists(Callable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) + exists(DataFlowCallable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) } /** @@ -148,11 +148,6 @@ private predicate localFlowStep(Node node1, Node node2, boolean preservesValue, additionalLocalFlowStep(node1, node2, config) and preservesValue = false } -pragma[noinline] -private Method returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - /** * Holds if field flow should be used for the given configuration. */ @@ -209,11 +204,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) ) or // flow out of a callable - exists(Method m, MethodAccess ma, ReturnNode ret | + exists(ReturnNode ret | nodeCandFwd1(ret, stored, config) and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node.asExpr() = ma + node = getAnOutputAtCall(_, ret.getPosition()) ) ) } @@ -287,10 +280,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) { ) or // flow out of a callable - exists(Method m, ExprNode ma | - nodeCand1(ma, stored, config) and - m = returnNodeGetEnclosingCallable(node) and - m = viableImpl(ma.getExpr()) + exists(Node out | + nodeCand1(out, stored, config) and + out = getAnOutputAtCall(_, node.(ReturnNode).getPosition()) ) ) } @@ -316,11 +308,13 @@ private predicate readCand1(Content f, Configuration config) { * from the configuration. */ pragma[nomagic] -private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Configuration config) { +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { nodeCand1(node, false, config) and p = node and t = getErasedRepr(node.getType()) and - not parameterValueFlowsThrough(p) + not parameterValueFlowsThrough(p, _) or nodeCand1(node, false, unbind(config)) and exists(Node mid | @@ -367,21 +361,21 @@ private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Con * additional step from the configuration. */ private predicate simpleArgumentFlowsThrough( - ArgumentNode arg, ExprNode call, RefType t, Configuration config + ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(ParameterNode param, ReturnNode ret | nodeCand1(arg, false, unbind(config)) and - nodeCand1(call, false, unbind(config)) and + nodeCand1(out, false, unbind(config)) and viableParamArg(param, arg) and simpleParameterFlow(param, ret, t, config) and - arg.argumentOf(call.getExpr(), _) + out = getAnOutputAtCall(arg.getCall(), ret.getPosition()) ) } /** - * Holds if data can flow from `node1` to `node2` by a step through a method. + * Holds if data can flow from `node1` to `node2` by a step through a callable. */ -private predicate flowThroughMethod( +private predicate flowThroughCallableCand1( Node node1, Node node2, boolean preservesValue, Configuration config ) { simpleArgumentFlowsThrough(node1, node2, _, config) and preservesValue = false @@ -391,13 +385,13 @@ private predicate flowThroughMethod( /** * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a method. + * through a callable. */ -private predicate localFlowStepOrFlowThroughMethod( +private predicate localFlowStepOrFlowThroughCallable( Node node1, Node node2, boolean preservesValue, Configuration config ) { localFlowStep(node1, node2, preservesValue, config) or - flowThroughMethod(node1, node2, preservesValue, config) + flowThroughCallableCand1(node1, node2, preservesValue, config) } /** @@ -405,7 +399,7 @@ private predicate localFlowStepOrFlowThroughMethod( * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ -private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { +private predicate flowOutOfCallableCand1(Node node1, Node node2, Configuration config) { nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) and ( @@ -415,13 +409,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config viableParamArg(p, node2.(PostUpdateNode).getPreUpdateNode()) ) or - // flow out of a method - exists(Method m, MethodAccess ma, ReturnNode ret | - ret = node1 and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node2.asExpr() = ma - ) + // flow out of a callable + node2 = getAnOutputAtCall(_, node1.(ReturnNode).getPosition()) ) } @@ -429,7 +418,7 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config * Holds if data can flow into a callable and that this step is part of a * path from a source to a sink. */ -private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { +private predicate flowIntoCallableCand1(Node node1, Node node2, Configuration config) { viableParamArg(node2, node1) and nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) @@ -441,7 +430,9 @@ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) * contexts. */ private int branch(Node n1, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n1, n, conf) or flowIntoCallableCand1(n1, n, conf) + ) } /** @@ -450,7 +441,9 @@ private int branch(Node n1, Configuration conf) { * contexts. */ private int join(Node n2, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n, n2, conf) or flowIntoCallableCand1(n, n2, conf) + ) } /** @@ -460,10 +453,10 @@ private int join(Node n2, Configuration conf) { * `allowsFieldFlow` flag indicates whether the branching is within the limit * specified by the configuration. */ -private predicate flowOutOfCallable( +private predicate flowOutOfCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowOutOfCallable(node1, node2, config) and + flowOutOfCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -478,10 +471,10 @@ private predicate flowOutOfCallable( * path from a source to a sink. The `allowsFieldFlow` flag indicates whether * the branching is within the limit specified by the configuration. */ -private predicate flowIntoCallable( +private predicate flowIntoCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowIntoCallable(node1, node2, config) and + flowIntoCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -505,7 +498,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ( exists(Node mid, boolean preservesValue | nodeCandFwd2(mid, fromArg, stored, config) and - localFlowStepOrFlowThroughMethod(mid, node, preservesValue, config) and + localFlowStepOrFlowThroughCallable(mid, node, preservesValue, config) and (stored = false or preservesValue = true) ) or @@ -534,14 +527,14 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, _, stored, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, false, stored, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (stored = false or allowsFieldFlow = true) ) @@ -574,7 +567,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and ( exists(Node mid, boolean preservesValue | - localFlowStepOrFlowThroughMethod(node, mid, preservesValue, config) and + localFlowStepOrFlowThroughCallable(node, mid, preservesValue, config) and nodeCand2(mid, toReturn, stored, config) and (stored = false or preservesValue = true) ) @@ -603,14 +596,14 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, false, stored, config) and toReturn = false and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, _, stored, config) and toReturn = true and (stored = false or allowsFieldFlow = true) @@ -663,10 +656,10 @@ private predicate localFlowEntry(Node node, Configuration config) { config.isSource(node) or jumpStep(_, node, _, config) or node instanceof ParameterNode or - node.asExpr() instanceof MethodAccess or + node instanceof OutNode or node instanceof PostUpdateNode or read(_, _, node) or - node.asExpr() instanceof CastExpr + node instanceof CastNode ) } @@ -677,14 +670,14 @@ private predicate localFlowEntry(Node node, Configuration config) { private predicate localFlowExit(Node node, Configuration config) { exists(Node next | nodeCand(next, config) | jumpStep(node, next, _, config) or - flowIntoCallable(node, next, config) or - flowOutOfCallable(node, next, config) or - flowThroughMethod(node, next, _, config) or + flowIntoCallableCand1(node, next, config) or + flowOutOfCallableCand1(node, next, config) or + flowThroughCallableCand1(node, next, _, config) or store(node, _, next) or read(node, _, next) ) or - node.asExpr() instanceof CastExpr + node instanceof CastNode or config.isSink(node) } @@ -706,7 +699,7 @@ private predicate localFlowStepPlus( exists(Node mid, boolean pv1, boolean pv2 | localFlowStepPlus(node1, mid, pv1, config) and localFlowStep(mid, node2, pv2, config) and - not mid.asExpr() instanceof CastExpr and + not mid instanceof CastNode and preservesValue = pv1.booleanAnd(pv2) and nodeCand(node2, unbind(config)) ) @@ -725,7 +718,7 @@ private predicate localFlowBigStep( } private newtype TAccessPathFront = - TFrontNil(Type t) or + TFrontNil(DataFlowType t) or TFrontHead(Content f) /** @@ -733,12 +726,12 @@ private newtype TAccessPathFront = */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) or exists(Content f | this = TFrontHead(f) | result = f.toString()) } - Type getType() { + DataFlowType getType() { this = TFrontNil(result) or exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) @@ -755,8 +748,8 @@ private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } private class CastingNode extends Node { CastingNode() { this instanceof ParameterNode or - this.asExpr() instanceof CastExpr or - this.asExpr() instanceof MethodAccess or + this instanceof CastNode or + this instanceof OutNode or this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode } } @@ -805,21 +798,21 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, _, apf, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, false, apf, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | flowCandFwd(mid, fromArg, apf0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and apf = apf0 or @@ -901,21 +894,21 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, false, apf, config) and toReturn = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, _, apf, config) and toReturn = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flowCand(mid, toReturn, apf0, config) and ( preservesValue = true and apf = apf0 @@ -954,7 +947,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) } private newtype TAccessPath = - TNil(Type t) or + TNil(DataFlowType t) or TCons(Content f, int len) { len in [1 .. 5] } /** @@ -975,7 +968,7 @@ private class AccessPath extends TAccessPath { this = TCons(_, result) } - Type getType() { + DataFlowType getType() { this = TNil(result) or exists(Content head | this = TCons(head, _) | result = head.getContainerType()) @@ -985,9 +978,11 @@ private class AccessPath extends TAccessPath { } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } - override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } } private class AccessPathCons extends AccessPath, TCons { @@ -1069,21 +1064,21 @@ private predicate flowFwd0( or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, _, apf, ap, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, false, apf, ap, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0, AccessPath ap0 | flowFwd(mid, fromArg, apf0, ap0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and ap = ap0 and apf = apf0 or @@ -1182,21 +1177,21 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, false, ap, config) and toReturn = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, _, ap, config) and toReturn = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPath ap0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flow(mid, toReturn, ap0, config) and ( preservesValue = true and ap = ap0 @@ -1274,8 +1269,14 @@ abstract class PathNode extends TPathNode { /** Gets a textual representation of this element. */ string toString() { result = getNode().toString() + ppAp() } + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + /** Gets the source location for this element. */ - Location getLocation() { result = getNode().getLocation() } + DataFlowLocation getLocation() { result = getNode().getLocation() } /** Gets the underlying `Node`. */ abstract Node getNode(); @@ -1284,20 +1285,31 @@ abstract class PathNode extends TPathNode { abstract Configuration getConfiguration(); /** Gets a successor. */ - abstract PathNode getSucc(); + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); private string ppAp() { this instanceof PathNodeSink and result = "" or - result = " [" + this.(PathNodeMid).getAp().toString() + "]" + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } } /** Holds if `n` can reach a sink. */ -private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getSucc()) } +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } /** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getSucc() = n2 and reach(n2) } +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) @@ -1337,7 +1349,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result.getConfiguration() = unbind(this.getConfiguration()) } - override PathNode getSucc() { + override PathNode getASuccessor() { // an intermediate step to another intermediate node result = getSuccMid() or @@ -1391,7 +1403,7 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } - override PathNode getSucc() { none() } + override PathNode getASuccessor() { none() } } /** @@ -1425,11 +1437,9 @@ private predicate flowStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or flowIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or - flowOutOfMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowOutOfCallable(mid, node, cc) and ap = mid.getAp() or - flowThroughMethod(mid, node.asExpr(), cc) and ap = TNil(getErasedRepr(node.getType())) - or - valueFlowThroughMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowThroughCallable(mid, node, cc) and ap = mid.getAp() } private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { @@ -1450,36 +1460,35 @@ private predicate contentStoreStep( } /** - * Holds if data may flow from `mid` to an exit of `m` in the context - * `innercc`, and the path did not flow through a parameter of `m`. + * Holds if data may flow from `mid` to a return at position `pos` in the + * context `innercc`, and the path did not flow through a parameter. */ -private predicate flowOutOfMethod0(PathNodeMid mid, Method m, CallContext innercc) { - exists(ReturnNode ret | - ret = mid.getNode() and - innercc = mid.getCallContext() and - m = returnNodeGetEnclosingCallable(ret) and - not innercc instanceof CallContextCall - ) +private predicate flowOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos.getAReturnNode() = mid.getNode() and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall } /** - * Holds if data may flow from `mid` to `ma`. The last step of this path - * is a return from a method and is recorded by `cc`, if needed. + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate flowOutOfMethod(PathNodeMid mid, MethodAccess ma, CallContext cc) { - exists(Method m, CallContext innercc | - flowOutOfMethod0(mid, m, innercc) and - resolveReturn(innercc, m, ma) +private predicate flowOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnPosition pos, DataFlowCallable c, DataFlowCall call, CallContext innercc | + flowOutOfCallable0(mid, pos, innercc) and + out = getAnOutputAtCall(call, pos) and + c = pos.getCallable() and + resolveReturn(innercc, c, call) | - if reducedViableImplInReturn(m, ma) then cc = TReturn(m, ma) else cc = TAnyCallContext() + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) } private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { exists( - PostUpdateNode n, ParameterNode p, Callable callable, CallContext innercc, int i, Call call, - ArgumentNode arg + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg | mid.getNode() = n and parameterValueFlowsToUpdate(p, n) and @@ -1500,7 +1509,9 @@ private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallCo * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. */ pragma[noinline] -private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, boolean emptyAp) { +private predicate flowIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { exists(ArgumentNode arg, AccessPath ap | arg = mid.getNode() and cc = mid.getCallContext() and @@ -1514,7 +1525,7 @@ private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, } pragma[noinline] -private predicate parameterCand(Callable callable, int i, Configuration config) { +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { exists(ParameterNode p | flow(p, config) and p.isParameterOf(callable, i) @@ -1523,7 +1534,8 @@ private predicate parameterCand(Callable callable, int i, Configuration config) pragma[nomagic] private predicate flowIntoCallable0( - PathNodeMid mid, Callable callable, int i, CallContext outercc, Call call, boolean emptyAp + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp ) { flowIntoArg(mid, i, outercc, call, emptyAp) and callable = resolveCall(call, outercc) and @@ -1536,9 +1548,9 @@ private predicate flowIntoCallable0( * respectively. */ private predicate flowIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, Call call + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call ) { - exists(int i, Callable callable, boolean emptyAp | + exists(int i, DataFlowCallable callable, boolean emptyAp | flowIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | @@ -1548,42 +1560,32 @@ private predicate flowIntoCallable( ) } -/** Holds if data may flow from `p` to a return statement in the callable. */ +/** Holds if data may flow from `p` to a return at position `pos`. */ pragma[nomagic] -private predicate paramFlowsThrough(ParameterNode p, CallContextCall cc, Configuration config) { - exists(PathNodeMid mid, ReturnNode ret | - mid.getNode() = ret and +private predicate paramFlowsThrough( + ParameterNode p, ReturnPosition pos, CallContextCall cc, Configuration config +) { + exists(PathNodeMid mid | + mid.getNode() = pos.getAReturnNode() and cc = mid.getCallContext() and - config = mid.getConfiguration() and - mid.getAp() instanceof AccessPathNil + config = mid.getConfiguration() | cc = TSomeCall(p, true) or - exists(int i | cc = TSpecificCall(_, i, true) | - p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) - ) + exists(int i | cc = TSpecificCall(_, i, true) | p.isParameterOf(pos.getCallable(), i)) ) } /** - * Holds if data may flow from `mid` to an argument of `methodcall`, - * through a called method `m`, and back out through a return statement in - * `m`. The context `cc` is restored to its value prior to entering `m`. + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate flowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p, CallContext innercc | - flowIntoCallable(mid, p, cc, innercc, methodcall) and - paramFlowsThrough(p, innercc, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p) and - mid.getAp() instanceof AccessPathNil - ) -} - -private predicate valueFlowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p | - flowIntoCallable(mid, p, cc, _, methodcall) and - parameterValueFlowsThrough(p) +private predicate flowThroughCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(DataFlowCall call, ParameterNode p, ReturnPosition pos, CallContext innercc | + flowIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, pos, innercc, unbind(mid.getConfiguration())) and + out = getAnOutputAtCall(call, pos) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 9d28e37cc8a..518bd18ee57 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -1,16 +1,15 @@ /** * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from `DataFlowUtil` - * and adds a global analysis, mainly exposed through the `Configuration` class. - * This file exists in several identical copies, allowing queries to use - * multiple `Configuration` classes that depend on each other without - * introducing mutual recursion among those configurations. + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. */ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public /** * A configuration of interprocedural data flow analysis. This defines @@ -95,7 +94,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowToExpr(Expr sink) { hasFlowTo(exprNode(sink)) } + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -114,7 +113,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi pragma[noinline] private predicate isAdditionalFlowStep( - Node node1, Node node2, Callable callable1, Callable callable2, Configuration config + Node node1, Node node2, DataFlowCallable callable1, DataFlowCallable callable2, + Configuration config ) { config.isAdditionalFlowStep(node1, node2) and callable1 = node1.getEnclosingCallable() and @@ -125,7 +125,7 @@ private predicate isAdditionalFlowStep( * Holds if the additional step from `node1` to `node2` does not jump between callables. */ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { - exists(Callable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) + exists(DataFlowCallable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) } /** @@ -148,11 +148,6 @@ private predicate localFlowStep(Node node1, Node node2, boolean preservesValue, additionalLocalFlowStep(node1, node2, config) and preservesValue = false } -pragma[noinline] -private Method returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - /** * Holds if field flow should be used for the given configuration. */ @@ -209,11 +204,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) ) or // flow out of a callable - exists(Method m, MethodAccess ma, ReturnNode ret | + exists(ReturnNode ret | nodeCandFwd1(ret, stored, config) and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node.asExpr() = ma + node = getAnOutputAtCall(_, ret.getPosition()) ) ) } @@ -287,10 +280,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) { ) or // flow out of a callable - exists(Method m, ExprNode ma | - nodeCand1(ma, stored, config) and - m = returnNodeGetEnclosingCallable(node) and - m = viableImpl(ma.getExpr()) + exists(Node out | + nodeCand1(out, stored, config) and + out = getAnOutputAtCall(_, node.(ReturnNode).getPosition()) ) ) } @@ -316,11 +308,13 @@ private predicate readCand1(Content f, Configuration config) { * from the configuration. */ pragma[nomagic] -private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Configuration config) { +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { nodeCand1(node, false, config) and p = node and t = getErasedRepr(node.getType()) and - not parameterValueFlowsThrough(p) + not parameterValueFlowsThrough(p, _) or nodeCand1(node, false, unbind(config)) and exists(Node mid | @@ -367,21 +361,21 @@ private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Con * additional step from the configuration. */ private predicate simpleArgumentFlowsThrough( - ArgumentNode arg, ExprNode call, RefType t, Configuration config + ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(ParameterNode param, ReturnNode ret | nodeCand1(arg, false, unbind(config)) and - nodeCand1(call, false, unbind(config)) and + nodeCand1(out, false, unbind(config)) and viableParamArg(param, arg) and simpleParameterFlow(param, ret, t, config) and - arg.argumentOf(call.getExpr(), _) + out = getAnOutputAtCall(arg.getCall(), ret.getPosition()) ) } /** - * Holds if data can flow from `node1` to `node2` by a step through a method. + * Holds if data can flow from `node1` to `node2` by a step through a callable. */ -private predicate flowThroughMethod( +private predicate flowThroughCallableCand1( Node node1, Node node2, boolean preservesValue, Configuration config ) { simpleArgumentFlowsThrough(node1, node2, _, config) and preservesValue = false @@ -391,13 +385,13 @@ private predicate flowThroughMethod( /** * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a method. + * through a callable. */ -private predicate localFlowStepOrFlowThroughMethod( +private predicate localFlowStepOrFlowThroughCallable( Node node1, Node node2, boolean preservesValue, Configuration config ) { localFlowStep(node1, node2, preservesValue, config) or - flowThroughMethod(node1, node2, preservesValue, config) + flowThroughCallableCand1(node1, node2, preservesValue, config) } /** @@ -405,7 +399,7 @@ private predicate localFlowStepOrFlowThroughMethod( * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ -private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { +private predicate flowOutOfCallableCand1(Node node1, Node node2, Configuration config) { nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) and ( @@ -415,13 +409,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config viableParamArg(p, node2.(PostUpdateNode).getPreUpdateNode()) ) or - // flow out of a method - exists(Method m, MethodAccess ma, ReturnNode ret | - ret = node1 and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node2.asExpr() = ma - ) + // flow out of a callable + node2 = getAnOutputAtCall(_, node1.(ReturnNode).getPosition()) ) } @@ -429,7 +418,7 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config * Holds if data can flow into a callable and that this step is part of a * path from a source to a sink. */ -private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { +private predicate flowIntoCallableCand1(Node node1, Node node2, Configuration config) { viableParamArg(node2, node1) and nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) @@ -441,7 +430,9 @@ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) * contexts. */ private int branch(Node n1, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n1, n, conf) or flowIntoCallableCand1(n1, n, conf) + ) } /** @@ -450,7 +441,9 @@ private int branch(Node n1, Configuration conf) { * contexts. */ private int join(Node n2, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n, n2, conf) or flowIntoCallableCand1(n, n2, conf) + ) } /** @@ -460,10 +453,10 @@ private int join(Node n2, Configuration conf) { * `allowsFieldFlow` flag indicates whether the branching is within the limit * specified by the configuration. */ -private predicate flowOutOfCallable( +private predicate flowOutOfCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowOutOfCallable(node1, node2, config) and + flowOutOfCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -478,10 +471,10 @@ private predicate flowOutOfCallable( * path from a source to a sink. The `allowsFieldFlow` flag indicates whether * the branching is within the limit specified by the configuration. */ -private predicate flowIntoCallable( +private predicate flowIntoCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowIntoCallable(node1, node2, config) and + flowIntoCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -505,7 +498,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ( exists(Node mid, boolean preservesValue | nodeCandFwd2(mid, fromArg, stored, config) and - localFlowStepOrFlowThroughMethod(mid, node, preservesValue, config) and + localFlowStepOrFlowThroughCallable(mid, node, preservesValue, config) and (stored = false or preservesValue = true) ) or @@ -534,14 +527,14 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, _, stored, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, false, stored, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (stored = false or allowsFieldFlow = true) ) @@ -574,7 +567,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and ( exists(Node mid, boolean preservesValue | - localFlowStepOrFlowThroughMethod(node, mid, preservesValue, config) and + localFlowStepOrFlowThroughCallable(node, mid, preservesValue, config) and nodeCand2(mid, toReturn, stored, config) and (stored = false or preservesValue = true) ) @@ -603,14 +596,14 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, false, stored, config) and toReturn = false and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, _, stored, config) and toReturn = true and (stored = false or allowsFieldFlow = true) @@ -663,10 +656,10 @@ private predicate localFlowEntry(Node node, Configuration config) { config.isSource(node) or jumpStep(_, node, _, config) or node instanceof ParameterNode or - node.asExpr() instanceof MethodAccess or + node instanceof OutNode or node instanceof PostUpdateNode or read(_, _, node) or - node.asExpr() instanceof CastExpr + node instanceof CastNode ) } @@ -677,14 +670,14 @@ private predicate localFlowEntry(Node node, Configuration config) { private predicate localFlowExit(Node node, Configuration config) { exists(Node next | nodeCand(next, config) | jumpStep(node, next, _, config) or - flowIntoCallable(node, next, config) or - flowOutOfCallable(node, next, config) or - flowThroughMethod(node, next, _, config) or + flowIntoCallableCand1(node, next, config) or + flowOutOfCallableCand1(node, next, config) or + flowThroughCallableCand1(node, next, _, config) or store(node, _, next) or read(node, _, next) ) or - node.asExpr() instanceof CastExpr + node instanceof CastNode or config.isSink(node) } @@ -706,7 +699,7 @@ private predicate localFlowStepPlus( exists(Node mid, boolean pv1, boolean pv2 | localFlowStepPlus(node1, mid, pv1, config) and localFlowStep(mid, node2, pv2, config) and - not mid.asExpr() instanceof CastExpr and + not mid instanceof CastNode and preservesValue = pv1.booleanAnd(pv2) and nodeCand(node2, unbind(config)) ) @@ -725,7 +718,7 @@ private predicate localFlowBigStep( } private newtype TAccessPathFront = - TFrontNil(Type t) or + TFrontNil(DataFlowType t) or TFrontHead(Content f) /** @@ -733,12 +726,12 @@ private newtype TAccessPathFront = */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) or exists(Content f | this = TFrontHead(f) | result = f.toString()) } - Type getType() { + DataFlowType getType() { this = TFrontNil(result) or exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) @@ -755,8 +748,8 @@ private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } private class CastingNode extends Node { CastingNode() { this instanceof ParameterNode or - this.asExpr() instanceof CastExpr or - this.asExpr() instanceof MethodAccess or + this instanceof CastNode or + this instanceof OutNode or this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode } } @@ -805,21 +798,21 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, _, apf, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, false, apf, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | flowCandFwd(mid, fromArg, apf0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and apf = apf0 or @@ -901,21 +894,21 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, false, apf, config) and toReturn = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, _, apf, config) and toReturn = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flowCand(mid, toReturn, apf0, config) and ( preservesValue = true and apf = apf0 @@ -954,7 +947,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) } private newtype TAccessPath = - TNil(Type t) or + TNil(DataFlowType t) or TCons(Content f, int len) { len in [1 .. 5] } /** @@ -975,7 +968,7 @@ private class AccessPath extends TAccessPath { this = TCons(_, result) } - Type getType() { + DataFlowType getType() { this = TNil(result) or exists(Content head | this = TCons(head, _) | result = head.getContainerType()) @@ -985,9 +978,11 @@ private class AccessPath extends TAccessPath { } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } - override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } } private class AccessPathCons extends AccessPath, TCons { @@ -1069,21 +1064,21 @@ private predicate flowFwd0( or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, _, apf, ap, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, false, apf, ap, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0, AccessPath ap0 | flowFwd(mid, fromArg, apf0, ap0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and ap = ap0 and apf = apf0 or @@ -1182,21 +1177,21 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, false, ap, config) and toReturn = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, _, ap, config) and toReturn = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPath ap0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flow(mid, toReturn, ap0, config) and ( preservesValue = true and ap = ap0 @@ -1274,8 +1269,14 @@ abstract class PathNode extends TPathNode { /** Gets a textual representation of this element. */ string toString() { result = getNode().toString() + ppAp() } + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + /** Gets the source location for this element. */ - Location getLocation() { result = getNode().getLocation() } + DataFlowLocation getLocation() { result = getNode().getLocation() } /** Gets the underlying `Node`. */ abstract Node getNode(); @@ -1284,20 +1285,31 @@ abstract class PathNode extends TPathNode { abstract Configuration getConfiguration(); /** Gets a successor. */ - abstract PathNode getSucc(); + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); private string ppAp() { this instanceof PathNodeSink and result = "" or - result = " [" + this.(PathNodeMid).getAp().toString() + "]" + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } } /** Holds if `n` can reach a sink. */ -private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getSucc()) } +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } /** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getSucc() = n2 and reach(n2) } +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) @@ -1337,7 +1349,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result.getConfiguration() = unbind(this.getConfiguration()) } - override PathNode getSucc() { + override PathNode getASuccessor() { // an intermediate step to another intermediate node result = getSuccMid() or @@ -1391,7 +1403,7 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } - override PathNode getSucc() { none() } + override PathNode getASuccessor() { none() } } /** @@ -1425,11 +1437,9 @@ private predicate flowStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or flowIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or - flowOutOfMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowOutOfCallable(mid, node, cc) and ap = mid.getAp() or - flowThroughMethod(mid, node.asExpr(), cc) and ap = TNil(getErasedRepr(node.getType())) - or - valueFlowThroughMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowThroughCallable(mid, node, cc) and ap = mid.getAp() } private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { @@ -1450,36 +1460,35 @@ private predicate contentStoreStep( } /** - * Holds if data may flow from `mid` to an exit of `m` in the context - * `innercc`, and the path did not flow through a parameter of `m`. + * Holds if data may flow from `mid` to a return at position `pos` in the + * context `innercc`, and the path did not flow through a parameter. */ -private predicate flowOutOfMethod0(PathNodeMid mid, Method m, CallContext innercc) { - exists(ReturnNode ret | - ret = mid.getNode() and - innercc = mid.getCallContext() and - m = returnNodeGetEnclosingCallable(ret) and - not innercc instanceof CallContextCall - ) +private predicate flowOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos.getAReturnNode() = mid.getNode() and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall } /** - * Holds if data may flow from `mid` to `ma`. The last step of this path - * is a return from a method and is recorded by `cc`, if needed. + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate flowOutOfMethod(PathNodeMid mid, MethodAccess ma, CallContext cc) { - exists(Method m, CallContext innercc | - flowOutOfMethod0(mid, m, innercc) and - resolveReturn(innercc, m, ma) +private predicate flowOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnPosition pos, DataFlowCallable c, DataFlowCall call, CallContext innercc | + flowOutOfCallable0(mid, pos, innercc) and + out = getAnOutputAtCall(call, pos) and + c = pos.getCallable() and + resolveReturn(innercc, c, call) | - if reducedViableImplInReturn(m, ma) then cc = TReturn(m, ma) else cc = TAnyCallContext() + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) } private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { exists( - PostUpdateNode n, ParameterNode p, Callable callable, CallContext innercc, int i, Call call, - ArgumentNode arg + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg | mid.getNode() = n and parameterValueFlowsToUpdate(p, n) and @@ -1500,7 +1509,9 @@ private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallCo * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. */ pragma[noinline] -private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, boolean emptyAp) { +private predicate flowIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { exists(ArgumentNode arg, AccessPath ap | arg = mid.getNode() and cc = mid.getCallContext() and @@ -1514,7 +1525,7 @@ private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, } pragma[noinline] -private predicate parameterCand(Callable callable, int i, Configuration config) { +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { exists(ParameterNode p | flow(p, config) and p.isParameterOf(callable, i) @@ -1523,7 +1534,8 @@ private predicate parameterCand(Callable callable, int i, Configuration config) pragma[nomagic] private predicate flowIntoCallable0( - PathNodeMid mid, Callable callable, int i, CallContext outercc, Call call, boolean emptyAp + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp ) { flowIntoArg(mid, i, outercc, call, emptyAp) and callable = resolveCall(call, outercc) and @@ -1536,9 +1548,9 @@ private predicate flowIntoCallable0( * respectively. */ private predicate flowIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, Call call + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call ) { - exists(int i, Callable callable, boolean emptyAp | + exists(int i, DataFlowCallable callable, boolean emptyAp | flowIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | @@ -1548,42 +1560,32 @@ private predicate flowIntoCallable( ) } -/** Holds if data may flow from `p` to a return statement in the callable. */ +/** Holds if data may flow from `p` to a return at position `pos`. */ pragma[nomagic] -private predicate paramFlowsThrough(ParameterNode p, CallContextCall cc, Configuration config) { - exists(PathNodeMid mid, ReturnNode ret | - mid.getNode() = ret and +private predicate paramFlowsThrough( + ParameterNode p, ReturnPosition pos, CallContextCall cc, Configuration config +) { + exists(PathNodeMid mid | + mid.getNode() = pos.getAReturnNode() and cc = mid.getCallContext() and - config = mid.getConfiguration() and - mid.getAp() instanceof AccessPathNil + config = mid.getConfiguration() | cc = TSomeCall(p, true) or - exists(int i | cc = TSpecificCall(_, i, true) | - p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) - ) + exists(int i | cc = TSpecificCall(_, i, true) | p.isParameterOf(pos.getCallable(), i)) ) } /** - * Holds if data may flow from `mid` to an argument of `methodcall`, - * through a called method `m`, and back out through a return statement in - * `m`. The context `cc` is restored to its value prior to entering `m`. + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate flowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p, CallContext innercc | - flowIntoCallable(mid, p, cc, innercc, methodcall) and - paramFlowsThrough(p, innercc, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p) and - mid.getAp() instanceof AccessPathNil - ) -} - -private predicate valueFlowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p | - flowIntoCallable(mid, p, cc, _, methodcall) and - parameterValueFlowsThrough(p) +private predicate flowThroughCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(DataFlowCall call, ParameterNode p, ReturnPosition pos, CallContext innercc | + flowIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, pos, innercc, unbind(mid.getConfiguration())) and + out = getAnOutputAtCall(call, pos) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 9d28e37cc8a..518bd18ee57 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -1,16 +1,15 @@ /** * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from `DataFlowUtil` - * and adds a global analysis, mainly exposed through the `Configuration` class. - * This file exists in several identical copies, allowing queries to use - * multiple `Configuration` classes that depend on each other without - * introducing mutual recursion among those configurations. + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. */ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public /** * A configuration of interprocedural data flow analysis. This defines @@ -95,7 +94,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowToExpr(Expr sink) { hasFlowTo(exprNode(sink)) } + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -114,7 +113,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi pragma[noinline] private predicate isAdditionalFlowStep( - Node node1, Node node2, Callable callable1, Callable callable2, Configuration config + Node node1, Node node2, DataFlowCallable callable1, DataFlowCallable callable2, + Configuration config ) { config.isAdditionalFlowStep(node1, node2) and callable1 = node1.getEnclosingCallable() and @@ -125,7 +125,7 @@ private predicate isAdditionalFlowStep( * Holds if the additional step from `node1` to `node2` does not jump between callables. */ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { - exists(Callable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) + exists(DataFlowCallable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) } /** @@ -148,11 +148,6 @@ private predicate localFlowStep(Node node1, Node node2, boolean preservesValue, additionalLocalFlowStep(node1, node2, config) and preservesValue = false } -pragma[noinline] -private Method returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - /** * Holds if field flow should be used for the given configuration. */ @@ -209,11 +204,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) ) or // flow out of a callable - exists(Method m, MethodAccess ma, ReturnNode ret | + exists(ReturnNode ret | nodeCandFwd1(ret, stored, config) and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node.asExpr() = ma + node = getAnOutputAtCall(_, ret.getPosition()) ) ) } @@ -287,10 +280,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) { ) or // flow out of a callable - exists(Method m, ExprNode ma | - nodeCand1(ma, stored, config) and - m = returnNodeGetEnclosingCallable(node) and - m = viableImpl(ma.getExpr()) + exists(Node out | + nodeCand1(out, stored, config) and + out = getAnOutputAtCall(_, node.(ReturnNode).getPosition()) ) ) } @@ -316,11 +308,13 @@ private predicate readCand1(Content f, Configuration config) { * from the configuration. */ pragma[nomagic] -private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Configuration config) { +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { nodeCand1(node, false, config) and p = node and t = getErasedRepr(node.getType()) and - not parameterValueFlowsThrough(p) + not parameterValueFlowsThrough(p, _) or nodeCand1(node, false, unbind(config)) and exists(Node mid | @@ -367,21 +361,21 @@ private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Con * additional step from the configuration. */ private predicate simpleArgumentFlowsThrough( - ArgumentNode arg, ExprNode call, RefType t, Configuration config + ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(ParameterNode param, ReturnNode ret | nodeCand1(arg, false, unbind(config)) and - nodeCand1(call, false, unbind(config)) and + nodeCand1(out, false, unbind(config)) and viableParamArg(param, arg) and simpleParameterFlow(param, ret, t, config) and - arg.argumentOf(call.getExpr(), _) + out = getAnOutputAtCall(arg.getCall(), ret.getPosition()) ) } /** - * Holds if data can flow from `node1` to `node2` by a step through a method. + * Holds if data can flow from `node1` to `node2` by a step through a callable. */ -private predicate flowThroughMethod( +private predicate flowThroughCallableCand1( Node node1, Node node2, boolean preservesValue, Configuration config ) { simpleArgumentFlowsThrough(node1, node2, _, config) and preservesValue = false @@ -391,13 +385,13 @@ private predicate flowThroughMethod( /** * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a method. + * through a callable. */ -private predicate localFlowStepOrFlowThroughMethod( +private predicate localFlowStepOrFlowThroughCallable( Node node1, Node node2, boolean preservesValue, Configuration config ) { localFlowStep(node1, node2, preservesValue, config) or - flowThroughMethod(node1, node2, preservesValue, config) + flowThroughCallableCand1(node1, node2, preservesValue, config) } /** @@ -405,7 +399,7 @@ private predicate localFlowStepOrFlowThroughMethod( * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ -private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { +private predicate flowOutOfCallableCand1(Node node1, Node node2, Configuration config) { nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) and ( @@ -415,13 +409,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config viableParamArg(p, node2.(PostUpdateNode).getPreUpdateNode()) ) or - // flow out of a method - exists(Method m, MethodAccess ma, ReturnNode ret | - ret = node1 and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node2.asExpr() = ma - ) + // flow out of a callable + node2 = getAnOutputAtCall(_, node1.(ReturnNode).getPosition()) ) } @@ -429,7 +418,7 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config * Holds if data can flow into a callable and that this step is part of a * path from a source to a sink. */ -private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { +private predicate flowIntoCallableCand1(Node node1, Node node2, Configuration config) { viableParamArg(node2, node1) and nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) @@ -441,7 +430,9 @@ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) * contexts. */ private int branch(Node n1, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n1, n, conf) or flowIntoCallableCand1(n1, n, conf) + ) } /** @@ -450,7 +441,9 @@ private int branch(Node n1, Configuration conf) { * contexts. */ private int join(Node n2, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n, n2, conf) or flowIntoCallableCand1(n, n2, conf) + ) } /** @@ -460,10 +453,10 @@ private int join(Node n2, Configuration conf) { * `allowsFieldFlow` flag indicates whether the branching is within the limit * specified by the configuration. */ -private predicate flowOutOfCallable( +private predicate flowOutOfCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowOutOfCallable(node1, node2, config) and + flowOutOfCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -478,10 +471,10 @@ private predicate flowOutOfCallable( * path from a source to a sink. The `allowsFieldFlow` flag indicates whether * the branching is within the limit specified by the configuration. */ -private predicate flowIntoCallable( +private predicate flowIntoCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowIntoCallable(node1, node2, config) and + flowIntoCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -505,7 +498,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ( exists(Node mid, boolean preservesValue | nodeCandFwd2(mid, fromArg, stored, config) and - localFlowStepOrFlowThroughMethod(mid, node, preservesValue, config) and + localFlowStepOrFlowThroughCallable(mid, node, preservesValue, config) and (stored = false or preservesValue = true) ) or @@ -534,14 +527,14 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, _, stored, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, false, stored, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (stored = false or allowsFieldFlow = true) ) @@ -574,7 +567,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and ( exists(Node mid, boolean preservesValue | - localFlowStepOrFlowThroughMethod(node, mid, preservesValue, config) and + localFlowStepOrFlowThroughCallable(node, mid, preservesValue, config) and nodeCand2(mid, toReturn, stored, config) and (stored = false or preservesValue = true) ) @@ -603,14 +596,14 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, false, stored, config) and toReturn = false and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, _, stored, config) and toReturn = true and (stored = false or allowsFieldFlow = true) @@ -663,10 +656,10 @@ private predicate localFlowEntry(Node node, Configuration config) { config.isSource(node) or jumpStep(_, node, _, config) or node instanceof ParameterNode or - node.asExpr() instanceof MethodAccess or + node instanceof OutNode or node instanceof PostUpdateNode or read(_, _, node) or - node.asExpr() instanceof CastExpr + node instanceof CastNode ) } @@ -677,14 +670,14 @@ private predicate localFlowEntry(Node node, Configuration config) { private predicate localFlowExit(Node node, Configuration config) { exists(Node next | nodeCand(next, config) | jumpStep(node, next, _, config) or - flowIntoCallable(node, next, config) or - flowOutOfCallable(node, next, config) or - flowThroughMethod(node, next, _, config) or + flowIntoCallableCand1(node, next, config) or + flowOutOfCallableCand1(node, next, config) or + flowThroughCallableCand1(node, next, _, config) or store(node, _, next) or read(node, _, next) ) or - node.asExpr() instanceof CastExpr + node instanceof CastNode or config.isSink(node) } @@ -706,7 +699,7 @@ private predicate localFlowStepPlus( exists(Node mid, boolean pv1, boolean pv2 | localFlowStepPlus(node1, mid, pv1, config) and localFlowStep(mid, node2, pv2, config) and - not mid.asExpr() instanceof CastExpr and + not mid instanceof CastNode and preservesValue = pv1.booleanAnd(pv2) and nodeCand(node2, unbind(config)) ) @@ -725,7 +718,7 @@ private predicate localFlowBigStep( } private newtype TAccessPathFront = - TFrontNil(Type t) or + TFrontNil(DataFlowType t) or TFrontHead(Content f) /** @@ -733,12 +726,12 @@ private newtype TAccessPathFront = */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) or exists(Content f | this = TFrontHead(f) | result = f.toString()) } - Type getType() { + DataFlowType getType() { this = TFrontNil(result) or exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) @@ -755,8 +748,8 @@ private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } private class CastingNode extends Node { CastingNode() { this instanceof ParameterNode or - this.asExpr() instanceof CastExpr or - this.asExpr() instanceof MethodAccess or + this instanceof CastNode or + this instanceof OutNode or this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode } } @@ -805,21 +798,21 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, _, apf, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, false, apf, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | flowCandFwd(mid, fromArg, apf0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and apf = apf0 or @@ -901,21 +894,21 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, false, apf, config) and toReturn = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, _, apf, config) and toReturn = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flowCand(mid, toReturn, apf0, config) and ( preservesValue = true and apf = apf0 @@ -954,7 +947,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) } private newtype TAccessPath = - TNil(Type t) or + TNil(DataFlowType t) or TCons(Content f, int len) { len in [1 .. 5] } /** @@ -975,7 +968,7 @@ private class AccessPath extends TAccessPath { this = TCons(_, result) } - Type getType() { + DataFlowType getType() { this = TNil(result) or exists(Content head | this = TCons(head, _) | result = head.getContainerType()) @@ -985,9 +978,11 @@ private class AccessPath extends TAccessPath { } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } - override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } } private class AccessPathCons extends AccessPath, TCons { @@ -1069,21 +1064,21 @@ private predicate flowFwd0( or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, _, apf, ap, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, false, apf, ap, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0, AccessPath ap0 | flowFwd(mid, fromArg, apf0, ap0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and ap = ap0 and apf = apf0 or @@ -1182,21 +1177,21 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, false, ap, config) and toReturn = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, _, ap, config) and toReturn = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPath ap0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flow(mid, toReturn, ap0, config) and ( preservesValue = true and ap = ap0 @@ -1274,8 +1269,14 @@ abstract class PathNode extends TPathNode { /** Gets a textual representation of this element. */ string toString() { result = getNode().toString() + ppAp() } + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + /** Gets the source location for this element. */ - Location getLocation() { result = getNode().getLocation() } + DataFlowLocation getLocation() { result = getNode().getLocation() } /** Gets the underlying `Node`. */ abstract Node getNode(); @@ -1284,20 +1285,31 @@ abstract class PathNode extends TPathNode { abstract Configuration getConfiguration(); /** Gets a successor. */ - abstract PathNode getSucc(); + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); private string ppAp() { this instanceof PathNodeSink and result = "" or - result = " [" + this.(PathNodeMid).getAp().toString() + "]" + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } } /** Holds if `n` can reach a sink. */ -private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getSucc()) } +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } /** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getSucc() = n2 and reach(n2) } +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) @@ -1337,7 +1349,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result.getConfiguration() = unbind(this.getConfiguration()) } - override PathNode getSucc() { + override PathNode getASuccessor() { // an intermediate step to another intermediate node result = getSuccMid() or @@ -1391,7 +1403,7 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } - override PathNode getSucc() { none() } + override PathNode getASuccessor() { none() } } /** @@ -1425,11 +1437,9 @@ private predicate flowStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or flowIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or - flowOutOfMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowOutOfCallable(mid, node, cc) and ap = mid.getAp() or - flowThroughMethod(mid, node.asExpr(), cc) and ap = TNil(getErasedRepr(node.getType())) - or - valueFlowThroughMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowThroughCallable(mid, node, cc) and ap = mid.getAp() } private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { @@ -1450,36 +1460,35 @@ private predicate contentStoreStep( } /** - * Holds if data may flow from `mid` to an exit of `m` in the context - * `innercc`, and the path did not flow through a parameter of `m`. + * Holds if data may flow from `mid` to a return at position `pos` in the + * context `innercc`, and the path did not flow through a parameter. */ -private predicate flowOutOfMethod0(PathNodeMid mid, Method m, CallContext innercc) { - exists(ReturnNode ret | - ret = mid.getNode() and - innercc = mid.getCallContext() and - m = returnNodeGetEnclosingCallable(ret) and - not innercc instanceof CallContextCall - ) +private predicate flowOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos.getAReturnNode() = mid.getNode() and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall } /** - * Holds if data may flow from `mid` to `ma`. The last step of this path - * is a return from a method and is recorded by `cc`, if needed. + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate flowOutOfMethod(PathNodeMid mid, MethodAccess ma, CallContext cc) { - exists(Method m, CallContext innercc | - flowOutOfMethod0(mid, m, innercc) and - resolveReturn(innercc, m, ma) +private predicate flowOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnPosition pos, DataFlowCallable c, DataFlowCall call, CallContext innercc | + flowOutOfCallable0(mid, pos, innercc) and + out = getAnOutputAtCall(call, pos) and + c = pos.getCallable() and + resolveReturn(innercc, c, call) | - if reducedViableImplInReturn(m, ma) then cc = TReturn(m, ma) else cc = TAnyCallContext() + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) } private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { exists( - PostUpdateNode n, ParameterNode p, Callable callable, CallContext innercc, int i, Call call, - ArgumentNode arg + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg | mid.getNode() = n and parameterValueFlowsToUpdate(p, n) and @@ -1500,7 +1509,9 @@ private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallCo * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. */ pragma[noinline] -private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, boolean emptyAp) { +private predicate flowIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { exists(ArgumentNode arg, AccessPath ap | arg = mid.getNode() and cc = mid.getCallContext() and @@ -1514,7 +1525,7 @@ private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, } pragma[noinline] -private predicate parameterCand(Callable callable, int i, Configuration config) { +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { exists(ParameterNode p | flow(p, config) and p.isParameterOf(callable, i) @@ -1523,7 +1534,8 @@ private predicate parameterCand(Callable callable, int i, Configuration config) pragma[nomagic] private predicate flowIntoCallable0( - PathNodeMid mid, Callable callable, int i, CallContext outercc, Call call, boolean emptyAp + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp ) { flowIntoArg(mid, i, outercc, call, emptyAp) and callable = resolveCall(call, outercc) and @@ -1536,9 +1548,9 @@ private predicate flowIntoCallable0( * respectively. */ private predicate flowIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, Call call + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call ) { - exists(int i, Callable callable, boolean emptyAp | + exists(int i, DataFlowCallable callable, boolean emptyAp | flowIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | @@ -1548,42 +1560,32 @@ private predicate flowIntoCallable( ) } -/** Holds if data may flow from `p` to a return statement in the callable. */ +/** Holds if data may flow from `p` to a return at position `pos`. */ pragma[nomagic] -private predicate paramFlowsThrough(ParameterNode p, CallContextCall cc, Configuration config) { - exists(PathNodeMid mid, ReturnNode ret | - mid.getNode() = ret and +private predicate paramFlowsThrough( + ParameterNode p, ReturnPosition pos, CallContextCall cc, Configuration config +) { + exists(PathNodeMid mid | + mid.getNode() = pos.getAReturnNode() and cc = mid.getCallContext() and - config = mid.getConfiguration() and - mid.getAp() instanceof AccessPathNil + config = mid.getConfiguration() | cc = TSomeCall(p, true) or - exists(int i | cc = TSpecificCall(_, i, true) | - p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) - ) + exists(int i | cc = TSpecificCall(_, i, true) | p.isParameterOf(pos.getCallable(), i)) ) } /** - * Holds if data may flow from `mid` to an argument of `methodcall`, - * through a called method `m`, and back out through a return statement in - * `m`. The context `cc` is restored to its value prior to entering `m`. + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate flowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p, CallContext innercc | - flowIntoCallable(mid, p, cc, innercc, methodcall) and - paramFlowsThrough(p, innercc, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p) and - mid.getAp() instanceof AccessPathNil - ) -} - -private predicate valueFlowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p | - flowIntoCallable(mid, p, cc, _, methodcall) and - parameterValueFlowsThrough(p) +private predicate flowThroughCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(DataFlowCall call, ParameterNode p, ReturnPosition pos, CallContext innercc | + flowIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, pos, innercc, unbind(mid.getConfiguration())) and + out = getAnOutputAtCall(call, pos) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 9d28e37cc8a..518bd18ee57 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -1,16 +1,15 @@ /** * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from `DataFlowUtil` - * and adds a global analysis, mainly exposed through the `Configuration` class. - * This file exists in several identical copies, allowing queries to use - * multiple `Configuration` classes that depend on each other without - * introducing mutual recursion among those configurations. + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. */ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public /** * A configuration of interprocedural data flow analysis. This defines @@ -95,7 +94,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowToExpr(Expr sink) { hasFlowTo(exprNode(sink)) } + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -114,7 +113,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi pragma[noinline] private predicate isAdditionalFlowStep( - Node node1, Node node2, Callable callable1, Callable callable2, Configuration config + Node node1, Node node2, DataFlowCallable callable1, DataFlowCallable callable2, + Configuration config ) { config.isAdditionalFlowStep(node1, node2) and callable1 = node1.getEnclosingCallable() and @@ -125,7 +125,7 @@ private predicate isAdditionalFlowStep( * Holds if the additional step from `node1` to `node2` does not jump between callables. */ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { - exists(Callable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) + exists(DataFlowCallable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) } /** @@ -148,11 +148,6 @@ private predicate localFlowStep(Node node1, Node node2, boolean preservesValue, additionalLocalFlowStep(node1, node2, config) and preservesValue = false } -pragma[noinline] -private Method returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - /** * Holds if field flow should be used for the given configuration. */ @@ -209,11 +204,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) ) or // flow out of a callable - exists(Method m, MethodAccess ma, ReturnNode ret | + exists(ReturnNode ret | nodeCandFwd1(ret, stored, config) and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node.asExpr() = ma + node = getAnOutputAtCall(_, ret.getPosition()) ) ) } @@ -287,10 +280,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) { ) or // flow out of a callable - exists(Method m, ExprNode ma | - nodeCand1(ma, stored, config) and - m = returnNodeGetEnclosingCallable(node) and - m = viableImpl(ma.getExpr()) + exists(Node out | + nodeCand1(out, stored, config) and + out = getAnOutputAtCall(_, node.(ReturnNode).getPosition()) ) ) } @@ -316,11 +308,13 @@ private predicate readCand1(Content f, Configuration config) { * from the configuration. */ pragma[nomagic] -private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Configuration config) { +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { nodeCand1(node, false, config) and p = node and t = getErasedRepr(node.getType()) and - not parameterValueFlowsThrough(p) + not parameterValueFlowsThrough(p, _) or nodeCand1(node, false, unbind(config)) and exists(Node mid | @@ -367,21 +361,21 @@ private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Con * additional step from the configuration. */ private predicate simpleArgumentFlowsThrough( - ArgumentNode arg, ExprNode call, RefType t, Configuration config + ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(ParameterNode param, ReturnNode ret | nodeCand1(arg, false, unbind(config)) and - nodeCand1(call, false, unbind(config)) and + nodeCand1(out, false, unbind(config)) and viableParamArg(param, arg) and simpleParameterFlow(param, ret, t, config) and - arg.argumentOf(call.getExpr(), _) + out = getAnOutputAtCall(arg.getCall(), ret.getPosition()) ) } /** - * Holds if data can flow from `node1` to `node2` by a step through a method. + * Holds if data can flow from `node1` to `node2` by a step through a callable. */ -private predicate flowThroughMethod( +private predicate flowThroughCallableCand1( Node node1, Node node2, boolean preservesValue, Configuration config ) { simpleArgumentFlowsThrough(node1, node2, _, config) and preservesValue = false @@ -391,13 +385,13 @@ private predicate flowThroughMethod( /** * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a method. + * through a callable. */ -private predicate localFlowStepOrFlowThroughMethod( +private predicate localFlowStepOrFlowThroughCallable( Node node1, Node node2, boolean preservesValue, Configuration config ) { localFlowStep(node1, node2, preservesValue, config) or - flowThroughMethod(node1, node2, preservesValue, config) + flowThroughCallableCand1(node1, node2, preservesValue, config) } /** @@ -405,7 +399,7 @@ private predicate localFlowStepOrFlowThroughMethod( * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ -private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { +private predicate flowOutOfCallableCand1(Node node1, Node node2, Configuration config) { nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) and ( @@ -415,13 +409,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config viableParamArg(p, node2.(PostUpdateNode).getPreUpdateNode()) ) or - // flow out of a method - exists(Method m, MethodAccess ma, ReturnNode ret | - ret = node1 and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node2.asExpr() = ma - ) + // flow out of a callable + node2 = getAnOutputAtCall(_, node1.(ReturnNode).getPosition()) ) } @@ -429,7 +418,7 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config * Holds if data can flow into a callable and that this step is part of a * path from a source to a sink. */ -private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { +private predicate flowIntoCallableCand1(Node node1, Node node2, Configuration config) { viableParamArg(node2, node1) and nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) @@ -441,7 +430,9 @@ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) * contexts. */ private int branch(Node n1, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n1, n, conf) or flowIntoCallableCand1(n1, n, conf) + ) } /** @@ -450,7 +441,9 @@ private int branch(Node n1, Configuration conf) { * contexts. */ private int join(Node n2, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n, n2, conf) or flowIntoCallableCand1(n, n2, conf) + ) } /** @@ -460,10 +453,10 @@ private int join(Node n2, Configuration conf) { * `allowsFieldFlow` flag indicates whether the branching is within the limit * specified by the configuration. */ -private predicate flowOutOfCallable( +private predicate flowOutOfCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowOutOfCallable(node1, node2, config) and + flowOutOfCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -478,10 +471,10 @@ private predicate flowOutOfCallable( * path from a source to a sink. The `allowsFieldFlow` flag indicates whether * the branching is within the limit specified by the configuration. */ -private predicate flowIntoCallable( +private predicate flowIntoCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowIntoCallable(node1, node2, config) and + flowIntoCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -505,7 +498,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ( exists(Node mid, boolean preservesValue | nodeCandFwd2(mid, fromArg, stored, config) and - localFlowStepOrFlowThroughMethod(mid, node, preservesValue, config) and + localFlowStepOrFlowThroughCallable(mid, node, preservesValue, config) and (stored = false or preservesValue = true) ) or @@ -534,14 +527,14 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, _, stored, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, false, stored, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (stored = false or allowsFieldFlow = true) ) @@ -574,7 +567,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and ( exists(Node mid, boolean preservesValue | - localFlowStepOrFlowThroughMethod(node, mid, preservesValue, config) and + localFlowStepOrFlowThroughCallable(node, mid, preservesValue, config) and nodeCand2(mid, toReturn, stored, config) and (stored = false or preservesValue = true) ) @@ -603,14 +596,14 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, false, stored, config) and toReturn = false and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, _, stored, config) and toReturn = true and (stored = false or allowsFieldFlow = true) @@ -663,10 +656,10 @@ private predicate localFlowEntry(Node node, Configuration config) { config.isSource(node) or jumpStep(_, node, _, config) or node instanceof ParameterNode or - node.asExpr() instanceof MethodAccess or + node instanceof OutNode or node instanceof PostUpdateNode or read(_, _, node) or - node.asExpr() instanceof CastExpr + node instanceof CastNode ) } @@ -677,14 +670,14 @@ private predicate localFlowEntry(Node node, Configuration config) { private predicate localFlowExit(Node node, Configuration config) { exists(Node next | nodeCand(next, config) | jumpStep(node, next, _, config) or - flowIntoCallable(node, next, config) or - flowOutOfCallable(node, next, config) or - flowThroughMethod(node, next, _, config) or + flowIntoCallableCand1(node, next, config) or + flowOutOfCallableCand1(node, next, config) or + flowThroughCallableCand1(node, next, _, config) or store(node, _, next) or read(node, _, next) ) or - node.asExpr() instanceof CastExpr + node instanceof CastNode or config.isSink(node) } @@ -706,7 +699,7 @@ private predicate localFlowStepPlus( exists(Node mid, boolean pv1, boolean pv2 | localFlowStepPlus(node1, mid, pv1, config) and localFlowStep(mid, node2, pv2, config) and - not mid.asExpr() instanceof CastExpr and + not mid instanceof CastNode and preservesValue = pv1.booleanAnd(pv2) and nodeCand(node2, unbind(config)) ) @@ -725,7 +718,7 @@ private predicate localFlowBigStep( } private newtype TAccessPathFront = - TFrontNil(Type t) or + TFrontNil(DataFlowType t) or TFrontHead(Content f) /** @@ -733,12 +726,12 @@ private newtype TAccessPathFront = */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) or exists(Content f | this = TFrontHead(f) | result = f.toString()) } - Type getType() { + DataFlowType getType() { this = TFrontNil(result) or exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) @@ -755,8 +748,8 @@ private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } private class CastingNode extends Node { CastingNode() { this instanceof ParameterNode or - this.asExpr() instanceof CastExpr or - this.asExpr() instanceof MethodAccess or + this instanceof CastNode or + this instanceof OutNode or this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode } } @@ -805,21 +798,21 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, _, apf, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, false, apf, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | flowCandFwd(mid, fromArg, apf0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and apf = apf0 or @@ -901,21 +894,21 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, false, apf, config) and toReturn = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, _, apf, config) and toReturn = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flowCand(mid, toReturn, apf0, config) and ( preservesValue = true and apf = apf0 @@ -954,7 +947,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) } private newtype TAccessPath = - TNil(Type t) or + TNil(DataFlowType t) or TCons(Content f, int len) { len in [1 .. 5] } /** @@ -975,7 +968,7 @@ private class AccessPath extends TAccessPath { this = TCons(_, result) } - Type getType() { + DataFlowType getType() { this = TNil(result) or exists(Content head | this = TCons(head, _) | result = head.getContainerType()) @@ -985,9 +978,11 @@ private class AccessPath extends TAccessPath { } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } - override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } } private class AccessPathCons extends AccessPath, TCons { @@ -1069,21 +1064,21 @@ private predicate flowFwd0( or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, _, apf, ap, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, false, apf, ap, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0, AccessPath ap0 | flowFwd(mid, fromArg, apf0, ap0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and ap = ap0 and apf = apf0 or @@ -1182,21 +1177,21 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, false, ap, config) and toReturn = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, _, ap, config) and toReturn = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPath ap0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flow(mid, toReturn, ap0, config) and ( preservesValue = true and ap = ap0 @@ -1274,8 +1269,14 @@ abstract class PathNode extends TPathNode { /** Gets a textual representation of this element. */ string toString() { result = getNode().toString() + ppAp() } + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + /** Gets the source location for this element. */ - Location getLocation() { result = getNode().getLocation() } + DataFlowLocation getLocation() { result = getNode().getLocation() } /** Gets the underlying `Node`. */ abstract Node getNode(); @@ -1284,20 +1285,31 @@ abstract class PathNode extends TPathNode { abstract Configuration getConfiguration(); /** Gets a successor. */ - abstract PathNode getSucc(); + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); private string ppAp() { this instanceof PathNodeSink and result = "" or - result = " [" + this.(PathNodeMid).getAp().toString() + "]" + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } } /** Holds if `n` can reach a sink. */ -private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getSucc()) } +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } /** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getSucc() = n2 and reach(n2) } +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) @@ -1337,7 +1349,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result.getConfiguration() = unbind(this.getConfiguration()) } - override PathNode getSucc() { + override PathNode getASuccessor() { // an intermediate step to another intermediate node result = getSuccMid() or @@ -1391,7 +1403,7 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } - override PathNode getSucc() { none() } + override PathNode getASuccessor() { none() } } /** @@ -1425,11 +1437,9 @@ private predicate flowStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or flowIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or - flowOutOfMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowOutOfCallable(mid, node, cc) and ap = mid.getAp() or - flowThroughMethod(mid, node.asExpr(), cc) and ap = TNil(getErasedRepr(node.getType())) - or - valueFlowThroughMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowThroughCallable(mid, node, cc) and ap = mid.getAp() } private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { @@ -1450,36 +1460,35 @@ private predicate contentStoreStep( } /** - * Holds if data may flow from `mid` to an exit of `m` in the context - * `innercc`, and the path did not flow through a parameter of `m`. + * Holds if data may flow from `mid` to a return at position `pos` in the + * context `innercc`, and the path did not flow through a parameter. */ -private predicate flowOutOfMethod0(PathNodeMid mid, Method m, CallContext innercc) { - exists(ReturnNode ret | - ret = mid.getNode() and - innercc = mid.getCallContext() and - m = returnNodeGetEnclosingCallable(ret) and - not innercc instanceof CallContextCall - ) +private predicate flowOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos.getAReturnNode() = mid.getNode() and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall } /** - * Holds if data may flow from `mid` to `ma`. The last step of this path - * is a return from a method and is recorded by `cc`, if needed. + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate flowOutOfMethod(PathNodeMid mid, MethodAccess ma, CallContext cc) { - exists(Method m, CallContext innercc | - flowOutOfMethod0(mid, m, innercc) and - resolveReturn(innercc, m, ma) +private predicate flowOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnPosition pos, DataFlowCallable c, DataFlowCall call, CallContext innercc | + flowOutOfCallable0(mid, pos, innercc) and + out = getAnOutputAtCall(call, pos) and + c = pos.getCallable() and + resolveReturn(innercc, c, call) | - if reducedViableImplInReturn(m, ma) then cc = TReturn(m, ma) else cc = TAnyCallContext() + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) } private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { exists( - PostUpdateNode n, ParameterNode p, Callable callable, CallContext innercc, int i, Call call, - ArgumentNode arg + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg | mid.getNode() = n and parameterValueFlowsToUpdate(p, n) and @@ -1500,7 +1509,9 @@ private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallCo * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. */ pragma[noinline] -private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, boolean emptyAp) { +private predicate flowIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { exists(ArgumentNode arg, AccessPath ap | arg = mid.getNode() and cc = mid.getCallContext() and @@ -1514,7 +1525,7 @@ private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, } pragma[noinline] -private predicate parameterCand(Callable callable, int i, Configuration config) { +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { exists(ParameterNode p | flow(p, config) and p.isParameterOf(callable, i) @@ -1523,7 +1534,8 @@ private predicate parameterCand(Callable callable, int i, Configuration config) pragma[nomagic] private predicate flowIntoCallable0( - PathNodeMid mid, Callable callable, int i, CallContext outercc, Call call, boolean emptyAp + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp ) { flowIntoArg(mid, i, outercc, call, emptyAp) and callable = resolveCall(call, outercc) and @@ -1536,9 +1548,9 @@ private predicate flowIntoCallable0( * respectively. */ private predicate flowIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, Call call + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call ) { - exists(int i, Callable callable, boolean emptyAp | + exists(int i, DataFlowCallable callable, boolean emptyAp | flowIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | @@ -1548,42 +1560,32 @@ private predicate flowIntoCallable( ) } -/** Holds if data may flow from `p` to a return statement in the callable. */ +/** Holds if data may flow from `p` to a return at position `pos`. */ pragma[nomagic] -private predicate paramFlowsThrough(ParameterNode p, CallContextCall cc, Configuration config) { - exists(PathNodeMid mid, ReturnNode ret | - mid.getNode() = ret and +private predicate paramFlowsThrough( + ParameterNode p, ReturnPosition pos, CallContextCall cc, Configuration config +) { + exists(PathNodeMid mid | + mid.getNode() = pos.getAReturnNode() and cc = mid.getCallContext() and - config = mid.getConfiguration() and - mid.getAp() instanceof AccessPathNil + config = mid.getConfiguration() | cc = TSomeCall(p, true) or - exists(int i | cc = TSpecificCall(_, i, true) | - p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) - ) + exists(int i | cc = TSpecificCall(_, i, true) | p.isParameterOf(pos.getCallable(), i)) ) } /** - * Holds if data may flow from `mid` to an argument of `methodcall`, - * through a called method `m`, and back out through a return statement in - * `m`. The context `cc` is restored to its value prior to entering `m`. + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate flowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p, CallContext innercc | - flowIntoCallable(mid, p, cc, innercc, methodcall) and - paramFlowsThrough(p, innercc, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p) and - mid.getAp() instanceof AccessPathNil - ) -} - -private predicate valueFlowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p | - flowIntoCallable(mid, p, cc, _, methodcall) and - parameterValueFlowsThrough(p) +private predicate flowThroughCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(DataFlowCall call, ParameterNode p, ReturnPosition pos, CallContext innercc | + flowIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, pos, innercc, unbind(mid.getConfiguration())) and + out = getAnOutputAtCall(call, pos) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 9d28e37cc8a..518bd18ee57 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -1,16 +1,15 @@ /** * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from `DataFlowUtil` - * and adds a global analysis, mainly exposed through the `Configuration` class. - * This file exists in several identical copies, allowing queries to use - * multiple `Configuration` classes that depend on each other without - * introducing mutual recursion among those configurations. + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. */ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public /** * A configuration of interprocedural data flow analysis. This defines @@ -95,7 +94,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowToExpr(Expr sink) { hasFlowTo(exprNode(sink)) } + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -114,7 +113,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi pragma[noinline] private predicate isAdditionalFlowStep( - Node node1, Node node2, Callable callable1, Callable callable2, Configuration config + Node node1, Node node2, DataFlowCallable callable1, DataFlowCallable callable2, + Configuration config ) { config.isAdditionalFlowStep(node1, node2) and callable1 = node1.getEnclosingCallable() and @@ -125,7 +125,7 @@ private predicate isAdditionalFlowStep( * Holds if the additional step from `node1` to `node2` does not jump between callables. */ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { - exists(Callable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) + exists(DataFlowCallable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) } /** @@ -148,11 +148,6 @@ private predicate localFlowStep(Node node1, Node node2, boolean preservesValue, additionalLocalFlowStep(node1, node2, config) and preservesValue = false } -pragma[noinline] -private Method returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - /** * Holds if field flow should be used for the given configuration. */ @@ -209,11 +204,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) ) or // flow out of a callable - exists(Method m, MethodAccess ma, ReturnNode ret | + exists(ReturnNode ret | nodeCandFwd1(ret, stored, config) and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node.asExpr() = ma + node = getAnOutputAtCall(_, ret.getPosition()) ) ) } @@ -287,10 +280,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) { ) or // flow out of a callable - exists(Method m, ExprNode ma | - nodeCand1(ma, stored, config) and - m = returnNodeGetEnclosingCallable(node) and - m = viableImpl(ma.getExpr()) + exists(Node out | + nodeCand1(out, stored, config) and + out = getAnOutputAtCall(_, node.(ReturnNode).getPosition()) ) ) } @@ -316,11 +308,13 @@ private predicate readCand1(Content f, Configuration config) { * from the configuration. */ pragma[nomagic] -private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Configuration config) { +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { nodeCand1(node, false, config) and p = node and t = getErasedRepr(node.getType()) and - not parameterValueFlowsThrough(p) + not parameterValueFlowsThrough(p, _) or nodeCand1(node, false, unbind(config)) and exists(Node mid | @@ -367,21 +361,21 @@ private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Con * additional step from the configuration. */ private predicate simpleArgumentFlowsThrough( - ArgumentNode arg, ExprNode call, RefType t, Configuration config + ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(ParameterNode param, ReturnNode ret | nodeCand1(arg, false, unbind(config)) and - nodeCand1(call, false, unbind(config)) and + nodeCand1(out, false, unbind(config)) and viableParamArg(param, arg) and simpleParameterFlow(param, ret, t, config) and - arg.argumentOf(call.getExpr(), _) + out = getAnOutputAtCall(arg.getCall(), ret.getPosition()) ) } /** - * Holds if data can flow from `node1` to `node2` by a step through a method. + * Holds if data can flow from `node1` to `node2` by a step through a callable. */ -private predicate flowThroughMethod( +private predicate flowThroughCallableCand1( Node node1, Node node2, boolean preservesValue, Configuration config ) { simpleArgumentFlowsThrough(node1, node2, _, config) and preservesValue = false @@ -391,13 +385,13 @@ private predicate flowThroughMethod( /** * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a method. + * through a callable. */ -private predicate localFlowStepOrFlowThroughMethod( +private predicate localFlowStepOrFlowThroughCallable( Node node1, Node node2, boolean preservesValue, Configuration config ) { localFlowStep(node1, node2, preservesValue, config) or - flowThroughMethod(node1, node2, preservesValue, config) + flowThroughCallableCand1(node1, node2, preservesValue, config) } /** @@ -405,7 +399,7 @@ private predicate localFlowStepOrFlowThroughMethod( * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ -private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { +private predicate flowOutOfCallableCand1(Node node1, Node node2, Configuration config) { nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) and ( @@ -415,13 +409,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config viableParamArg(p, node2.(PostUpdateNode).getPreUpdateNode()) ) or - // flow out of a method - exists(Method m, MethodAccess ma, ReturnNode ret | - ret = node1 and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node2.asExpr() = ma - ) + // flow out of a callable + node2 = getAnOutputAtCall(_, node1.(ReturnNode).getPosition()) ) } @@ -429,7 +418,7 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config * Holds if data can flow into a callable and that this step is part of a * path from a source to a sink. */ -private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { +private predicate flowIntoCallableCand1(Node node1, Node node2, Configuration config) { viableParamArg(node2, node1) and nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) @@ -441,7 +430,9 @@ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) * contexts. */ private int branch(Node n1, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n1, n, conf) or flowIntoCallableCand1(n1, n, conf) + ) } /** @@ -450,7 +441,9 @@ private int branch(Node n1, Configuration conf) { * contexts. */ private int join(Node n2, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n, n2, conf) or flowIntoCallableCand1(n, n2, conf) + ) } /** @@ -460,10 +453,10 @@ private int join(Node n2, Configuration conf) { * `allowsFieldFlow` flag indicates whether the branching is within the limit * specified by the configuration. */ -private predicate flowOutOfCallable( +private predicate flowOutOfCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowOutOfCallable(node1, node2, config) and + flowOutOfCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -478,10 +471,10 @@ private predicate flowOutOfCallable( * path from a source to a sink. The `allowsFieldFlow` flag indicates whether * the branching is within the limit specified by the configuration. */ -private predicate flowIntoCallable( +private predicate flowIntoCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowIntoCallable(node1, node2, config) and + flowIntoCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -505,7 +498,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ( exists(Node mid, boolean preservesValue | nodeCandFwd2(mid, fromArg, stored, config) and - localFlowStepOrFlowThroughMethod(mid, node, preservesValue, config) and + localFlowStepOrFlowThroughCallable(mid, node, preservesValue, config) and (stored = false or preservesValue = true) ) or @@ -534,14 +527,14 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, _, stored, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, false, stored, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (stored = false or allowsFieldFlow = true) ) @@ -574,7 +567,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and ( exists(Node mid, boolean preservesValue | - localFlowStepOrFlowThroughMethod(node, mid, preservesValue, config) and + localFlowStepOrFlowThroughCallable(node, mid, preservesValue, config) and nodeCand2(mid, toReturn, stored, config) and (stored = false or preservesValue = true) ) @@ -603,14 +596,14 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, false, stored, config) and toReturn = false and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, _, stored, config) and toReturn = true and (stored = false or allowsFieldFlow = true) @@ -663,10 +656,10 @@ private predicate localFlowEntry(Node node, Configuration config) { config.isSource(node) or jumpStep(_, node, _, config) or node instanceof ParameterNode or - node.asExpr() instanceof MethodAccess or + node instanceof OutNode or node instanceof PostUpdateNode or read(_, _, node) or - node.asExpr() instanceof CastExpr + node instanceof CastNode ) } @@ -677,14 +670,14 @@ private predicate localFlowEntry(Node node, Configuration config) { private predicate localFlowExit(Node node, Configuration config) { exists(Node next | nodeCand(next, config) | jumpStep(node, next, _, config) or - flowIntoCallable(node, next, config) or - flowOutOfCallable(node, next, config) or - flowThroughMethod(node, next, _, config) or + flowIntoCallableCand1(node, next, config) or + flowOutOfCallableCand1(node, next, config) or + flowThroughCallableCand1(node, next, _, config) or store(node, _, next) or read(node, _, next) ) or - node.asExpr() instanceof CastExpr + node instanceof CastNode or config.isSink(node) } @@ -706,7 +699,7 @@ private predicate localFlowStepPlus( exists(Node mid, boolean pv1, boolean pv2 | localFlowStepPlus(node1, mid, pv1, config) and localFlowStep(mid, node2, pv2, config) and - not mid.asExpr() instanceof CastExpr and + not mid instanceof CastNode and preservesValue = pv1.booleanAnd(pv2) and nodeCand(node2, unbind(config)) ) @@ -725,7 +718,7 @@ private predicate localFlowBigStep( } private newtype TAccessPathFront = - TFrontNil(Type t) or + TFrontNil(DataFlowType t) or TFrontHead(Content f) /** @@ -733,12 +726,12 @@ private newtype TAccessPathFront = */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) or exists(Content f | this = TFrontHead(f) | result = f.toString()) } - Type getType() { + DataFlowType getType() { this = TFrontNil(result) or exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) @@ -755,8 +748,8 @@ private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } private class CastingNode extends Node { CastingNode() { this instanceof ParameterNode or - this.asExpr() instanceof CastExpr or - this.asExpr() instanceof MethodAccess or + this instanceof CastNode or + this instanceof OutNode or this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode } } @@ -805,21 +798,21 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, _, apf, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, false, apf, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | flowCandFwd(mid, fromArg, apf0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and apf = apf0 or @@ -901,21 +894,21 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, false, apf, config) and toReturn = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, _, apf, config) and toReturn = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flowCand(mid, toReturn, apf0, config) and ( preservesValue = true and apf = apf0 @@ -954,7 +947,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) } private newtype TAccessPath = - TNil(Type t) or + TNil(DataFlowType t) or TCons(Content f, int len) { len in [1 .. 5] } /** @@ -975,7 +968,7 @@ private class AccessPath extends TAccessPath { this = TCons(_, result) } - Type getType() { + DataFlowType getType() { this = TNil(result) or exists(Content head | this = TCons(head, _) | result = head.getContainerType()) @@ -985,9 +978,11 @@ private class AccessPath extends TAccessPath { } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } - override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } } private class AccessPathCons extends AccessPath, TCons { @@ -1069,21 +1064,21 @@ private predicate flowFwd0( or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, _, apf, ap, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, false, apf, ap, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0, AccessPath ap0 | flowFwd(mid, fromArg, apf0, ap0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and ap = ap0 and apf = apf0 or @@ -1182,21 +1177,21 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, false, ap, config) and toReturn = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, _, ap, config) and toReturn = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPath ap0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flow(mid, toReturn, ap0, config) and ( preservesValue = true and ap = ap0 @@ -1274,8 +1269,14 @@ abstract class PathNode extends TPathNode { /** Gets a textual representation of this element. */ string toString() { result = getNode().toString() + ppAp() } + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + /** Gets the source location for this element. */ - Location getLocation() { result = getNode().getLocation() } + DataFlowLocation getLocation() { result = getNode().getLocation() } /** Gets the underlying `Node`. */ abstract Node getNode(); @@ -1284,20 +1285,31 @@ abstract class PathNode extends TPathNode { abstract Configuration getConfiguration(); /** Gets a successor. */ - abstract PathNode getSucc(); + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); private string ppAp() { this instanceof PathNodeSink and result = "" or - result = " [" + this.(PathNodeMid).getAp().toString() + "]" + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } } /** Holds if `n` can reach a sink. */ -private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getSucc()) } +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } /** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getSucc() = n2 and reach(n2) } +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) @@ -1337,7 +1349,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result.getConfiguration() = unbind(this.getConfiguration()) } - override PathNode getSucc() { + override PathNode getASuccessor() { // an intermediate step to another intermediate node result = getSuccMid() or @@ -1391,7 +1403,7 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } - override PathNode getSucc() { none() } + override PathNode getASuccessor() { none() } } /** @@ -1425,11 +1437,9 @@ private predicate flowStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or flowIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or - flowOutOfMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowOutOfCallable(mid, node, cc) and ap = mid.getAp() or - flowThroughMethod(mid, node.asExpr(), cc) and ap = TNil(getErasedRepr(node.getType())) - or - valueFlowThroughMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowThroughCallable(mid, node, cc) and ap = mid.getAp() } private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { @@ -1450,36 +1460,35 @@ private predicate contentStoreStep( } /** - * Holds if data may flow from `mid` to an exit of `m` in the context - * `innercc`, and the path did not flow through a parameter of `m`. + * Holds if data may flow from `mid` to a return at position `pos` in the + * context `innercc`, and the path did not flow through a parameter. */ -private predicate flowOutOfMethod0(PathNodeMid mid, Method m, CallContext innercc) { - exists(ReturnNode ret | - ret = mid.getNode() and - innercc = mid.getCallContext() and - m = returnNodeGetEnclosingCallable(ret) and - not innercc instanceof CallContextCall - ) +private predicate flowOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos.getAReturnNode() = mid.getNode() and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall } /** - * Holds if data may flow from `mid` to `ma`. The last step of this path - * is a return from a method and is recorded by `cc`, if needed. + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate flowOutOfMethod(PathNodeMid mid, MethodAccess ma, CallContext cc) { - exists(Method m, CallContext innercc | - flowOutOfMethod0(mid, m, innercc) and - resolveReturn(innercc, m, ma) +private predicate flowOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnPosition pos, DataFlowCallable c, DataFlowCall call, CallContext innercc | + flowOutOfCallable0(mid, pos, innercc) and + out = getAnOutputAtCall(call, pos) and + c = pos.getCallable() and + resolveReturn(innercc, c, call) | - if reducedViableImplInReturn(m, ma) then cc = TReturn(m, ma) else cc = TAnyCallContext() + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) } private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { exists( - PostUpdateNode n, ParameterNode p, Callable callable, CallContext innercc, int i, Call call, - ArgumentNode arg + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg | mid.getNode() = n and parameterValueFlowsToUpdate(p, n) and @@ -1500,7 +1509,9 @@ private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallCo * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. */ pragma[noinline] -private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, boolean emptyAp) { +private predicate flowIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { exists(ArgumentNode arg, AccessPath ap | arg = mid.getNode() and cc = mid.getCallContext() and @@ -1514,7 +1525,7 @@ private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, } pragma[noinline] -private predicate parameterCand(Callable callable, int i, Configuration config) { +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { exists(ParameterNode p | flow(p, config) and p.isParameterOf(callable, i) @@ -1523,7 +1534,8 @@ private predicate parameterCand(Callable callable, int i, Configuration config) pragma[nomagic] private predicate flowIntoCallable0( - PathNodeMid mid, Callable callable, int i, CallContext outercc, Call call, boolean emptyAp + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp ) { flowIntoArg(mid, i, outercc, call, emptyAp) and callable = resolveCall(call, outercc) and @@ -1536,9 +1548,9 @@ private predicate flowIntoCallable0( * respectively. */ private predicate flowIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, Call call + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call ) { - exists(int i, Callable callable, boolean emptyAp | + exists(int i, DataFlowCallable callable, boolean emptyAp | flowIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | @@ -1548,42 +1560,32 @@ private predicate flowIntoCallable( ) } -/** Holds if data may flow from `p` to a return statement in the callable. */ +/** Holds if data may flow from `p` to a return at position `pos`. */ pragma[nomagic] -private predicate paramFlowsThrough(ParameterNode p, CallContextCall cc, Configuration config) { - exists(PathNodeMid mid, ReturnNode ret | - mid.getNode() = ret and +private predicate paramFlowsThrough( + ParameterNode p, ReturnPosition pos, CallContextCall cc, Configuration config +) { + exists(PathNodeMid mid | + mid.getNode() = pos.getAReturnNode() and cc = mid.getCallContext() and - config = mid.getConfiguration() and - mid.getAp() instanceof AccessPathNil + config = mid.getConfiguration() | cc = TSomeCall(p, true) or - exists(int i | cc = TSpecificCall(_, i, true) | - p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) - ) + exists(int i | cc = TSpecificCall(_, i, true) | p.isParameterOf(pos.getCallable(), i)) ) } /** - * Holds if data may flow from `mid` to an argument of `methodcall`, - * through a called method `m`, and back out through a return statement in - * `m`. The context `cc` is restored to its value prior to entering `m`. + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate flowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p, CallContext innercc | - flowIntoCallable(mid, p, cc, innercc, methodcall) and - paramFlowsThrough(p, innercc, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p) and - mid.getAp() instanceof AccessPathNil - ) -} - -private predicate valueFlowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p | - flowIntoCallable(mid, p, cc, _, methodcall) and - parameterValueFlowsThrough(p) +private predicate flowThroughCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(DataFlowCall call, ParameterNode p, ReturnPosition pos, CallContext innercc | + flowIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, pos, innercc, unbind(mid.getConfiguration())) and + out = getAnOutputAtCall(call, pos) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll index c05680686e1..3eb1906099f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -1,6 +1,5 @@ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public cached private module ImplCommon { @@ -9,11 +8,8 @@ private module ImplCommon { * The instance parameter is considered to have index `-1`. */ pragma[nomagic] - private predicate viableParam(Call call, int i, ParameterNode p) { - exists(Callable callable | - callable = viableCallable(call) and - p.isParameterOf(callable, i) - ) + private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + p.isParameterOf(viableCallable(call), i) } /** @@ -21,7 +17,7 @@ private module ImplCommon { */ cached predicate viableParamArg(ParameterNode p, ArgumentNode arg) { - exists(int i, Call call | + exists(int i, DataFlowCall call | viableParam(call, i, p) and arg.argumentOf(call, i) ) @@ -49,24 +45,24 @@ private module ImplCommon { } /** - * Holds if `p` can flow to a `ReturnNode` in the same callable using only - * value-preserving steps. + * Holds if `p` can flow to a return node with position `pos` in the same + * callable using only value-preserving steps. */ cached - predicate parameterValueFlowsThrough(ParameterNode p) { - exists(ReturnNode ret | parameterValueFlow(p, ret)) + predicate parameterValueFlowsThrough(ParameterNode p, ReturnPosition pos) { + parameterValueFlow(p, pos.getAReturnNode()) } /** * Holds if `arg` flows through `call` using only value-preserving steps. */ cached - predicate argumentValueFlowsThrough(ArgumentNode arg, ExprNode call) { - exists(ParameterNode param | + predicate argumentValueFlowsThrough(ArgumentNode arg, Node out) { + exists(ParameterNode param, ReturnPosition ret | viableParamArg(param, arg) and - parameterValueFlowsThrough(param) and - arg.argumentOf(call.getExpr(), _) and - compatibleTypes(arg.getType(), call.getType()) + parameterValueFlowsThrough(param, ret) and + out = getAnOutputAtCall(arg.getCall(), ret) and + compatibleTypes(arg.getType(), out.getType()) ) } @@ -113,7 +109,7 @@ private module ImplCommon { private predicate storeViaSideEffect(Node node1, Content f, PostUpdateNode node2) { storeStep(node1, f, node2) and readStep(_, f, _) or - exists(Call call, int i1, int i2 | + exists(DataFlowCall call, int i1, int i2 | setterCall(call, i1, i2, f) and node1.(ArgumentNode).argumentOf(call, i1) and node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and @@ -133,8 +129,8 @@ private module ImplCommon { } pragma[nomagic] - private predicate setterCall(Call call, int i1, int i2, Content f) { - exists(Callable callable, ParameterNode p1, ParameterNode p2 | + private predicate setterCall(DataFlowCall call, int i1, int i2, Content f) { + exists(DataFlowCallable callable, ParameterNode p1, ParameterNode p2 | setterInParam(p1, f, p2) and callable = viableCallable(call) and p1.isParameterOf(callable, i1) and @@ -147,7 +143,7 @@ private module ImplCommon { arg = node1 and viableParamArg(p, arg) and setterReturn(p, f) and - arg.argumentOf(node2.asExpr(), _) and + node2 = arg.getCall().getNode() and compatibleTypes(node1.getTypeBound(), f.getType()) and compatibleTypes(node2.getTypeBound(), f.getContainerType()) ) @@ -173,7 +169,7 @@ private module ImplCommon { arg = node1 and viableParamArg(p, arg) and getter(p, f) and - arg.argumentOf(node2.asExpr(), _) and + node2 = arg.getCall().getNode() and compatibleTypes(node1.getTypeBound(), f.getContainerType()) and compatibleTypes(node2.getTypeBound(), f.getType()) ) @@ -200,14 +196,14 @@ private module ImplCommon { * Holds if `call` passes an implicit or explicit instance argument, i.e., an * expression that reaches a `this` parameter. */ - private predicate callHasInstanceArgument(Call call) { + private predicate callHasInstanceArgument(DataFlowCall call) { exists(ArgumentNode arg | arg.argumentOf(call, -1)) } cached newtype TCallContext = TAnyCallContext() or - TSpecificCall(Call call, int i, boolean emptyAp) { + TSpecificCall(DataFlowCall call, int i, boolean emptyAp) { reducedViableImplInCallContext(_, _, call) and (emptyAp = true or emptyAp = false) and ( @@ -217,7 +213,7 @@ private module ImplCommon { ) } or TSomeCall(ParameterNode p, boolean emptyAp) { emptyAp = true or emptyAp = false } or - TReturn(Method m, MethodAccess ma) { reducedViableImplInReturn(m, ma) } + TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } } import ImplCommon @@ -227,14 +223,14 @@ import ImplCommon * * There are four cases: * - `TAnyCallContext()` : No restrictions on method flow. - * - `TSpecificCall(Call call, int i)` : Flow entered through the `i`th + * - `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. * - `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. - * - `TReturn(Method m, MethodAccess ma)` : Flow reached `ma` from `m` and - * this dispatch target of `ma` implies a reduced set of dispatch origins + * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and + * this dispatch target of `call` implies a reduced set of dispatch origins * to which data may flow if it should reach a `return` statement. */ abstract class CallContext extends TCallContext { @@ -248,7 +244,11 @@ class CallContextAny extends CallContext, TAnyCallContext { abstract class CallContextCall extends CallContext { } class CallContextSpecificCall extends CallContextCall, TSpecificCall { - override string toString() { result = "CcCall" } + override string toString() { + exists(DataFlowCall call, int i | this = TSpecificCall(call, i, _) | + result = "CcCall(" + call + ", " + i + ")" + ) + } } class CallContextSomeCall extends CallContextCall, TSomeCall { @@ -256,23 +256,25 @@ class CallContextSomeCall extends CallContextCall, TSomeCall { } class CallContextReturn extends CallContext, TReturn { - override string toString() { result = "CcReturn" } + override string toString() { + exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") + } } bindingset[cc, callable] -predicate resolveReturn(CallContext cc, Callable callable, Call call) { +predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { cc instanceof CallContextAny and callable = viableCallable(call) or - exists(Method m0, MethodAccess ma0 | - ma0.getEnclosingCallable() = callable and - cc = TReturn(m0, ma0) and - m0 = prunedViableImplInCallContextReverse(ma0, call) + exists(DataFlowCallable c0, DataFlowCall call0 | + call0.getEnclosingCallable() = callable and + cc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) ) } bindingset[call, cc] -Callable resolveCall(Call call, CallContext cc) { - exists(Call ctx | cc = TSpecificCall(ctx, _, _) | +DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx, _, _) | if reducedViableImplInCallContext(call, _, ctx) then result = prunedViableImplInCallContext(call, ctx) else result = viableCallable(call) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll index 9d28e37cc8a..518bd18ee57 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll @@ -1,16 +1,15 @@ /** * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from `DataFlowUtil` - * and adds a global analysis, mainly exposed through the `Configuration` class. - * This file exists in several identical copies, allowing queries to use - * multiple `Configuration` classes that depend on each other without - * introducing mutual recursion among those configurations. + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. */ -import DataFlowUtil -private import DataFlowPrivate -private import DataFlowDispatch private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public /** * A configuration of interprocedural data flow analysis. This defines @@ -95,7 +94,7 @@ abstract class Configuration extends string { /** * Holds if data may flow from some source to `sink` for this configuration. */ - predicate hasFlowToExpr(Expr sink) { hasFlowTo(exprNode(sink)) } + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -114,7 +113,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi pragma[noinline] private predicate isAdditionalFlowStep( - Node node1, Node node2, Callable callable1, Callable callable2, Configuration config + Node node1, Node node2, DataFlowCallable callable1, DataFlowCallable callable2, + Configuration config ) { config.isAdditionalFlowStep(node1, node2) and callable1 = node1.getEnclosingCallable() and @@ -125,7 +125,7 @@ private predicate isAdditionalFlowStep( * Holds if the additional step from `node1` to `node2` does not jump between callables. */ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { - exists(Callable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) + exists(DataFlowCallable callable | isAdditionalFlowStep(node1, node2, callable, callable, config)) } /** @@ -148,11 +148,6 @@ private predicate localFlowStep(Node node1, Node node2, boolean preservesValue, additionalLocalFlowStep(node1, node2, config) and preservesValue = false } -pragma[noinline] -private Method returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - /** * Holds if field flow should be used for the given configuration. */ @@ -209,11 +204,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) ) or // flow out of a callable - exists(Method m, MethodAccess ma, ReturnNode ret | + exists(ReturnNode ret | nodeCandFwd1(ret, stored, config) and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node.asExpr() = ma + node = getAnOutputAtCall(_, ret.getPosition()) ) ) } @@ -287,10 +280,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) { ) or // flow out of a callable - exists(Method m, ExprNode ma | - nodeCand1(ma, stored, config) and - m = returnNodeGetEnclosingCallable(node) and - m = viableImpl(ma.getExpr()) + exists(Node out | + nodeCand1(out, stored, config) and + out = getAnOutputAtCall(_, node.(ReturnNode).getPosition()) ) ) } @@ -316,11 +308,13 @@ private predicate readCand1(Content f, Configuration config) { * from the configuration. */ pragma[nomagic] -private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Configuration config) { +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { nodeCand1(node, false, config) and p = node and t = getErasedRepr(node.getType()) and - not parameterValueFlowsThrough(p) + not parameterValueFlowsThrough(p, _) or nodeCand1(node, false, unbind(config)) and exists(Node mid | @@ -367,21 +361,21 @@ private predicate simpleParameterFlow(ParameterNode p, Node node, RefType t, Con * additional step from the configuration. */ private predicate simpleArgumentFlowsThrough( - ArgumentNode arg, ExprNode call, RefType t, Configuration config + ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(ParameterNode param, ReturnNode ret | nodeCand1(arg, false, unbind(config)) and - nodeCand1(call, false, unbind(config)) and + nodeCand1(out, false, unbind(config)) and viableParamArg(param, arg) and simpleParameterFlow(param, ret, t, config) and - arg.argumentOf(call.getExpr(), _) + out = getAnOutputAtCall(arg.getCall(), ret.getPosition()) ) } /** - * Holds if data can flow from `node1` to `node2` by a step through a method. + * Holds if data can flow from `node1` to `node2` by a step through a callable. */ -private predicate flowThroughMethod( +private predicate flowThroughCallableCand1( Node node1, Node node2, boolean preservesValue, Configuration config ) { simpleArgumentFlowsThrough(node1, node2, _, config) and preservesValue = false @@ -391,13 +385,13 @@ private predicate flowThroughMethod( /** * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a method. + * through a callable. */ -private predicate localFlowStepOrFlowThroughMethod( +private predicate localFlowStepOrFlowThroughCallable( Node node1, Node node2, boolean preservesValue, Configuration config ) { localFlowStep(node1, node2, preservesValue, config) or - flowThroughMethod(node1, node2, preservesValue, config) + flowThroughCallableCand1(node1, node2, preservesValue, config) } /** @@ -405,7 +399,7 @@ private predicate localFlowStepOrFlowThroughMethod( * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ -private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { +private predicate flowOutOfCallableCand1(Node node1, Node node2, Configuration config) { nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) and ( @@ -415,13 +409,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config viableParamArg(p, node2.(PostUpdateNode).getPreUpdateNode()) ) or - // flow out of a method - exists(Method m, MethodAccess ma, ReturnNode ret | - ret = node1 and - m = returnNodeGetEnclosingCallable(ret) and - m = viableImpl(ma) and - node2.asExpr() = ma - ) + // flow out of a callable + node2 = getAnOutputAtCall(_, node1.(ReturnNode).getPosition()) ) } @@ -429,7 +418,7 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config * Holds if data can flow into a callable and that this step is part of a * path from a source to a sink. */ -private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { +private predicate flowIntoCallableCand1(Node node1, Node node2, Configuration config) { viableParamArg(node2, node1) and nodeCand1(node1, _, unbind(config)) and nodeCand1(node2, _, config) @@ -441,7 +430,9 @@ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) * contexts. */ private int branch(Node n1, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n1, n, conf) or flowIntoCallableCand1(n1, n, conf) + ) } /** @@ -450,7 +441,9 @@ private int branch(Node n1, Configuration conf) { * contexts. */ private int join(Node n2, Configuration conf) { - result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) + result = strictcount(Node n | + flowOutOfCallableCand1(n, n2, conf) or flowIntoCallableCand1(n, n2, conf) + ) } /** @@ -460,10 +453,10 @@ private int join(Node n2, Configuration conf) { * `allowsFieldFlow` flag indicates whether the branching is within the limit * specified by the configuration. */ -private predicate flowOutOfCallable( +private predicate flowOutOfCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowOutOfCallable(node1, node2, config) and + flowOutOfCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -478,10 +471,10 @@ private predicate flowOutOfCallable( * path from a source to a sink. The `allowsFieldFlow` flag indicates whether * the branching is within the limit specified by the configuration. */ -private predicate flowIntoCallable( +private predicate flowIntoCallableCand1( Node node1, Node node2, boolean allowsFieldFlow, Configuration config ) { - flowIntoCallable(node1, node2, config) and + flowIntoCallableCand1(node1, node2, config) and exists(int b, int j | b = branch(node1, config) and j = join(node2, config) and @@ -505,7 +498,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ( exists(Node mid, boolean preservesValue | nodeCandFwd2(mid, fromArg, stored, config) and - localFlowStepOrFlowThroughMethod(mid, node, preservesValue, config) and + localFlowStepOrFlowThroughCallable(mid, node, preservesValue, config) and (stored = false or preservesValue = true) ) or @@ -534,14 +527,14 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, _, stored, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | nodeCandFwd2(mid, false, stored, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (stored = false or allowsFieldFlow = true) ) @@ -574,7 +567,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and ( exists(Node mid, boolean preservesValue | - localFlowStepOrFlowThroughMethod(node, mid, preservesValue, config) and + localFlowStepOrFlowThroughCallable(node, mid, preservesValue, config) and nodeCand2(mid, toReturn, stored, config) and (stored = false or preservesValue = true) ) @@ -603,14 +596,14 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, false, stored, config) and toReturn = false and (stored = false or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and nodeCand2(mid, _, stored, config) and toReturn = true and (stored = false or allowsFieldFlow = true) @@ -663,10 +656,10 @@ private predicate localFlowEntry(Node node, Configuration config) { config.isSource(node) or jumpStep(_, node, _, config) or node instanceof ParameterNode or - node.asExpr() instanceof MethodAccess or + node instanceof OutNode or node instanceof PostUpdateNode or read(_, _, node) or - node.asExpr() instanceof CastExpr + node instanceof CastNode ) } @@ -677,14 +670,14 @@ private predicate localFlowEntry(Node node, Configuration config) { private predicate localFlowExit(Node node, Configuration config) { exists(Node next | nodeCand(next, config) | jumpStep(node, next, _, config) or - flowIntoCallable(node, next, config) or - flowOutOfCallable(node, next, config) or - flowThroughMethod(node, next, _, config) or + flowIntoCallableCand1(node, next, config) or + flowOutOfCallableCand1(node, next, config) or + flowThroughCallableCand1(node, next, _, config) or store(node, _, next) or read(node, _, next) ) or - node.asExpr() instanceof CastExpr + node instanceof CastNode or config.isSink(node) } @@ -706,7 +699,7 @@ private predicate localFlowStepPlus( exists(Node mid, boolean pv1, boolean pv2 | localFlowStepPlus(node1, mid, pv1, config) and localFlowStep(mid, node2, pv2, config) and - not mid.asExpr() instanceof CastExpr and + not mid instanceof CastNode and preservesValue = pv1.booleanAnd(pv2) and nodeCand(node2, unbind(config)) ) @@ -725,7 +718,7 @@ private predicate localFlowBigStep( } private newtype TAccessPathFront = - TFrontNil(Type t) or + TFrontNil(DataFlowType t) or TFrontHead(Content f) /** @@ -733,12 +726,12 @@ private newtype TAccessPathFront = */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) or exists(Content f | this = TFrontHead(f) | result = f.toString()) } - Type getType() { + DataFlowType getType() { this = TFrontNil(result) or exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) @@ -755,8 +748,8 @@ private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } private class CastingNode extends Node { CastingNode() { this instanceof ParameterNode or - this.asExpr() instanceof CastExpr or - this.asExpr() instanceof MethodAccess or + this instanceof CastNode or + this instanceof OutNode or this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode } } @@ -805,21 +798,21 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, _, apf, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowCandFwd(mid, false, apf, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | flowCandFwd(mid, fromArg, apf0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and apf = apf0 or @@ -901,21 +894,21 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, false, apf, config) and toReturn = false and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flowCand(mid, _, apf, config) and toReturn = true and (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flowCand(mid, toReturn, apf0, config) and ( preservesValue = true and apf = apf0 @@ -954,7 +947,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) } private newtype TAccessPath = - TNil(Type t) or + TNil(DataFlowType t) or TCons(Content f, int len) { len in [1 .. 5] } /** @@ -975,7 +968,7 @@ private class AccessPath extends TAccessPath { this = TCons(_, result) } - Type getType() { + DataFlowType getType() { this = TNil(result) or exists(Content head | this = TCons(head, _) | result = head.getContainerType()) @@ -985,9 +978,11 @@ private class AccessPath extends TAccessPath { } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } - override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } } private class AccessPathCons extends AccessPath, TCons { @@ -1069,21 +1064,21 @@ private predicate flowFwd0( or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, _, apf, ap, config) and - flowIntoCallable(mid, node, allowsFieldFlow, config) and + flowIntoCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | flowFwd(mid, false, apf, ap, config) and - flowOutOfCallable(mid, node, allowsFieldFlow, config) and + flowOutOfCallableCand1(mid, node, allowsFieldFlow, config) and fromArg = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPathFront apf0, AccessPath ap0 | flowFwd(mid, fromArg, apf0, ap0, config) and - flowThroughMethod(mid, node, preservesValue, config) and + flowThroughCallableCand1(mid, node, preservesValue, config) and ( preservesValue = true and ap = ap0 and apf = apf0 or @@ -1182,21 +1177,21 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ) or exists(Node mid, boolean allowsFieldFlow | - flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowIntoCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, false, ap, config) and toReturn = false and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean allowsFieldFlow | - flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowOutOfCallableCand1(node, mid, allowsFieldFlow, config) and flow(mid, _, ap, config) and toReturn = true and (ap instanceof AccessPathNil or allowsFieldFlow = true) ) or exists(Node mid, boolean preservesValue, AccessPath ap0 | - flowThroughMethod(node, mid, preservesValue, config) and + flowThroughCallableCand1(node, mid, preservesValue, config) and flow(mid, toReturn, ap0, config) and ( preservesValue = true and ap = ap0 @@ -1274,8 +1269,14 @@ abstract class PathNode extends TPathNode { /** Gets a textual representation of this element. */ string toString() { result = getNode().toString() + ppAp() } + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + /** Gets the source location for this element. */ - Location getLocation() { result = getNode().getLocation() } + DataFlowLocation getLocation() { result = getNode().getLocation() } /** Gets the underlying `Node`. */ abstract Node getNode(); @@ -1284,20 +1285,31 @@ abstract class PathNode extends TPathNode { abstract Configuration getConfiguration(); /** Gets a successor. */ - abstract PathNode getSucc(); + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); private string ppAp() { this instanceof PathNodeSink and result = "" or - result = " [" + this.(PathNodeMid).getAp().toString() + "]" + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } } /** Holds if `n` can reach a sink. */ -private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getSucc()) } +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } /** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getSucc() = n2 and reach(n2) } +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) @@ -1337,7 +1349,7 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result.getConfiguration() = unbind(this.getConfiguration()) } - override PathNode getSucc() { + override PathNode getASuccessor() { // an intermediate step to another intermediate node result = getSuccMid() or @@ -1391,7 +1403,7 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } - override PathNode getSucc() { none() } + override PathNode getASuccessor() { none() } } /** @@ -1425,11 +1437,9 @@ private predicate flowStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or flowIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or - flowOutOfMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowOutOfCallable(mid, node, cc) and ap = mid.getAp() or - flowThroughMethod(mid, node.asExpr(), cc) and ap = TNil(getErasedRepr(node.getType())) - or - valueFlowThroughMethod(mid, node.asExpr(), cc) and ap = mid.getAp() + flowThroughCallable(mid, node, cc) and ap = mid.getAp() } private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { @@ -1450,36 +1460,35 @@ private predicate contentStoreStep( } /** - * Holds if data may flow from `mid` to an exit of `m` in the context - * `innercc`, and the path did not flow through a parameter of `m`. + * Holds if data may flow from `mid` to a return at position `pos` in the + * context `innercc`, and the path did not flow through a parameter. */ -private predicate flowOutOfMethod0(PathNodeMid mid, Method m, CallContext innercc) { - exists(ReturnNode ret | - ret = mid.getNode() and - innercc = mid.getCallContext() and - m = returnNodeGetEnclosingCallable(ret) and - not innercc instanceof CallContextCall - ) +private predicate flowOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos.getAReturnNode() = mid.getNode() and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall } /** - * Holds if data may flow from `mid` to `ma`. The last step of this path - * is a return from a method and is recorded by `cc`, if needed. + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate flowOutOfMethod(PathNodeMid mid, MethodAccess ma, CallContext cc) { - exists(Method m, CallContext innercc | - flowOutOfMethod0(mid, m, innercc) and - resolveReturn(innercc, m, ma) +private predicate flowOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnPosition pos, DataFlowCallable c, DataFlowCall call, CallContext innercc | + flowOutOfCallable0(mid, pos, innercc) and + out = getAnOutputAtCall(call, pos) and + c = pos.getCallable() and + resolveReturn(innercc, c, call) | - if reducedViableImplInReturn(m, ma) then cc = TReturn(m, ma) else cc = TAnyCallContext() + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) } private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { exists( - PostUpdateNode n, ParameterNode p, Callable callable, CallContext innercc, int i, Call call, - ArgumentNode arg + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg | mid.getNode() = n and parameterValueFlowsToUpdate(p, n) and @@ -1500,7 +1509,9 @@ private predicate flowOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallCo * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. */ pragma[noinline] -private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, boolean emptyAp) { +private predicate flowIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { exists(ArgumentNode arg, AccessPath ap | arg = mid.getNode() and cc = mid.getCallContext() and @@ -1514,7 +1525,7 @@ private predicate flowIntoArg(PathNodeMid mid, int i, CallContext cc, Call call, } pragma[noinline] -private predicate parameterCand(Callable callable, int i, Configuration config) { +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { exists(ParameterNode p | flow(p, config) and p.isParameterOf(callable, i) @@ -1523,7 +1534,8 @@ private predicate parameterCand(Callable callable, int i, Configuration config) pragma[nomagic] private predicate flowIntoCallable0( - PathNodeMid mid, Callable callable, int i, CallContext outercc, Call call, boolean emptyAp + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp ) { flowIntoArg(mid, i, outercc, call, emptyAp) and callable = resolveCall(call, outercc) and @@ -1536,9 +1548,9 @@ private predicate flowIntoCallable0( * respectively. */ private predicate flowIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, Call call + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call ) { - exists(int i, Callable callable, boolean emptyAp | + exists(int i, DataFlowCallable callable, boolean emptyAp | flowIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | @@ -1548,42 +1560,32 @@ private predicate flowIntoCallable( ) } -/** Holds if data may flow from `p` to a return statement in the callable. */ +/** Holds if data may flow from `p` to a return at position `pos`. */ pragma[nomagic] -private predicate paramFlowsThrough(ParameterNode p, CallContextCall cc, Configuration config) { - exists(PathNodeMid mid, ReturnNode ret | - mid.getNode() = ret and +private predicate paramFlowsThrough( + ParameterNode p, ReturnPosition pos, CallContextCall cc, Configuration config +) { + exists(PathNodeMid mid | + mid.getNode() = pos.getAReturnNode() and cc = mid.getCallContext() and - config = mid.getConfiguration() and - mid.getAp() instanceof AccessPathNil + config = mid.getConfiguration() | cc = TSomeCall(p, true) or - exists(int i | cc = TSpecificCall(_, i, true) | - p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) - ) + exists(int i | cc = TSpecificCall(_, i, true) | p.isParameterOf(pos.getCallable(), i)) ) } /** - * Holds if data may flow from `mid` to an argument of `methodcall`, - * through a called method `m`, and back out through a return statement in - * `m`. The context `cc` is restored to its value prior to entering `m`. + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate flowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p, CallContext innercc | - flowIntoCallable(mid, p, cc, innercc, methodcall) and - paramFlowsThrough(p, innercc, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p) and - mid.getAp() instanceof AccessPathNil - ) -} - -private predicate valueFlowThroughMethod(PathNodeMid mid, Call methodcall, CallContext cc) { - exists(ParameterNode p | - flowIntoCallable(mid, p, cc, _, methodcall) and - parameterValueFlowsThrough(p) +private predicate flowThroughCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(DataFlowCall call, ParameterNode p, ReturnPosition pos, CallContext innercc | + flowIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, pos, innercc, unbind(mid.getConfiguration())) and + out = getAnOutputAtCall(call, pos) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll new file mode 100644 index 00000000000..1f1e9125845 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll @@ -0,0 +1,11 @@ +/** + * Provides Java-specific definitions for use in the data flow library. + */ +module Private { + import DataFlowPrivate + import DataFlowDispatch +} + +module Public { + import DataFlowUtil +} diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 62475df4d37..32939d3c7f6 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -1,5 +1,6 @@ -import java +private import java private import DataFlowUtil +private import DataFlowDispatch private import semmle.code.java.dataflow.SSA private import semmle.code.java.dataflow.TypeFlow @@ -22,7 +23,7 @@ class ArgumentNode extends Node { * Holds if this argument occurs at the given position in the given call. * The instance argument is considered to have index `-1`. */ - predicate argumentOf(Call call, int pos) { + predicate argumentOf(DataFlowCall call, int pos) { exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition()) or call = this.(ImplicitVarargsArray).getCall() and @@ -30,11 +31,22 @@ class ArgumentNode extends Node { or pos = -1 and this = getInstanceArgument(call) } + + /** Gets the call in which this node is an argument. */ + DataFlowCall getCall() { this.argumentOf(result, _) } } /** A data flow node that occurs as the result of a `ReturnStmt`. */ class ReturnNode extends ExprNode { ReturnNode() { exists(ReturnStmt ret | this.getExpr() = ret.getResult()) } + + /** Gets the position at which this value is returned. */ + ReturnPosition getPosition() { this = result.getAReturnNode() } +} + +/** A data flow node that represents a call. */ +class OutNode extends ExprNode { + OutNode() { this.getExpr() instanceof Call } } /** @@ -230,3 +242,21 @@ predicate compatibleTypes(Type t1, Type t2) { canContainBool(e1) and canContainBool(e2) ) } + +/** A node that performs a type cast. */ +class CastNode extends ExprNode { + CastNode() { this.getExpr() instanceof CastExpr } +} + +class DataFlowCallable = Callable; + +class DataFlowExpr = Expr; + +class DataFlowType = RefType; + +class DataFlowLocation = Location; + +class DataFlowCall extends Call { + /** Gets the data flow node corresponding to this call. */ + ExprNode getNode() { result.getExpr() = this } +}