From 9f3a577c1947eae0fc546d681762a844946b901b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 9 Jul 2024 12:53:41 +0200 Subject: [PATCH] Java: Adopt shared SSA library --- .../semmle/code/java/dataflow/Nullness.qll | 35 +- java/ql/lib/semmle/code/java/dataflow/SSA.qll | 896 ++---------------- .../code/java/dataflow/internal/BaseSSA.qll | 4 +- .../java/dataflow/internal/DataFlowNodes.qll | 46 +- .../dataflow/internal/DataFlowPrivate.qll | 6 +- .../java/dataflow/internal/DataFlowUtil.qll | 68 +- .../code/java/dataflow/internal/SsaImpl.qll | 745 +++++++++++++++ .../Dead Code/DeadLocals.qll | 54 +- .../Dead Code/DeadStoreOfLocal.ql | 11 +- .../Dead Code/DeadStoreOfLocalUnread.ql | 11 +- .../dataflow/capture/test.expected | 20 + .../dataflow/null/testnullflow.expected | 2 + .../dataflow/partial/test.expected | 4 +- .../dataflow/partial/testRev.expected | 4 +- .../switchexpr/switchexprflow.expected | 4 + .../dataflow/taint-ioutils/dataFlow.expected | 5 + .../dataflow/this-flow/this-flow.expected | 2 + .../pattern-switch/dfg/GuardTest.java | 6 + .../ql/test/library-tests/ssa/ssaDef.expected | 27 - 19 files changed, 978 insertions(+), 972 deletions(-) create mode 100644 java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll diff --git a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll index 618716629b10..17f1b4ae7021 100644 --- a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll +++ b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll @@ -463,6 +463,21 @@ private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond not cond.getCondition().(Expr).getAChildExpr*() = npecand.getAnAccess() } +pragma[nomagic] +private ConditionBlock ssaIntegerGuard(SsaVariable v, boolean branch, int k, boolean is_k) { + result.getCondition() = integerGuard(v.getAUse(), branch, k, is_k) +} + +pragma[nomagic] +private ConditionBlock ssaIntBoundGuard(SsaVariable v, boolean branch_with_lower_bound_k, int k) { + result.getCondition() = intBoundGuard(v.getAUse(), branch_with_lower_bound_k, k) +} + +pragma[nomagic] +private ConditionBlock ssaEnumConstEquality(SsaVariable v, boolean polarity, EnumConstant c) { + result.getCondition() = enumConstEquality(v.getAUse(), polarity, c) +} + /** A pair of correlated conditions for a given NPE candidate. */ private predicate correlatedConditions( SsaSourceVariable npecand, ConditionBlock cond1, ConditionBlock cond2, boolean inverted @@ -485,25 +500,23 @@ private predicate correlatedConditions( inverted = branch1.booleanXor(branch2) ) or - exists(SsaVariable v, VarRead rv1, VarRead rv2, int k, boolean branch1, boolean branch2 | - rv1 = v.getAUse() and - rv2 = v.getAUse() and - cond1.getCondition() = integerGuard(rv1, branch1, k, true) and - cond1.getCondition() = integerGuard(rv1, branch1.booleanNot(), k, false) and - cond2.getCondition() = integerGuard(rv2, branch2, k, true) and - cond2.getCondition() = integerGuard(rv2, branch2.booleanNot(), k, false) and + exists(SsaVariable v, int k, boolean branch1, boolean branch2 | + cond1 = ssaIntegerGuard(v, branch1, k, true) and + cond1 = ssaIntegerGuard(v, branch1.booleanNot(), k, false) and + cond2 = ssaIntegerGuard(v, branch2, k, true) and + cond2 = ssaIntegerGuard(v, branch2.booleanNot(), k, false) and inverted = branch1.booleanXor(branch2) ) or exists(SsaVariable v, int k, boolean branch1, boolean branch2 | - cond1.getCondition() = intBoundGuard(v.getAUse(), branch1, k) and - cond2.getCondition() = intBoundGuard(v.getAUse(), branch2, k) and + cond1 = ssaIntBoundGuard(v, branch1, k) and + cond2 = ssaIntBoundGuard(v, branch2, k) and inverted = branch1.booleanXor(branch2) ) or exists(SsaVariable v, EnumConstant c, boolean pol1, boolean pol2 | - cond1.getCondition() = enumConstEquality(v.getAUse(), pol1, c) and - cond2.getCondition() = enumConstEquality(v.getAUse(), pol2, c) and + cond1 = ssaEnumConstEquality(v, pol1, c) and + cond2 = ssaEnumConstEquality(v, pol2, c) and inverted = pol1.booleanXor(pol2) ) or diff --git a/java/ql/lib/semmle/code/java/dataflow/SSA.qll b/java/ql/lib/semmle/code/java/dataflow/SSA.qll index ce58e7d58dc1..66be73a4c013 100644 --- a/java/ql/lib/semmle/code/java/dataflow/SSA.qll +++ b/java/ql/lib/semmle/code/java/dataflow/SSA.qll @@ -12,31 +12,7 @@ */ import java -private import semmle.code.java.dispatch.VirtualDispatch -private import semmle.code.java.dispatch.WrappedInvocation - -private predicate fieldAccessInCallable(FieldAccess fa, Field f, Callable c) { - f = fa.getField() and - c = fa.getEnclosingCallable() -} - -cached -private newtype TSsaSourceVariable = - TLocalVar(Callable c, LocalScopeVariable v) { - c = v.getCallable() or c = v.getAnAccess().getEnclosingCallable() - } or - TPlainField(Callable c, Field f) { - exists(FieldRead fr | - fieldAccessInCallable(fr, f, c) and - (fr.isOwnFieldAccess() or f.isStatic()) - ) - } or - TEnclosingField(Callable c, Field f, RefType t) { - exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.isEnclosingFieldAccess(t)) - } or - TQualifiedField(Callable c, SsaSourceVariable q, InstanceField f) { - exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.getQualifier() = q.getAnAccess()) - } +private import internal.SsaImpl /** * A fully qualified variable in the context of a `Callable` in which it is @@ -160,776 +136,22 @@ class SsaSourceField extends SsaSourceVariable { } } -private module TrackedVariablesImpl { - /** Gets the number of accesses of `f`. */ - private int numberOfAccesses(SsaSourceField f) { - result = strictcount(FieldAccess fa | fa = f.getAnAccess()) - } - - /** Holds if `f` is accessed inside a loop. */ - private predicate loopAccessed(SsaSourceField f) { - exists(LoopStmt l, FieldRead fr | fr = f.getAnAccess() | - l.getBody() = fr.getEnclosingStmt().getEnclosingStmt*() or - l.getCondition() = fr.getParent*() or - l.(ForStmt).getAnUpdate() = fr.getParent*() - ) - } - - /** Holds if `f` is accessed more than once or inside a loop. */ - private predicate multiAccessed(SsaSourceField f) { loopAccessed(f) or 1 < numberOfAccesses(f) } - - /** - * Holds if `f` is a field that is interesting as a basis for SSA. - * - * - A field that is read twice is interesting as we want to know whether the - * reads refer to the same value. - * - A field that is both written and read is interesting as we want to know - * whether the read might get the written value. - * - A field that is read in a loop is interesting as we want to know whether - * the value is the same in different iterations (that is, whether the SSA - * definition can be placed outside the loop). - * - A volatile field is never interesting, since all reads must reread from - * memory and we are forced to assume that the value can change at any point. - */ - cached - predicate trackField(SsaSourceField f) { multiAccessed(f) and not f.isVolatile() } - - /** - * The variables that form the basis of the non-trivial SSA construction. - * Fields that aren't tracked get a trivial SSA construction (a definition - * prior to every read). - */ - class TrackedVar extends SsaSourceVariable { - TrackedVar() { - this = TLocalVar(_, _) or - trackField(this) - } - } - - class TrackedField extends TrackedVar, SsaSourceField { } -} - -private import TrackedVariablesImpl - -cached -private module SsaImpl { - /** Gets the destination variable of an update of a tracked variable. */ - cached - TrackedVar getDestVar(VariableUpdate upd) { - result.getAnAccess() = upd.(Assignment).getDest() - or - exists(LocalVariableDecl v | v = upd.(LocalVariableDeclExpr).getVariable() | - result = TLocalVar(v.getCallable(), v) - ) - or - result.getAnAccess() = upd.(UnaryAssignExpr).getExpr() - } - - /** Holds if `n` must update the locally tracked variable `v`. */ - cached - predicate certainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { - exists(VariableUpdate a | a.getControlFlowNode() = n | getDestVar(a) = v) and - b.getNode(i) = n and - hasDominanceInformation(b) - or - certainVariableUpdate(v.getQualifier(), n, b, i) - } - - /** Gets the definition point of a nested class in the parent scope. */ - private ControlFlowNode parentDef(NestedClass nc) { - nc.(AnonymousClass).getClassInstanceExpr().getControlFlowNode() = result or - nc.(LocalClass).getLocalTypeDeclStmt().getControlFlowNode() = result - } - - /** - * Gets the enclosing type of a nested class. - * - * Differs from `RefType.getEnclosingType()` by including anonymous classes defined by lambdas. - */ - private RefType desugaredGetEnclosingType(NestedClass inner) { - exists(ControlFlowNode node | - node = parentDef(inner) and - node.getEnclosingCallable().getDeclaringType() = result - ) - } - - /** - * Gets the control flow node at which the variable is read to get the value for - * a `VarAccess` inside a closure. `capturedvar` is the variable in its defining - * scope, and `closurevar` is the variable in the closure. - */ - private ControlFlowNode captureNode(TrackedVar capturedvar, TrackedVar closurevar) { - exists( - LocalScopeVariable v, Callable inner, Callable outer, NestedClass innerclass, VarAccess va - | - va.getVariable() = v and - inner = va.getEnclosingCallable() and - outer = v.getCallable() and - inner != outer and - inner.getDeclaringType() = innerclass and - result = parentDef(desugaredGetEnclosingType*(innerclass)) and - result.getEnclosingStmt().getEnclosingCallable() = outer and - capturedvar = TLocalVar(outer, v) and - closurevar = TLocalVar(inner, v) - ) - } - - /** Holds if `VarAccess` `use` of `v` occurs in `b` at index `i`. */ - private predicate variableUse(TrackedVar v, VarRead use, BasicBlock b, int i) { - v.getAnAccess() = use and b.getNode(i) = use.getControlFlowNode() - } - - /** Holds if the value of `v` is captured in `b` at index `i`. */ - private predicate variableCapture( - TrackedVar capturedvar, TrackedVar closurevar, BasicBlock b, int i - ) { - b.getNode(i) = captureNode(capturedvar, closurevar) - } - - /** Holds if the value of `v` is read in `b` at index `i`. */ - private predicate variableUseOrCapture(TrackedVar v, BasicBlock b, int i) { - variableUse(v, _, b, i) or variableCapture(v, _, b, i) - } - - /* - * Liveness analysis to restrict the size of the SSA representation. - */ - - private predicate liveAtEntry(TrackedVar v, BasicBlock b) { - exists(int i | variableUseOrCapture(v, b, i) | - not exists(int j | certainVariableUpdate(v, _, b, j) | j < i) - ) - or - liveAtExit(v, b) and not certainVariableUpdate(v, _, b, _) - } - - private predicate liveAtExit(TrackedVar v, BasicBlock b) { liveAtEntry(v, b.getABBSuccessor()) } - - /* - * The SSA construction for a field `f` relies on implicit update nodes at - * every call site that conceivably could reach an update of the field. - * - * At a first approximation we need to find update paths of the form: - * Callable --(callEdge)-->* Callable(setter of f) - * - * This can be improved by excluding paths ending in: - * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f) - * as these updates are guaranteed not to alias with the `f` under - * consideration. - * - * This set of paths can be expressed positively by noting that those - * that set `this.f` end in zero or more `intraInstanceCallEdge`s between - * methods, and before those is either the originating `Call` or a - * `crossInstanceCallEdge`. - */ - - /** - * Holds if `fw` is a field write that is not relevant as an implicit SSA - * update, since it is an initialization and therefore cannot alias. - */ - private predicate init(FieldWrite fw) { - fw.getEnclosingCallable() instanceof InitializerMethod - or - fw.getEnclosingCallable() instanceof Constructor and fw.isOwnFieldAccess() - or - exists(LocalVariableDecl v | - v.getAnAccess() = fw.getQualifier() and - forex(VariableAssign va | va.getDestVar() = v and exists(va.getSource()) | - va.getSource() instanceof ClassInstanceExpr - ) - ) - } - - /** - * Holds if `fw` is an update of `f` in `c` that is relevant for SSA construction. - */ - cached - predicate relevantFieldUpdate(Callable c, Field f, FieldWrite fw) { - fw = f.getAnAccess() and - not init(fw) and - fw.getEnclosingCallable() = c and - exists(TrackedField nf | nf.getField() = f) - } - - /** Holds if `c` can change the value of `this.f` and is relevant for SSA construction. */ - private predicate setsOwnField(Method c, Field f) { - exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and fw.isOwnFieldAccess()) - } - - /** - * Holds if `c` can change the value of `f` and is relevant for SSA - * construction excluding those cases covered by `setsOwnField`. - */ - private predicate setsOtherField(Callable c, Field f) { - exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and not fw.isOwnFieldAccess()) - } - - pragma[nomagic] - private predicate innerclassSupertypeStar(InnerClass t1, RefType t2) { - t1.getASourceSupertype*().getSourceDeclaration() = t2 - } - - /** - * Holds if `(c1,m2)` is a call edge to a method that does not change the value - * of `this`. - * - * Constructor-to-constructor calls can also be intra-instance, but are not - * included, as this does not affect whether a call chain ends in - * - * ``` - * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f) - * ``` - */ - private predicate intraInstanceCallEdge(Callable c1, Method m2) { - exists(MethodCall ma, RefType t1 | - ma.getCaller() = c1 and - m2 = viableImpl_v2(ma) and - not m2.isStatic() and - ( - not exists(ma.getQualifier()) or - ma.getQualifier() instanceof ThisAccess or - ma.getQualifier() instanceof SuperAccess - ) and - c1.getDeclaringType() = t1 and - if t1 instanceof InnerClass - then - innerclassSupertypeStar(t1, ma.getCallee().getSourceDeclaration().getDeclaringType()) and - not exists(ma.getQualifier().(ThisAccess).getQualifier()) and - not exists(ma.getQualifier().(SuperAccess).getQualifier()) - else any() - ) - } - - private Callable tgt(Call c) { - result = viableImpl_v2(c) - or - result = getRunnerTarget(c) - or - c instanceof ConstructorCall and result = c.getCallee().getSourceDeclaration() - } - - /** Holds if `(c1,c2)` is an edge in the call graph. */ - private predicate callEdge(Callable c1, Callable c2) { - exists(Call c | c.getCaller() = c1 and c2 = tgt(c)) - } - - /** Holds if `(c1,c2)` is an edge in the call graph excluding `intraInstanceCallEdge`. */ - private predicate crossInstanceCallEdge(Callable c1, Callable c2) { - callEdge(c1, c2) and not intraInstanceCallEdge(c1, c2) - } - - /** - * Holds if `call` occurs in the same basic block, `b`, as `f` at index `i` and - * `f` has an update somewhere. - */ - private predicate updateCandidate(TrackedField f, Call call, BasicBlock b, int i) { - b.getNode(i).asCall() = call and - call.getEnclosingCallable() = f.getEnclosingCallable() and - relevantFieldUpdate(_, f.getField(), _) - } - - /** - * Holds if `rankix` is the rank of index `i` at which there is a use, a - * certain update, or a potential update of `f` in the basic block `b`. - * - * Basic block indices are translated to rank indices in order to skip - * irrelevant indices at which there is update or use when traversing - * basic blocks. - */ - private predicate callDefUseRank(TrackedField f, BasicBlock b, int rankix, int i) { - updateCandidate(f, _, b, _) and - i = - rank[rankix](int j | - certainVariableUpdate(f, _, b, j) or - variableUseOrCapture(f, b, j) or - updateCandidate(f, _, b, j) - ) - } - - /** - * Holds if `f` is live in `b` at index `i`. The rank of `i` is `rankix` as - * defined by `callDefUseRank`. - */ - private predicate liveAtRank(TrackedField f, BasicBlock b, int rankix, int i) { - callDefUseRank(f, b, rankix, i) and - ( - rankix = max(int rix | callDefUseRank(f, b, rix, _)) and liveAtExit(f, b) - or - variableUseOrCapture(f, b, i) - or - exists(int j | liveAtRank(f, b, rankix + 1, j) and not certainVariableUpdate(f, _, b, j)) - ) - } - - /** - * Holds if `call` is relevant as a potential update of `f`. This requires the - * existence of an update to `f` somewhere and that `f` is live at `call`. - */ - private predicate relevantCall(Call call, TrackedField f) { - exists(BasicBlock b, int i | - updateCandidate(f, call, b, i) and - liveAtRank(f, b, _, i) - ) - } - - private predicate source(Call call, TrackedField f, Field field, Callable c, boolean fresh) { - relevantCall(call, f) and - field = f.getField() and - c = tgt(call) and - if c instanceof Constructor then fresh = true else fresh = false - } - - /** - * A callable in a potential call-chain between a source that cares about the - * value of some field `f` and a sink that may overwrite `f`. The boolean - * `fresh` indicates whether the instance `this` in `c` has been freshly - * allocated along the call-chain. - */ - private newtype TCallableNode = - MkCallableNode(Callable c, boolean fresh) { source(_, _, _, c, fresh) or edge(_, c, fresh) } - - private predicate edge(TCallableNode n, Callable c2, boolean f2) { - exists(Callable c1, boolean f1 | n = MkCallableNode(c1, f1) | - intraInstanceCallEdge(c1, c2) and f2 = f1 - or - crossInstanceCallEdge(c1, c2) and - if c2 instanceof Constructor then f2 = true else f2 = false - ) - } - - private predicate edge(TCallableNode n1, TCallableNode n2) { - exists(Callable c2, boolean f2 | - edge(n1, c2, f2) and - n2 = MkCallableNode(c2, f2) - ) - } - - pragma[noinline] - private predicate source(Call call, TrackedField f, Field field, TCallableNode n) { - exists(Callable c, boolean fresh | - source(call, f, field, c, fresh) and - n = MkCallableNode(c, fresh) - ) - } - - private predicate sink(Callable c, Field f, TCallableNode n) { - setsOwnField(c, f) and n = MkCallableNode(c, false) - or - setsOtherField(c, f) and n = MkCallableNode(c, _) - } - - private predicate prunedNode(TCallableNode n) { - sink(_, _, n) - or - exists(TCallableNode mid | edge(n, mid) and prunedNode(mid)) - } - - private predicate prunedEdge(TCallableNode n1, TCallableNode n2) { - prunedNode(n1) and - prunedNode(n2) and - edge(n1, n2) - } - - private predicate edgePlus(TCallableNode c1, TCallableNode c2) = fastTC(prunedEdge/2)(c1, c2) - - /** - * Holds if there exists a call-chain originating in `call` that can update `f` on some instance - * where `f` and `call` share the same enclosing callable in which a - * `FieldRead` of `f` is reachable from `call`. - */ - pragma[noopt] - cached - predicate updatesNamedField(Call call, TrackedField f, Callable setter) { - exists(TCallableNode src, TCallableNode sink, Field field | - source(call, f, field, src) and - sink(setter, field, sink) and - (src = sink or edgePlus(src, sink)) - ) - } - - /** Holds if `n` might update the locally tracked variable `v`. */ - cached - predicate uncertainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { - exists(Call c | c = n.asCall() | updatesNamedField(c, v, _)) and - b.getNode(i) = n and - hasDominanceInformation(b) - or - uncertainVariableUpdate(v.getQualifier(), n, b, i) - } - - /** Holds if `n` updates the locally tracked variable `v`. */ - private predicate variableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { - certainVariableUpdate(v, n, b, i) or uncertainVariableUpdate(v, n, b, i) - } - - /** Holds if a phi node for `v` is needed at the beginning of basic block `b`. */ - cached - predicate phiNode(TrackedVar v, BasicBlock b) { - liveAtEntry(v, b) and - exists(BasicBlock def | dominanceFrontier(def, b) | - variableUpdate(v, _, def, _) or phiNode(v, def) - ) - } - - /** Holds if `v` has an implicit definition at the entry, `b`, of the callable. */ - cached - predicate hasEntryDef(TrackedVar v, BasicBlock b) { - exists(LocalScopeVariable l, Callable c | - v = TLocalVar(c, l) and c.getBody().getControlFlowNode() = b - | - l instanceof Parameter or - l.getCallable() != c - ) - or - v instanceof SsaSourceField and - v.getEnclosingCallable().getBody().getControlFlowNode() = b and - liveAtEntry(v, b) - } - - /** - * The construction of SSA form ensures that each use of a variable is - * dominated by its definition. A definition of an SSA variable therefore - * reaches a `ControlFlowNode` if it is the _closest_ SSA variable definition - * that dominates the node. If two definitions dominate a node then one must - * dominate the other, so therefore the definition of _closest_ is given by the - * dominator tree. Thus, reaching definitions can be calculated in terms of - * dominance. - */ - cached - module SsaDefReaches { - /** - * Holds if `rankix` is the rank the index `i` at which there is an SSA definition or use of - * `v` in the basic block `b`. - * - * Basic block indices are translated to rank indices in order to skip - * irrelevant indices at which there is no definition or use when traversing - * basic blocks. - */ - private predicate defUseRank(TrackedVar v, BasicBlock b, int rankix, int i) { - i = - rank[rankix](int j | - any(TrackedSsaDef def).definesAt(v, b, j) or variableUseOrCapture(v, b, j) - ) - } - - /** Gets the maximum rank index for the given variable and basic block. */ - private int lastRank(TrackedVar v, BasicBlock b) { - result = max(int rankix | defUseRank(v, b, rankix, _)) - } - - /** Holds if a definition of an SSA variable occurs at the specified rank index in basic block `b`. */ - private predicate ssaDefRank(TrackedVar v, TrackedSsaDef def, BasicBlock b, int rankix) { - exists(int i | - def.definesAt(v, b, i) and - defUseRank(v, b, rankix, i) - ) - } - - /** Holds if the SSA definition reaches the rank index `rankix` in its own basic block `b`. */ - private predicate ssaDefReachesRank(TrackedVar v, TrackedSsaDef def, BasicBlock b, int rankix) { - ssaDefRank(v, def, b, rankix) - or - ssaDefReachesRank(v, def, b, rankix - 1) and - rankix <= lastRank(v, b) and - not ssaDefRank(v, _, b, rankix) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches the end of a basic block `b`, at - * which point it is still live, without crossing another SSA definition of `v`. - */ - cached - predicate ssaDefReachesEndOfBlock(TrackedVar v, TrackedSsaDef def, BasicBlock b) { - liveAtExit(v, b) and - ( - ssaDefReachesRank(v, def, b, lastRank(v, b)) - or - exists(BasicBlock idom | - bbIDominates(pragma[only_bind_into](idom), b) and // It is sufficient to traverse the dominator graph, cf. discussion above. - ssaDefReachesEndOfBlock(v, def, idom) and - not any(TrackedSsaDef other).definesAt(v, b, _) - ) - ) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches `use` in the same basic block - * without crossing another SSA definition of `v`. - */ - private predicate ssaDefReachesUseWithinBlock(TrackedVar v, TrackedSsaDef def, VarRead use) { - exists(BasicBlock b, int rankix, int i | - ssaDefReachesRank(v, def, b, rankix) and - defUseRank(v, b, rankix, i) and - variableUse(v, use, b, i) - ) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches `use` without crossing another - * SSA definition of `v`. - */ - cached - predicate ssaDefReachesUse(TrackedVar v, TrackedSsaDef def, VarRead use) { - ssaDefReachesUseWithinBlock(v, def, use) - or - exists(BasicBlock b | - variableUse(v, use, b, _) and - ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and - not ssaDefReachesUseWithinBlock(v, _, use) - ) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches the capture point of - * `closurevar` in the same basic block without crossing another SSA - * definition of `v`. - */ - private predicate ssaDefReachesCaptureWithinBlock( - TrackedVar v, TrackedSsaDef def, TrackedVar closurevar - ) { - exists(BasicBlock b, int rankix, int i | - ssaDefReachesRank(v, def, b, rankix) and - defUseRank(v, b, rankix, i) and - variableCapture(v, closurevar, b, i) - ) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches capture point of - * `closurevar` without crossing another SSA definition of `v`. - */ - cached - predicate ssaDefReachesCapture(TrackedVar v, TrackedSsaDef def, TrackedVar closurevar) { - ssaDefReachesCaptureWithinBlock(v, def, closurevar) - or - exists(BasicBlock b | - variableCapture(v, closurevar, b, _) and - ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and - not ssaDefReachesCaptureWithinBlock(v, _, closurevar) - ) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches `redef` in the same basic block - * without crossing another SSA definition of `v`. - */ - private predicate ssaDefReachesUncertainDefWithinBlock( - TrackedVar v, TrackedSsaDef def, SsaUncertainImplicitUpdate redef - ) { - exists(BasicBlock b, int rankix, int i | - ssaDefReachesRank(v, def, b, rankix) and - defUseRank(v, b, rankix + 1, i) and - redef.(TrackedSsaDef).definesAt(v, b, i) - ) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches `redef` without crossing another - * SSA definition of `v`. - */ - cached - predicate ssaDefReachesUncertainDef( - TrackedVar v, TrackedSsaDef def, SsaUncertainImplicitUpdate redef - ) { - ssaDefReachesUncertainDefWithinBlock(v, def, redef) - or - exists(BasicBlock b | - redef.(TrackedSsaDef).definesAt(v, b, _) and - ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and - not ssaDefReachesUncertainDefWithinBlock(v, _, redef) - ) - } - } - - private module AdjacentUsesImpl { - /** - * Holds if `rankix` is the rank the index `i` at which there is an SSA definition or explicit use of - * `v` in the basic block `b`. - */ - private predicate defUseRank(TrackedVar v, BasicBlock b, int rankix, int i) { - i = rank[rankix](int j | any(TrackedSsaDef def).definesAt(v, b, j) or variableUse(v, _, b, j)) - } - - /** Gets the maximum rank index for the given variable and basic block. */ - private int lastRank(TrackedVar v, BasicBlock b) { - result = max(int rankix | defUseRank(v, b, rankix, _)) - } - - /** Holds if `v` is defined or used in `b`. */ - private predicate varOccursInBlock(TrackedVar v, BasicBlock b) { defUseRank(v, b, _, _) } - - /** Holds if `v` occurs in `b` or one of `b`'s transitive successors. */ - private predicate blockPrecedesVar(TrackedVar v, BasicBlock b) { - varOccursInBlock(v, b) - or - ssaDefReachesEndOfBlock(v, _, b) - } - - /** - * Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and - * in `b2` or one of its transitive successors but not in any block on the path - * between `b1` and `b2`. - */ - private predicate varBlockReaches(TrackedVar v, BasicBlock b1, BasicBlock b2) { - varOccursInBlock(v, b1) and - pragma[only_bind_into](b2) = b1.getABBSuccessor() and - blockPrecedesVar(v, b2) - or - exists(BasicBlock mid | - varBlockReaches(v, b1, mid) and - pragma[only_bind_into](b2) = mid.getABBSuccessor() and - not varOccursInBlock(v, mid) and - blockPrecedesVar(v, b2) - ) - } - - /** - * Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and - * `b2` but not in any block on the path between `b1` and `b2`. - */ - private predicate varBlockStep(TrackedVar v, BasicBlock b1, BasicBlock b2) { - varBlockReaches(v, b1, b2) and - varOccursInBlock(v, b2) - } - - /** - * Holds if `v` occurs at index `i1` in `b1` and at index `i2` in `b2` and - * there is a path between them without any occurrence of `v`. - */ - pragma[nomagic] - predicate adjacentVarRefs(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2) { - exists(int rankix | - b1 = b2 and - defUseRank(v, b1, rankix, i1) and - defUseRank(v, b2, rankix + 1, i2) - ) - or - defUseRank(v, b1, lastRank(v, b1), i1) and - varBlockStep(v, b1, b2) and - defUseRank(v, b2, 1, i2) - } - } - - private import AdjacentUsesImpl - - /** - * Holds if the value defined at `def` can reach `use` without passing through - * any other uses, but possibly through phi nodes and uncertain implicit updates. - */ - cached - predicate firstUse(TrackedSsaDef def, VarRead use) { - exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 | - adjacentVarRefs(v, b1, i1, b2, i2) and - def.definesAt(v, b1, i1) and - variableUse(v, use, b2, i2) - ) - or - exists(TrackedVar v, TrackedSsaDef redef, BasicBlock b1, int i1, BasicBlock b2, int i2 | - redef instanceof SsaUncertainImplicitUpdate or redef instanceof SsaPhiNode - | - adjacentVarRefs(v, b1, i1, b2, i2) and - def.definesAt(v, b1, i1) and - redef.definesAt(v, b2, i2) and - firstUse(redef, use) - ) - } - - cached - module SsaPublic { - /** - * Holds if `use1` and `use2` form an adjacent use-use-pair of the same SSA - * variable, that is, the value read in `use1` can reach `use2` without passing - * through any other use or any SSA definition of the variable. - */ - cached - predicate adjacentUseUseSameVar(VarRead use1, VarRead use2) { - exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 | - adjacentVarRefs(v, b1, i1, b2, i2) and - variableUse(v, use1, b1, i1) and - variableUse(v, use2, b2, i2) - ) - } - - /** - * Holds if `use1` and `use2` form an adjacent use-use-pair of the same - * `SsaSourceVariable`, that is, the value read in `use1` can reach `use2` - * without passing through any other use or any SSA definition of the variable - * except for phi nodes and uncertain implicit updates. - */ - cached - predicate adjacentUseUse(VarRead use1, VarRead use2) { - adjacentUseUseSameVar(use1, use2) - or - exists(TrackedVar v, TrackedSsaDef def, BasicBlock b1, int i1, BasicBlock b2, int i2 | - adjacentVarRefs(v, b1, i1, b2, i2) and - variableUse(v, use1, b1, i1) and - def.definesAt(v, b2, i2) and - firstUse(def, use2) and - (def instanceof SsaUncertainImplicitUpdate or def instanceof SsaPhiNode) - ) - } - } -} - -private import SsaImpl -private import SsaDefReaches -import SsaPublic - -cached -private newtype TSsaVariable = - TSsaPhiNode(TrackedVar v, BasicBlock b) { phiNode(v, b) } or - TSsaCertainUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { - certainVariableUpdate(v, n, b, i) - } or - TSsaUncertainUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { - uncertainVariableUpdate(v, n, b, i) - } or - TSsaEntryDef(TrackedVar v, BasicBlock b) { hasEntryDef(v, b) } or - TSsaUntracked(SsaSourceField nf, ControlFlowNode n) { - n = nf.getAnAccess().(FieldRead).getControlFlowNode() and not trackField(nf) - } - -/** - * An SSA definition excluding those variables that use a trivial SSA construction. - */ -private class TrackedSsaDef extends SsaVariable { - TrackedSsaDef() { not this = TSsaUntracked(_, _) } - - /** - * Holds if this SSA definition occurs at the specified position. - * Phi nodes are placed at index -1. - */ - predicate definesAt(TrackedVar v, BasicBlock b, int i) { - this = TSsaPhiNode(v, b) and i = -1 - or - this = TSsaCertainUpdate(v, _, b, i) - or - this = TSsaUncertainUpdate(v, _, b, i) - or - this = TSsaEntryDef(v, b) and i = 0 - } -} - /** * An SSA variable. */ -class SsaVariable extends TSsaVariable { +class SsaVariable extends Definition { /** Gets the SSA source variable underlying this SSA variable. */ - SsaSourceVariable getSourceVariable() { - this = TSsaPhiNode(result, _) or - this = TSsaCertainUpdate(result, _, _, _) or - this = TSsaUncertainUpdate(result, _, _, _) or - this = TSsaEntryDef(result, _) or - this = TSsaUntracked(result, _) - } + SsaSourceVariable getSourceVariable() { result = super.getSourceVariable() } /** Gets the `ControlFlowNode` at which this SSA variable is defined. */ ControlFlowNode getCfgNode() { - this = TSsaPhiNode(_, result) or - this = TSsaCertainUpdate(_, result, _, _) or - this = TSsaUncertainUpdate(_, result, _, _) or - this = TSsaEntryDef(_, result) or - this = TSsaUntracked(_, result) + exists(BasicBlock bb, int i, int j | + this.definesAt(_, bb, i) and + // untracked definitions are inserted just before reads + (if this instanceof UntrackedDef then j = i + 1 else j = i) and + // phi nodes are inserted at position `-1` + result = bb.getNode(max([j, 0])) + ) } /** Gets a textual representation of this SSA variable. */ @@ -939,13 +161,10 @@ class SsaVariable extends TSsaVariable { Location getLocation() { result = this.getCfgNode().getLocation() } /** Gets the `BasicBlock` in which this SSA variable is defined. */ - BasicBlock getBasicBlock() { result = this.getCfgNode().getBasicBlock() } + BasicBlock getBasicBlock() { result = super.getBasicBlock() } /** Gets an access of this SSA variable. */ - VarRead getAUse() { - ssaDefReachesUse(_, this, result) or - this = TSsaUntracked(_, result.getControlFlowNode()) - } + VarRead getAUse() { result = getAUse(this) } /** * Gets an access of the SSA source variable underlying this SSA variable @@ -956,13 +175,10 @@ class SsaVariable extends TSsaVariable { * Subsequent uses can be found by following the steps defined by * `adjacentUseUse`. */ - VarRead getAFirstUse() { - firstUse(this, result) or - this = TSsaUntracked(_, result.getControlFlowNode()) - } + VarRead getAFirstUse() { firstUse(this, result) } /** Holds if this SSA variable is live at the end of `b`. */ - predicate isLiveAtEndOfBlock(BasicBlock b) { ssaDefReachesEndOfBlock(_, this, b) } + predicate isLiveAtEndOfBlock(BasicBlock b) { ssaDefReachesEndOfBlock(b, this) } /** * Gets an SSA variable whose value can flow to this one in one step. This @@ -982,29 +198,20 @@ class SsaVariable extends TSsaVariable { } /** An SSA variable that either explicitly or implicitly updates the variable. */ -class SsaUpdate extends SsaVariable { - SsaUpdate() { - this = TSsaCertainUpdate(_, _, _, _) or - this = TSsaUncertainUpdate(_, _, _, _) or - this = TSsaUntracked(_, _) - } +class SsaUpdate extends SsaVariable instanceof WriteDefinition { + SsaUpdate() { not this instanceof SsaImplicitInit } } /** An SSA variable that is defined by a `VariableUpdate`. */ -class SsaExplicitUpdate extends SsaUpdate, TSsaCertainUpdate { - SsaExplicitUpdate() { - exists(VariableUpdate upd | - upd.getControlFlowNode() = this.getCfgNode() and getDestVar(upd) = this.getSourceVariable() - ) - } +class SsaExplicitUpdate extends SsaUpdate { + private VariableUpdate upd; + + SsaExplicitUpdate() { ssaExplicitUpdate(this, upd) } override string toString() { result = "SSA def(" + this.getSourceVariable() + ")" } /** Gets the `VariableUpdate` defining the SSA variable. */ - VariableUpdate getDefiningExpr() { - result.getControlFlowNode() = this.getCfgNode() and - getDestVar(result) = this.getSourceVariable() - } + VariableUpdate getDefiningExpr() { result = upd } } /** @@ -1020,13 +227,27 @@ class SsaImplicitUpdate extends SsaUpdate { result = "SSA impl upd[" + this.getKind() + "](" + this.getSourceVariable() + ")" } + private predicate hasExplicitQualifierUpdate() { + exists(SsaExplicitUpdate qdef, BasicBlock bb, int i | + qdef.definesAt(this.getSourceVariable().getQualifier(), bb, i) and + this.definesAt(_, bb, i) + ) + } + + private predicate hasImplicitQualifierUpdate() { + exists(SsaUncertainImplicitUpdate qdef, BasicBlock bb, int i | + qdef.definesAt(this.getSourceVariable().getQualifier(), bb, i) and + this.definesAt(_, bb, i) + ) + } + private string getKind() { - this = TSsaUntracked(_, _) and result = "untracked" + this instanceof UntrackedDef and result = "untracked" or - certainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCfgNode(), _, _) and + this.hasExplicitQualifierUpdate() and result = "explicit qualifier" or - if uncertainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCfgNode(), _, _) + if this.hasImplicitQualifierUpdate() then if exists(this.getANonLocalUpdate()) then result = "nonlocal + nonlocal qualifier" @@ -1041,9 +262,8 @@ class SsaImplicitUpdate extends SsaUpdate { */ FieldWrite getANonLocalUpdate() { exists(SsaSourceField f, Callable setter | - f = this.getSourceVariable() and relevantFieldUpdate(setter, f.getField(), result) and - updatesNamedField(this.getCfgNode().asCall(), f, setter) + defUpdatesNamedField(this, f, setter) ) } @@ -1055,9 +275,11 @@ class SsaImplicitUpdate extends SsaUpdate { * of its qualifiers is volatile. */ predicate assignsUnknownValue() { - this = TSsaUntracked(_, _) or - certainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCfgNode(), _, _) or - uncertainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCfgNode(), _, _) + this instanceof UntrackedDef + or + this.hasExplicitQualifierUpdate() + or + this.hasImplicitQualifierUpdate() } } @@ -1066,25 +288,27 @@ class SsaImplicitUpdate extends SsaUpdate { * This is a `Call` that might reach a non-local update of the field or one of * its qualifiers. */ -class SsaUncertainImplicitUpdate extends SsaImplicitUpdate, TSsaUncertainUpdate { +class SsaUncertainImplicitUpdate extends SsaImplicitUpdate { + SsaUncertainImplicitUpdate() { ssaUncertainImplicitUpdate(this) } + /** * Gets the immediately preceding definition. Since this update is uncertain * the value from the preceding definition might still be valid. */ - SsaVariable getPriorDef() { ssaDefReachesUncertainDef(_, result, this) } + SsaVariable getPriorDef() { ssaDefReachesUncertainDef(result, this) } } /** * An SSA variable that is defined by its initial value in the callable. This * includes initial values of parameters, fields, and closure variables. */ -class SsaImplicitInit extends SsaVariable, TSsaEntryDef { +class SsaImplicitInit extends SsaVariable instanceof WriteDefinition { + SsaImplicitInit() { ssaImplicitInit(this) } + override string toString() { result = "SSA init(" + this.getSourceVariable() + ")" } /** Holds if this is a closure variable that captures the value of `capturedvar`. */ - predicate captures(SsaVariable capturedvar) { - ssaDefReachesCapture(_, capturedvar, this.getSourceVariable()) - } + predicate captures(SsaVariable capturedvar) { captures(this, capturedvar) } /** * Holds if the SSA variable is a parameter defined by its initial value in the callable. @@ -1096,23 +320,15 @@ class SsaImplicitInit extends SsaVariable, TSsaEntryDef { } /** An SSA phi node. */ -class SsaPhiNode extends SsaVariable, TSsaPhiNode { +class SsaPhiNode extends SsaVariable instanceof PhiNode { override string toString() { result = "SSA phi(" + this.getSourceVariable() + ")" } /** Gets an input to the phi node defining the SSA variable. */ - SsaVariable getAPhiInput() { - exists(BasicBlock phiPred, TrackedVar v | - v = this.getSourceVariable() and - this.getCfgNode().(BasicBlock).getABBPredecessor() = phiPred and - ssaDefReachesEndOfBlock(v, result, phiPred) - ) - } + SsaVariable getAPhiInput() { this.hasInputFromBlock(result, _) } /** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */ predicate hasInputFromBlock(SsaVariable inp, BasicBlock bb) { - this.getAPhiInput() = inp and - this.getBasicBlock().getABBPredecessor() = bb and - inp.isLiveAtEndOfBlock(bb) + phiHasInputFromBlock(this, inp, bb) } } @@ -1137,3 +353,5 @@ Expr sameValue(SsaVariable v, VarAccess va) { or result.(RefTypeCastingExpr).getExpr() = sameValue(v, va) } + +import SsaPublic diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll index 073d87d9744c..cf62fed552b3 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll @@ -57,7 +57,7 @@ class BaseSsaSourceVariable extends TBaseSsaSourceVariable { } cached -private module SsaImpl { +private module BaseSsaImpl { /** Gets the destination variable of an update of a tracked variable. */ cached BaseSsaSourceVariable getDestVar(VariableUpdate upd) { @@ -440,7 +440,7 @@ private module SsaImpl { } } -private import SsaImpl +private import BaseSsaImpl private import SsaDefReaches import SsaPublic diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll index bf867d21d3cc..df9a487a2c1f 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll @@ -10,6 +10,8 @@ private import FlowSummaryImpl as FlowSummaryImpl private import DataFlowImplCommon as DataFlowImplCommon private import semmle.code.java.controlflow.Guards private import semmle.code.java.dataflow.RangeUtils +private import semmle.code.java.dataflow.SSA +private import SsaImpl as SsaImpl /** Gets a string for approximating the name of a field. */ string approximateFieldContent(FieldContent fc) { result = fc.getField().getName().prefix(1) } @@ -21,8 +23,34 @@ private predicate deadcode(Expr e) { ) } +module SsaFlow { + module Impl = SsaImpl::DataFlowIntegration; + + Impl::Node asNode(Node n) { + n = TSsaNode(result) + or + result.(Impl::ExprNode).getExpr() = n.asExpr() + or + result.(Impl::ExprPostUpdateNode).getExpr() = n.(PostUpdateNode).getPreUpdateNode().asExpr() + or + TExplicitParameterNode(result.(Impl::ParameterNode).getParameter()) = n + } + + predicate localFlowStep( + SsaImpl::Impl::DefinitionExt def, Node nodeFrom, Node nodeTo, boolean isUseStep + ) { + Impl::localFlowStep(def, asNode(nodeFrom), asNode(nodeTo), isUseStep) + } + + predicate localMustFlowStep(SsaImpl::Impl::DefinitionExt def, Node nodeFrom, Node nodeTo) { + Impl::localMustFlowStep(def, asNode(nodeFrom), asNode(nodeTo)) + } +} + cached private module Cached { + private import semmle.code.java.controlflow.internal.GuardsLogic as GuardsLogic + cached newtype TNode = TExprNode(Expr e) { @@ -31,6 +59,7 @@ private module Cached { not e.getType() instanceof VoidType and not e.getParent*() instanceof Annotation } or + TSsaNode(SsaFlow::Impl::SsaNode node) or TExplicitParameterNode(Parameter p) { exists(p.getCallable().getBody()) } or TImplicitVarargsArray(Call c) { c.getCallee().isVarargs() and @@ -137,6 +166,8 @@ module Public { result = this.(FieldValueNode).getField().getType() or result instanceof TypeObject and this instanceof AdditionalNode + or + result = this.(SsaNode).getDefinitionExt().getSourceVariable().getType() } /** Gets the callable in which this node occurs. */ @@ -358,6 +389,18 @@ module Public { private import Public +class SsaNode extends Node, TSsaNode { + private SsaFlow::Impl::SsaNode node; + + SsaNode() { this = TSsaNode(node) } + + SsaImpl::Impl::DefinitionExt getDefinitionExt() { result = node.getDefinitionExt() } + + override Location getLocation() { result = node.getLocation() } + + override string toString() { result = node.toString() } +} + private class NewExpr extends PostUpdateNode, TExprNode { NewExpr() { exists(ClassInstanceExpr cie | this = TExprNode(cie)) } @@ -398,7 +441,8 @@ module Private { result.asSummarizedCallable() = n.(FlowSummaryNode).getSummarizedCallable() or result.asCallable() = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() or result.asFieldScope() = n.(FieldValueNode).getField() or - result.asCallable() = any(Expr e | n.(AdditionalNode).nodeAt(e, _)).getEnclosingCallable() + result.asCallable() = any(Expr e | n.(AdditionalNode).nodeAt(e, _)).getEnclosingCallable() or + result.asCallable() = n.(SsaNode).getDefinitionExt().getBasicBlock().getEnclosingCallable() } /** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */ diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 704e5714784c..d1df53a8a859 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -581,7 +581,11 @@ predicate forceHighPrecision(Content c) { } /** Holds if `n` should be hidden from path explanations. */ -predicate nodeIsHidden(Node n) { n instanceof FlowSummaryNode } +predicate nodeIsHidden(Node n) { + n instanceof FlowSummaryNode + or + n instanceof SsaNode +} class LambdaCallKind = Method; // the "apply" method in the functional interface diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index c66af2c78c5c..cc03f227151d 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -10,6 +10,7 @@ private import semmle.code.java.dataflow.ExternalFlow private import semmle.code.java.dataflow.FlowSteps private import semmle.code.java.dataflow.FlowSummary private import semmle.code.java.dataflow.InstanceAccess +private import semmle.code.java.dataflow.internal.SsaImpl as SsaImpl private import FlowSummaryImpl as FlowSummaryImpl private import TaintTrackingUtil as TaintTrackingUtil private import DataFlowNodes @@ -101,6 +102,10 @@ predicate hasNonlocalValue(FieldRead fr) { ) } +private predicate capturedVariableRead(Node n) { + n.asExpr().(VarRead).getVariable() instanceof CapturedVariable +} + cached private module Cached { /** @@ -110,7 +115,7 @@ private module Cached { predicate localFlowStep(Node node1, Node node2) { simpleLocalFlowStep0(node1, node2, _) or - adjacentUseUse(node1.asExpr(), node2.asExpr()) + SsaFlow::localFlowStep(_, node1, node2, _) or // Simple flow through library code is included in the exposed local // step relation, even though flow is technically inter-procedural @@ -127,6 +132,20 @@ private module Cached { predicate simpleLocalFlowStep(Node node1, Node node2, string model) { simpleLocalFlowStep0(node1, node2, model) or + exists(boolean isUseStep | + SsaFlow::localFlowStep(_, node1, node2, isUseStep) and + not capturedVariableRead(node2) and + model = "" + | + isUseStep = false + or + isUseStep = true and + not exists(FieldRead fr | + hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr() + ) and + not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1, _) + ) + or any(AdditionalValueStep a).step(node1, node2) and pragma[only_bind_out](node1.getEnclosingCallable()) = pragma[only_bind_out](node2.getEnclosingCallable()) and @@ -149,14 +168,7 @@ predicate localMustFlowStep(Node node1, Node node2) { node2.(ImplicitInstanceAccess).getInstanceAccess().(OwnInstanceAccess).getEnclosingCallable() ) or - exists(SsaImplicitInit init | - init.isParameterDefinition(node1.asParameter()) and init.getAUse() = node2.asExpr() - ) - or - exists(SsaExplicitUpdate upd | - upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() and - upd.getAUse() = node2.asExpr() - ) + SsaFlow::localMustFlowStep(_, node1, node2) or node2.asExpr().(CastingExpr).getExpr() = node1.asExpr() or @@ -168,10 +180,6 @@ predicate localMustFlowStep(Node node1, Node node2) { import Cached -private predicate capturedVariableRead(Node n) { - n.asExpr().(VarRead).getVariable() instanceof CapturedVariable -} - /** * Holds if there is a data flow step from `e1` to `e2` that only steps from * child to parent in the AST. @@ -213,34 +221,8 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) { private predicate simpleLocalFlowStep0(Node node1, Node node2, string model) { ( TaintTrackingUtil::forceCachingInSameStage() and - // Variable flow steps through adjacent def-use and use-use pairs. - exists(SsaExplicitUpdate upd | - upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or - upd.getDefiningExpr().(AssignOp) = node1.asExpr() or - upd.getDefiningExpr().(RecordBindingVariableExpr) = node1.asExpr() - | - node2.asExpr() = upd.getAFirstUse() and - not capturedVariableRead(node2) - ) - or - exists(SsaImplicitInit init | - init.isParameterDefinition(node1.asParameter()) and - node2.asExpr() = init.getAFirstUse() and - not capturedVariableRead(node2) - ) - or - adjacentUseUse(node1.asExpr(), node2.asExpr()) and - not exists(FieldRead fr | - hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr() - ) and - not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1, _) and - not capturedVariableRead(node2) - or ThisFlow::adjacentThisRefs(node1, node2) or - adjacentUseUse(node1.(PostUpdateNode).getPreUpdateNode().asExpr(), node2.asExpr()) and - not capturedVariableRead(node2) - or ThisFlow::adjacentThisRefs(node1.(PostUpdateNode).getPreUpdateNode(), node2) or simpleAstFlowStep(node1.asExpr(), node2.asExpr()) @@ -404,11 +386,7 @@ signature predicate guardChecksSig(Guard g, Expr e, boolean branch); module BarrierGuard { /** Gets a node that is safely guarded by the given guard check. */ Node getABarrierNode() { - exists(Guard g, SsaVariable v, boolean branch, VarRead use | - guardChecks(g, v.getAUse(), branch) and - use = v.getAUse() and - g.controls(use.getBasicBlock(), branch) and - result.asExpr() = use - ) + SsaFlow::asNode(result) = + SsaImpl::DataFlowIntegration::BarrierGuard::getABarrierNode() } } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll new file mode 100644 index 000000000000..ac231531a98d --- /dev/null +++ b/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll @@ -0,0 +1,745 @@ +import java +private import codeql.ssa.Ssa as SsaImplCommon +private import semmle.code.java.dataflow.SSA +private import semmle.code.java.dispatch.VirtualDispatch +private import semmle.code.java.dispatch.WrappedInvocation +private import semmle.code.java.controlflow.Guards as Guards + +predicate fieldAccessInCallable(FieldAccess fa, Field f, Callable c) { + f = fa.getField() and + c = fa.getEnclosingCallable() +} + +cached +newtype TSsaSourceVariable = + TLocalVar(Callable c, LocalScopeVariable v) { + c = v.getCallable() or c = v.getAnAccess().getEnclosingCallable() + } or + TPlainField(Callable c, Field f) { + exists(FieldRead fr | + fieldAccessInCallable(fr, f, c) and + (fr.isOwnFieldAccess() or f.isStatic()) + ) + } or + TEnclosingField(Callable c, Field f, RefType t) { + exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.isEnclosingFieldAccess(t)) + } or + TQualifiedField(Callable c, SsaSourceVariable q, InstanceField f) { + exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.getQualifier() = q.getAnAccess()) + } + +private module TrackedVariablesImpl { + /** Gets the number of accesses of `f`. */ + private int numberOfAccesses(SsaSourceField f) { + result = strictcount(FieldAccess fa | fa = f.getAnAccess()) + } + + /** Holds if `f` is accessed inside a loop. */ + private predicate loopAccessed(SsaSourceField f) { + exists(LoopStmt l, FieldRead fr | fr = f.getAnAccess() | + l.getBody() = fr.getEnclosingStmt().getEnclosingStmt*() or + l.getCondition() = fr.getParent*() or + l.(ForStmt).getAnUpdate() = fr.getParent*() + ) + } + + /** Holds if `f` is accessed more than once or inside a loop. */ + predicate multiAccessed(SsaSourceField f) { loopAccessed(f) or 1 < numberOfAccesses(f) } + + /** + * Holds if `f` is a field that is interesting as a basis for SSA. + * + * - A field that is read twice is interesting as we want to know whether the + * reads refer to the same value. + * - A field that is both written and read is interesting as we want to know + * whether the read might get the written value. + * - A field that is read in a loop is interesting as we want to know whether + * the value is the same in different iterations (that is, whether the SSA + * definition can be placed outside the loop). + * - A volatile field is never interesting, since all reads must reread from + * memory and we are forced to assume that the value can change at any point. + */ + cached + predicate trackField(SsaSourceField f) { multiAccessed(f) and not f.isVolatile() } + + /** + * The variables that form the basis of the non-trivial SSA construction. + * Fields that aren't tracked get a trivial SSA construction (a definition + * prior to every read). + */ + class TrackedVar extends SsaSourceVariable { + TrackedVar() { + this = TLocalVar(_, _) or + trackField(this) + } + } + + class TrackedField extends TrackedVar, SsaSourceField { } +} + +private import TrackedVariablesImpl + +private predicate untrackedFieldWrite(BasicBlock bb, int i, SsaSourceVariable v) { + v = + any(SsaSourceField nf | + bb.getNode(i + 1) = nf.getAnAccess().(FieldRead).getControlFlowNode() and not trackField(nf) + ) +} + +/** Gets the definition point of a nested class in the parent scope. */ +private ControlFlowNode parentDef(NestedClass nc) { + nc.(AnonymousClass).getClassInstanceExpr().getControlFlowNode() = result or + nc.(LocalClass).getLocalTypeDeclStmt().getControlFlowNode() = result +} + +/** + * Gets the enclosing type of a nested class. + * + * Differs from `RefType.getEnclosingType()` by including anonymous classes defined by lambdas. + */ +private RefType desugaredGetEnclosingType(NestedClass inner) { + exists(ControlFlowNode node | + node = parentDef(inner) and + node.getEnclosingCallable().getDeclaringType() = result + ) +} + +/** + * Gets the control flow node at which the variable is read to get the value for + * a `VarAccess` inside a closure. `capturedvar` is the variable in its defining + * scope, and `closurevar` is the variable in the closure. + */ +private ControlFlowNode captureNode(TrackedVar capturedvar, TrackedVar closurevar) { + exists( + LocalScopeVariable v, Callable inner, Callable outer, NestedClass innerclass, VarAccess va + | + va.getVariable() = v and + inner = va.getEnclosingCallable() and + outer = v.getCallable() and + inner != outer and + inner.getDeclaringType() = innerclass and + result = parentDef(desugaredGetEnclosingType*(innerclass)) and + result.getEnclosingStmt().getEnclosingCallable() = outer and + capturedvar = TLocalVar(outer, v) and + closurevar = TLocalVar(inner, v) + ) +} + +/** Holds if the value of `v` is captured in `b` at index `i`. */ +private predicate variableCapture(TrackedVar capturedvar, TrackedVar closurevar, BasicBlock b, int i) { + b.getNode(i) = captureNode(capturedvar, closurevar) +} + +/** Holds if `n` must update the locally tracked variable `v`. */ +pragma[nomagic] +private predicate certainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { + exists(VariableUpdate a | a.getControlFlowNode() = n | getDestVar(a) = v) and + b.getNode(i) = n and + hasDominanceInformation(b) + or + certainVariableUpdate(v.getQualifier(), n, b, i) +} + +/** Holds if `v` has an implicit definition at the entry, `b`, of the callable. */ +pragma[nomagic] +private predicate hasEntryDef(TrackedVar v, BasicBlock b) { + exists(LocalScopeVariable l, Callable c | + v = TLocalVar(c, l) and c.getBody().getControlFlowNode() = b + | + l instanceof Parameter or + l.getCallable() != c + ) + or + v instanceof SsaSourceField and v.getEnclosingCallable().getBody().getControlFlowNode() = b +} + +/** Holds if `n` might update the locally tracked variable `v`. */ +pragma[nomagic] +private predicate uncertainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { + exists(Call c | c = n.asCall() | updatesNamedField(c, v, _)) and + b.getNode(i) = n and + hasDominanceInformation(b) + or + uncertainVariableUpdate(v.getQualifier(), n, b, i) +} + +private module SsaInput implements SsaImplCommon::InputSig { + private import java as J + private import semmle.code.java.controlflow.Dominance as Dom + + class BasicBlock = J::BasicBlock; + + class ControlFlowNode = J::ControlFlowNode; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { Dom::bbIDominates(result, bb) } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getABBSuccessor() } + + class ExitBasicBlock extends BasicBlock { + ExitBasicBlock() { not exists(this.getABBSuccessor()) } + } + + class SourceVariable = SsaSourceVariable; + + /** + * Holds if the `i`th node of basic block `bb` is a (potential) write to source + * variable `v`. The Boolean `certain` indicates whether the write is certain. + * + * This includes implicit writes via calls. + */ + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + certainVariableUpdate(v, _, bb, i) and + certain = true + or + untrackedFieldWrite(bb, i, v) and + certain = true + or + hasEntryDef(v, bb) and + i = 0 and + certain = true + or + uncertainVariableUpdate(v, _, bb, i) and + certain = false + } + + /** + * Holds if the `i`th of basic block `bb` reads source variable `v`. + * + * This includes implicit reads via calls. + */ + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(VarRead use | + v.getAnAccess() = use and bb.getNode(i) = use.getControlFlowNode() and certain = true + ) + or + variableCapture(v, _, bb, i) and + certain = false + } +} + +import SsaImplCommon::Make as Impl + +final class Definition = Impl::Definition; + +final class WriteDefinition = Impl::WriteDefinition; + +final class UncertainWriteDefinition = Impl::UncertainWriteDefinition; + +final class PhiNode = Impl::PhiNode; + +class UntrackedDef extends Definition { + private VarRead read; + + UntrackedDef() { ssaUntrackedDef(this, read) } + + string toString() { result = read.toString() } + + Location getLocation() { result = read.getLocation() } +} + +cached +private module Cached { + /** Gets the destination variable of an update of a tracked variable. */ + cached + TrackedVar getDestVar(VariableUpdate upd) { + result.getAnAccess() = upd.(Assignment).getDest() + or + exists(LocalVariableDecl v | v = upd.(LocalVariableDeclExpr).getVariable() | + result = TLocalVar(v.getCallable(), v) + ) + or + result.getAnAccess() = upd.(UnaryAssignExpr).getExpr() + } + + cached + predicate ssaExplicitUpdate(SsaUpdate def, VariableUpdate upd) { + exists(SsaSourceVariable v, BasicBlock bb, int i | + def.definesAt(v, bb, i) and + certainVariableUpdate(v, upd.getControlFlowNode(), bb, i) and + getDestVar(upd) = def.getSourceVariable() + ) + } + + cached + predicate ssaUntrackedDef(Definition def, VarRead read) { + exists(SsaSourceVariable v, BasicBlock bb, int i | + def.definesAt(v, bb, i) and + untrackedFieldWrite(bb, i, v) and + read.getControlFlowNode() = bb.getNode(i + 1) + ) + } + + /* + * The SSA construction for a field `f` relies on implicit update nodes at + * every call site that conceivably could reach an update of the field. + * + * At a first approximation we need to find update paths of the form: + * Callable --(callEdge)-->* Callable(setter of f) + * + * This can be improved by excluding paths ending in: + * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f) + * as these updates are guaranteed not to alias with the `f` under + * consideration. + * + * This set of paths can be expressed positively by noting that those + * that set `this.f` end in zero or more `intraInstanceCallEdge`s between + * methods, and before those is either the originating `Call` or a + * `crossInstanceCallEdge`. + */ + + /** + * Holds if `fw` is a field write that is not relevant as an implicit SSA + * update, since it is an initialization and therefore cannot alias. + */ + private predicate init(FieldWrite fw) { + fw.getEnclosingCallable() instanceof InitializerMethod + or + fw.getEnclosingCallable() instanceof Constructor and fw.isOwnFieldAccess() + or + exists(LocalVariableDecl v | + v.getAnAccess() = fw.getQualifier() and + forex(VariableAssign va | va.getDestVar() = v and exists(va.getSource()) | + va.getSource() instanceof ClassInstanceExpr + ) + ) + } + + /** + * Holds if `fw` is an update of `f` in `c` that is relevant for SSA construction. + */ + cached + predicate relevantFieldUpdate(Callable c, Field f, FieldWrite fw) { + fw = f.getAnAccess() and + not init(fw) and + fw.getEnclosingCallable() = c and + exists(TrackedField nf | nf.getField() = f) + } + + /** Holds if `c` can change the value of `this.f` and is relevant for SSA construction. */ + private predicate setsOwnField(Method c, Field f) { + exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and fw.isOwnFieldAccess()) + } + + /** + * Holds if `c` can change the value of `f` and is relevant for SSA + * construction excluding those cases covered by `setsOwnField`. + */ + private predicate setsOtherField(Callable c, Field f) { + exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and not fw.isOwnFieldAccess()) + } + + pragma[nomagic] + private predicate innerclassSupertypeStar(InnerClass t1, RefType t2) { + t1.getASourceSupertype*().getSourceDeclaration() = t2 + } + + /** + * Holds if `(c1,m2)` is a call edge to a method that does not change the value + * of `this`. + * + * Constructor-to-constructor calls can also be intra-instance, but are not + * included, as this does not affect whether a call chain ends in + * + * ``` + * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f) + * ``` + */ + private predicate intraInstanceCallEdge(Callable c1, Method m2) { + exists(MethodCall ma, RefType t1 | + ma.getCaller() = c1 and + m2 = viableImpl_v2(ma) and + not m2.isStatic() and + ( + not exists(ma.getQualifier()) or + ma.getQualifier() instanceof ThisAccess or + ma.getQualifier() instanceof SuperAccess + ) and + c1.getDeclaringType() = t1 and + if t1 instanceof InnerClass + then + innerclassSupertypeStar(t1, ma.getCallee().getSourceDeclaration().getDeclaringType()) and + not exists(ma.getQualifier().(ThisAccess).getQualifier()) and + not exists(ma.getQualifier().(SuperAccess).getQualifier()) + else any() + ) + } + + private Callable tgt(Call c) { + result = viableImpl_v2(c) + or + result = getRunnerTarget(c) + or + c instanceof ConstructorCall and result = c.getCallee().getSourceDeclaration() + } + + /** Holds if `(c1,c2)` is an edge in the call graph. */ + private predicate callEdge(Callable c1, Callable c2) { + exists(Call c | c.getCaller() = c1 and c2 = tgt(c)) + } + + /** Holds if `(c1,c2)` is an edge in the call graph excluding `intraInstanceCallEdge`. */ + private predicate crossInstanceCallEdge(Callable c1, Callable c2) { + callEdge(c1, c2) and not intraInstanceCallEdge(c1, c2) + } + + /** + * Holds if `call` is relevant as a potential update of `f`. This requires the + * existence of an update to `f` somewhere. + */ + private predicate relevantCall(Call call, TrackedField f) { + call.getEnclosingCallable() = f.getEnclosingCallable() and + relevantFieldUpdate(_, f.getField(), _) + } + + private predicate source(Call call, TrackedField f, Field field, Callable c, boolean fresh) { + relevantCall(call, f) and + field = f.getField() and + c = tgt(call) and + if c instanceof Constructor then fresh = true else fresh = false + } + + /** + * A callable in a potential call-chain between a source that cares about the + * value of some field `f` and a sink that may overwrite `f`. The boolean + * `fresh` indicates whether the instance `this` in `c` has been freshly + * allocated along the call-chain. + */ + private newtype TCallableNode = + MkCallableNode(Callable c, boolean fresh) { source(_, _, _, c, fresh) or edge(_, c, fresh) } + + private predicate edge(TCallableNode n, Callable c2, boolean f2) { + exists(Callable c1, boolean f1 | n = MkCallableNode(c1, f1) | + intraInstanceCallEdge(c1, c2) and f2 = f1 + or + crossInstanceCallEdge(c1, c2) and + if c2 instanceof Constructor then f2 = true else f2 = false + ) + } + + private predicate edge(TCallableNode n1, TCallableNode n2) { + exists(Callable c2, boolean f2 | + edge(n1, c2, f2) and + n2 = MkCallableNode(c2, f2) + ) + } + + pragma[noinline] + private predicate source(Call call, TrackedField f, Field field, TCallableNode n) { + exists(Callable c, boolean fresh | + source(call, f, field, c, fresh) and + n = MkCallableNode(c, fresh) + ) + } + + private predicate sink(Callable c, Field f, TCallableNode n) { + setsOwnField(c, f) and n = MkCallableNode(c, false) + or + setsOtherField(c, f) and n = MkCallableNode(c, _) + } + + private predicate prunedNode(TCallableNode n) { + sink(_, _, n) + or + exists(TCallableNode mid | edge(n, mid) and prunedNode(mid)) + } + + private predicate prunedEdge(TCallableNode n1, TCallableNode n2) { + prunedNode(n1) and + prunedNode(n2) and + edge(n1, n2) + } + + private predicate edgePlus(TCallableNode c1, TCallableNode c2) = fastTC(prunedEdge/2)(c1, c2) + + /** + * Holds if there exists a call-chain originating in `call` that can update `f` on some instance + * where `f` and `call` share the same enclosing callable in which a + * `FieldRead` of `f` is reachable from `call`. + */ + pragma[noopt] + private predicate updatesNamedFieldImpl(Call call, TrackedField f, Callable setter) { + exists(TCallableNode src, TCallableNode sink, Field field | + source(call, f, field, src) and + sink(setter, field, sink) and + (src = sink or edgePlus(src, sink)) + ) + } + + cached + predicate defUpdatesNamedField(SsaImplicitUpdate def, TrackedField f, Callable setter) { + f = def.getSourceVariable() and + updatesNamedField(def.getCfgNode().asCall(), f, setter) + } + + cached + predicate ssaUncertainImplicitUpdate(SsaImplicitUpdate def) { + exists(SsaSourceVariable v, BasicBlock bb, int i | + def.definesAt(v, bb, i) and + uncertainVariableUpdate(v, _, bb, i) + ) + } + + cached + predicate ssaImplicitInit(WriteDefinition def) { + exists(SsaSourceVariable v, BasicBlock bb, int i | + def.definesAt(v, bb, i) and + hasEntryDef(v, bb) and + i = 0 + ) + } + + pragma[nomagic] + private predicate captureDefReaches(Definition def, SsaInput::BasicBlock bb2, int i2) { + variableCapture(def.getSourceVariable(), _, _, _) and + exists(SsaInput::BasicBlock bb1, int i1 | + Impl::adjacentDefRead(def, bb1, i1, bb2, i2) and + def.definesAt(_, bb1, i1) + ) + or + exists(SsaInput::BasicBlock bb3, int i3 | + captureDefReaches(def, bb3, i3) and + SsaInput::variableRead(bb3, i3, _, _) and + Impl::adjacentDefRead(def, bb3, i3, bb2, i2) + ) + } + + /** Holds if `init` is a closure variable that captures the value of `capturedvar`. */ + cached + predicate captures(SsaImplicitInit init, SsaVariable capturedvar) { + exists(BasicBlock bb, int i | + captureDefReaches(capturedvar, bb, i) and + variableCapture(capturedvar.getSourceVariable(), init.getSourceVariable(), bb, i) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches `redef` without crossing another + * SSA definition of `v`. + */ + cached + predicate ssaDefReachesUncertainDef(TrackedSsaDef def, SsaUncertainImplicitUpdate redef) { + Impl::uncertainWriteDefinitionInput(redef, def) + } + + pragma[nomagic] + private predicate defReaches(Definition def, DataFlowIntegration::Node node) { + exists(DataFlowIntegration::SsaDefinitionExtNode nodeFrom | + nodeFrom.getDefinitionExt() = def and + DataFlowIntegrationImpl::localFlowStep(_, nodeFrom, node, false) + ) + or + exists(DataFlowIntegration::Node mid | + defReaches(def, mid) and + DataFlowIntegrationImpl::localFlowStep(_, mid, node, _) + | + // flow into phi input node + mid instanceof DataFlowIntegration::SsaInputNode + or + // flow into definition + mid instanceof DataFlowIntegration::SsaDefinitionExtNode + ) + } + + /** + * Holds if the value defined at `def` can reach `use` without passing through + * any other uses, but possibly through phi nodes and uncertain implicit updates. + */ + cached + predicate firstUse(Definition def, VarRead use) { + exists(DataFlowIntegration::ExprNode nodeTo | + nodeTo.getExpr() = use and + defReaches(def, nodeTo) + ) + } + + cached + VarRead getAUse(Definition def) { + exists(SsaSourceVariable v, BasicBlock bb, int i | + Impl::ssaDefReachesRead(v, def, bb, i) and + result.getControlFlowNode() = bb.getNode(i) and + result = v.getAnAccess() + ) + } + + cached + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def) { + Impl::ssaDefReachesEndOfBlock(bb, def, _) + } + + cached + predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { + Impl::phiHasInputFromBlock(phi, inp, bb) + } + + cached + module DataFlowIntegration { + import DataFlowIntegrationImpl + + cached + predicate localFlowStep(Impl::DefinitionExt def, Node nodeFrom, Node nodeTo, boolean isUseStep) { + not def instanceof UntrackedDef and + DataFlowIntegrationImpl::localFlowStep(def, nodeFrom, nodeTo, isUseStep) + } + + cached + predicate localMustFlowStep(Impl::DefinitionExt def, Node nodeFrom, Node nodeTo) { + not def instanceof UntrackedDef and + DataFlowIntegrationImpl::localMustFlowStep(def, nodeFrom, nodeTo) + } + + signature predicate guardChecksSig(Guards::Guard g, Expr e, boolean branch); + + cached // nothing is actually cached + module BarrierGuard { + private predicate guardChecksAdjTypes( + DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e, boolean branch + ) { + guardChecks(g, e, branch) + } + + private Node getABarrierNodeImpl() { + result = DataFlowIntegrationImpl::BarrierGuard::getABarrierNode() + } + + predicate getABarrierNode = getABarrierNodeImpl/0; + } + } + + cached + module SsaPublic { + pragma[nomagic] + private predicate useReaches(VarRead use, DataFlowIntegration::Node node, boolean sameVar) { + exists(DataFlowIntegration::ExprNode nodeFrom | + nodeFrom.getExpr() = use and + DataFlowIntegration::localFlowStep(_, nodeFrom, node, true) and + sameVar = true + ) + or + exists(DataFlowIntegration::Node mid, boolean sameVarMid | + useReaches(use, mid, sameVarMid) and + DataFlowIntegration::localFlowStep(_, mid, node, _) + | + // flow into phi input node + mid instanceof DataFlowIntegration::SsaInputNode and + sameVar = false + or + // flow into definition + exists(Impl::DefinitionExt def | + def = mid.(DataFlowIntegration::SsaDefinitionExtNode).getDefinitionExt() + | + if def instanceof Impl::PhiReadNode then sameVar = sameVarMid else sameVar = false + ) + ) + } + + /** + * Holds if `use1` and `use2` form an adjacent use-use-pair of the same SSA + * variable, that is, the value read in `use1` can reach `use2` without passing + * through any other use or any SSA definition of the variable. + */ + cached + predicate adjacentUseUseSameVar(VarRead use1, VarRead use2) { + exists(DataFlowIntegration::ExprNode nodeTo | + nodeTo.getExpr() = use2 and + useReaches(use1, nodeTo, true) + ) + } + + /** + * Holds if `use1` and `use2` form an adjacent use-use-pair of the same + * `SsaSourceVariable`, that is, the value read in `use1` can reach `use2` + * without passing through any other use or any SSA definition of the variable + * except for phi nodes and uncertain implicit updates. + */ + cached + predicate adjacentUseUse(VarRead use1, VarRead use2) { + exists(DataFlowIntegration::ExprNode nodeTo | + nodeTo.getExpr() = use2 and + useReaches(use1, nodeTo, _) + ) + } + } + + /** + * Provides internal implementation predicates that are not cached and should not + * be used outside of this file. + */ + cached // needed to avoid compilation error; has no actual effect + module Internal { + predicate updatesNamedField = updatesNamedFieldImpl/3; // use alias to avoid caching + } +} + +import Cached +private import Internal + +/** + * An SSA definition excluding those variables that use a trivial SSA construction. + */ +private class TrackedSsaDef extends Definition { + TrackedSsaDef() { not this.getSourceVariable() = any(SsaSourceField f | not trackField(f)) } + + /** + * Holds if this SSA definition occurs at the specified position. + * Phi nodes are placed at index -1. + */ + predicate definesAt(TrackedVar v, BasicBlock b, int i) { super.definesAt(v, b, i) } +} + +private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInputSig { + private import java as J + + class Expr instanceof J::Expr { + string toString() { result = super.toString() } + + Location getLocation() { result = super.getLocation() } + + predicate hasCfgNode(BasicBlock bb, int i) { + super.getControlFlowNode() = bb.(J::BasicBlock).getNode(i) + } + } + + Expr getARead(Definition def) { result = getAUse(def) } + + class Parameter = J::Parameter; + + predicate ssaDefAssigns(Impl::WriteDefinition def, Expr value) { + exists(VariableUpdate upd | upd = def.(SsaExplicitUpdate).getDefiningExpr() | + value = upd.(VariableAssign).getSource() or + value = upd.(AssignOp) or + value = upd.(RecordBindingVariableExpr) + ) + } + + predicate ssaDefInitializesParam(Impl::WriteDefinition def, Parameter p) { + def.(SsaImplicitInit).getSourceVariable() = + any(SsaSourceVariable v | + v.getVariable() = p and + v.getEnclosingCallable() = p.getCallable() + ) + } + + predicate allowFlowIntoUncertainDef(UncertainWriteDefinition def) { + def instanceof SsaUncertainImplicitUpdate + } + + class Guard extends Guards::Guard { + predicate hasCfgNode(BasicBlock bb, int i) { + this = bb.getNode(i).asExpr() + or + this = bb.getNode(i).asStmt() + } + } + + /** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */ + predicate guardControlsBlock(Guard guard, BasicBlock bb, boolean branch) { + guard.controls(bb, branch) + } + + /** Gets an immediate conditional successor of basic block `bb`, if any. */ + BasicBlock getAConditionalBasicBlockSuccessor(BasicBlock bb, boolean branch) { + result = bb.(Guards::ConditionBlock).getTestSuccessor(branch) + } +} + +private module DataFlowIntegrationImpl = Impl::DataFlowIntegration; diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll b/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll index 39719f3524d4..773743370f64 100644 --- a/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll @@ -6,46 +6,36 @@ import java import semmle.code.java.dataflow.SSA private import semmle.code.java.frameworks.Assertions -private predicate emptyDecl(SsaExplicitUpdate ssa) { - exists(LocalVariableDeclExpr decl | - decl = ssa.getDefiningExpr() and - not exists(decl.getInit()) and - not exists(EnhancedForStmt for | for.getVariable() = decl) - ) +private predicate emptyDecl(LocalVariableDeclExpr decl) { + not exists(decl.getInit()) and + not exists(EnhancedForStmt for | for.getVariable() = decl) } -/** - * A dead SSA variable. Excludes parameters, and phi nodes are never dead, so only includes `VariableUpdate`s. - */ -predicate deadLocal(SsaExplicitUpdate ssa) { - ssa.getSourceVariable().getVariable() instanceof LocalScopeVariable and - not exists(ssa.getAUse()) and - not exists(SsaPhiNode phi | phi.getAPhiInput() = ssa) and - not exists(SsaImplicitInit init | init.captures(ssa)) and - not emptyDecl(ssa) and - not readImplicitly(ssa, _) +/** A dead variable update. */ +predicate deadLocal(VariableUpdate upd) { + upd.getDestVar() instanceof LocalScopeVariable and + not exists(SsaExplicitUpdate ssa | upd = ssa.getDefiningExpr()) and + not emptyDecl(upd) and + not readImplicitly(upd, _) } /** - * A dead SSA variable that is expected to be dead as indicated by an assertion. + * A dead variable update that is expected to be dead as indicated by an assertion. */ -predicate expectedDead(SsaExplicitUpdate ssa) { - deadLocal(ssa) and - assertFail(ssa.getBasicBlock(), _) -} +predicate expectedDead(VariableUpdate upd) { assertFail(upd.getBasicBlock(), _) } /** - * A dead SSA variable that is overwritten by a live SSA definition. + * A dead update that is overwritten by a live update. */ -predicate overwritten(SsaExplicitUpdate ssa) { - deadLocal(ssa) and - exists(SsaExplicitUpdate overwrite | - overwrite.getSourceVariable() = ssa.getSourceVariable() and +predicate overwritten(VariableUpdate upd) { + deadLocal(upd) and + exists(VariableUpdate overwrite | + overwrite.getDestVar() = upd.getDestVar() and not deadLocal(overwrite) and - not overwrite.getDefiningExpr() instanceof LocalVariableDeclExpr and + not overwrite instanceof LocalVariableDeclExpr and exists(BasicBlock bb1, BasicBlock bb2, int i, int j | - bb1.getNode(i) = ssa.getCfgNode() and - bb2.getNode(j) = overwrite.getCfgNode() + bb1.getNode(i) = upd.getControlFlowNode() and + bb2.getNode(j) = overwrite.getControlFlowNode() | bb1.getABBSuccessor+() = bb2 or @@ -63,9 +53,9 @@ predicate read(LocalScopeVariable v) { readImplicitly(_, v) } -private predicate readImplicitly(SsaExplicitUpdate ssa, LocalScopeVariable v) { - v = ssa.getSourceVariable().getVariable() and - exists(TryStmt try | try.getAResourceVariable() = ssa.getDefiningExpr().getDestVar()) +predicate readImplicitly(VariableUpdate upd, LocalScopeVariable v) { + v = upd.getDestVar() and + exists(TryStmt try | try.getAResourceVariable() = upd.getDestVar()) } /** diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql index cf555ad92d19..3f96f43bc249 100644 --- a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql @@ -40,13 +40,12 @@ predicate excludedInit(Type t, Expr decl) { ) } -from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa +from VariableUpdate def, LocalScopeVariable v where - def = ssa.getDefiningExpr() and - v = ssa.getSourceVariable().getVariable() and - deadLocal(ssa) and - not expectedDead(ssa) and - overwritten(ssa) and + def.getDestVar() = v and + deadLocal(def) and + not expectedDead(def) and + overwritten(def) and not exists(LocalVariableDeclExpr decl | def = decl | excludedInit(decl.getVariable().getType(), decl.getInit()) ) diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql index 582e109e35ed..f8969086a564 100644 --- a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql @@ -11,13 +11,12 @@ import java import DeadLocals -from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa +from VariableUpdate def, LocalScopeVariable v where - def = ssa.getDefiningExpr() and - v = ssa.getSourceVariable().getVariable() and - deadLocal(ssa) and - not expectedDead(ssa) and - not overwritten(ssa) and + def.getDestVar() = v and + deadLocal(def) and + not expectedDead(def) and + not overwritten(def) and read(v) and not def.(AssignExpr).getSource() instanceof NullLiteral and (def instanceof Assignment or def.(UnaryAssignExpr).getParent() instanceof ExprStmt) diff --git a/java/ql/test/library-tests/dataflow/capture/test.expected b/java/ql/test/library-tests/dataflow/capture/test.expected index f18999a1d81f..a98b4990228e 100644 --- a/java/ql/test/library-tests/dataflow/capture/test.expected +++ b/java/ql/test/library-tests/dataflow/capture/test.expected @@ -1,7 +1,10 @@ +| A.java:14:14:14:16 | "A" : String | A.java:14:7:14:20 | SSA def(a) : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:15:16:15:16 | a : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:15:16:15:22 | get(...) : String | | A.java:14:14:14:16 | "A" : String | A.java:18:8:18:15 | p : String | +| A.java:14:14:14:16 | "A" : String | A.java:18:25:40:3 | SSA def(p) : String | +| A.java:14:14:14:16 | "A" : String | A.java:28:7:38:5 | SSA def(a) : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:28:11:38:5 | p : String | | A.java:14:14:14:16 | "A" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [p] | @@ -13,11 +16,16 @@ | A.java:14:14:14:16 | "A" : String | A.java:35:26:35:27 | this : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:39:12:39:12 | a : new A(...) { ... } [p] | | A.java:14:14:14:16 | "A" : String | A.java:39:12:39:12 | p : String | +| A.java:21:11:21:13 | "B" : String | A.java:14:7:14:20 | SSA def(a) : new A(...) { ... } [String s] | | A.java:21:11:21:13 | "B" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [String s] | | A.java:21:11:21:13 | "B" : String | A.java:15:16:15:16 | a : new A(...) { ... } [String s] | | A.java:21:11:21:13 | "B" : String | A.java:15:16:15:22 | get(...) : String | | A.java:21:11:21:13 | "B" : String | A.java:21:7:21:13 | ...=... : String | +| A.java:21:11:21:13 | "B" : String | A.java:21:7:21:13 | SSA def(s) : String | +| A.java:21:11:21:13 | "B" : String | A.java:21:7:21:13 | [input] SSA phi(s) : String | +| A.java:21:11:21:13 | "B" : String | A.java:25:5:25:26 | SSA phi(s) : String | | A.java:21:11:21:13 | "B" : String | A.java:25:5:25:26 | phi(String s) : String | +| A.java:21:11:21:13 | "B" : String | A.java:28:7:38:5 | SSA def(a) : new A(...) { ... } [String s] | | A.java:21:11:21:13 | "B" : String | A.java:28:11:38:5 | String s : String | | A.java:21:11:21:13 | "B" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [String s] | | A.java:21:11:21:13 | "B" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [String s] | @@ -29,11 +37,16 @@ | A.java:21:11:21:13 | "B" : String | A.java:35:26:35:27 | this : new A(...) { ... } [String s] | | A.java:21:11:21:13 | "B" : String | A.java:39:12:39:12 | String s : String | | A.java:21:11:21:13 | "B" : String | A.java:39:12:39:12 | a : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:14:7:14:20 | SSA def(a) : new A(...) { ... } [String s] | | A.java:23:11:23:13 | "C" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [String s] | | A.java:23:11:23:13 | "C" : String | A.java:15:16:15:16 | a : new A(...) { ... } [String s] | | A.java:23:11:23:13 | "C" : String | A.java:15:16:15:22 | get(...) : String | | A.java:23:11:23:13 | "C" : String | A.java:23:7:23:13 | ...=... : String | +| A.java:23:11:23:13 | "C" : String | A.java:23:7:23:13 | SSA def(s) : String | +| A.java:23:11:23:13 | "C" : String | A.java:23:7:23:13 | [input] SSA phi(s) : String | +| A.java:23:11:23:13 | "C" : String | A.java:25:5:25:26 | SSA phi(s) : String | | A.java:23:11:23:13 | "C" : String | A.java:25:5:25:26 | phi(String s) : String | +| A.java:23:11:23:13 | "C" : String | A.java:28:7:38:5 | SSA def(a) : new A(...) { ... } [String s] | | A.java:23:11:23:13 | "C" : String | A.java:28:11:38:5 | String s : String | | A.java:23:11:23:13 | "C" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [String s] | | A.java:23:11:23:13 | "C" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [String s] | @@ -47,16 +60,20 @@ | A.java:23:11:23:13 | "C" : String | A.java:39:12:39:12 | a : new A(...) { ... } [String s] | | A.java:25:22:25:24 | "D" : String | A.java:4:5:4:7 | parameter this [Return] : Box [elem] | | A.java:25:22:25:24 | "D" : String | A.java:4:9:4:16 | e : String | +| A.java:25:22:25:24 | "D" : String | A.java:4:19:4:31 | SSA def(e) : String | | A.java:25:22:25:24 | "D" : String | A.java:4:21:4:24 | this <.field> [post update] : Box [elem] | | A.java:25:22:25:24 | "D" : String | A.java:4:21:4:28 | ...=... : String | | A.java:25:22:25:24 | "D" : String | A.java:4:28:4:28 | e : String | | A.java:25:22:25:24 | "D" : String | A.java:6:12:6:18 | parameter this : Box [elem] | | A.java:25:22:25:24 | "D" : String | A.java:6:31:6:34 | elem : String | | A.java:25:22:25:24 | "D" : String | A.java:6:31:6:34 | this <.field> : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:14:7:14:20 | SSA def(a) : new A(...) { ... } [Box b1, ... (2)] | | A.java:25:22:25:24 | "D" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [Box b1, ... (2)] | | A.java:25:22:25:24 | "D" : String | A.java:15:16:15:16 | a : new A(...) { ... } [Box b1, ... (2)] | | A.java:25:22:25:24 | "D" : String | A.java:15:16:15:22 | get(...) : String | +| A.java:25:22:25:24 | "D" : String | A.java:25:9:25:25 | SSA def(b1) : Box [elem] | | A.java:25:22:25:24 | "D" : String | A.java:25:14:25:25 | new Box(...) : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:28:7:38:5 | SSA def(a) : new A(...) { ... } [Box b1, ... (2)] | | A.java:25:22:25:24 | "D" : String | A.java:28:11:38:5 | Box b1 : Box [elem] | | A.java:25:22:25:24 | "D" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [Box b1, ... (2)] | | A.java:25:22:25:24 | "D" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [Box b1, ... (2)] | @@ -71,16 +88,19 @@ | A.java:25:22:25:24 | "D" : String | A.java:39:12:39:12 | a : new A(...) { ... } [Box b1, ... (2)] | | A.java:27:16:27:18 | "E" : String | A.java:5:10:5:16 | parameter this [Return] : Box [elem] | | A.java:27:16:27:18 | "E" : String | A.java:5:18:5:25 | e : String | +| A.java:27:16:27:18 | "E" : String | A.java:5:28:5:40 | SSA def(e) : String | | A.java:27:16:27:18 | "E" : String | A.java:5:30:5:33 | this <.field> [post update] : Box [elem] | | A.java:27:16:27:18 | "E" : String | A.java:5:30:5:37 | ...=... : String | | A.java:27:16:27:18 | "E" : String | A.java:5:37:5:37 | e : String | | A.java:27:16:27:18 | "E" : String | A.java:6:12:6:18 | parameter this : Box [elem] | | A.java:27:16:27:18 | "E" : String | A.java:6:31:6:34 | elem : String | | A.java:27:16:27:18 | "E" : String | A.java:6:31:6:34 | this <.field> : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:14:7:14:20 | SSA def(a) : new A(...) { ... } [Box b2, ... (2)] | | A.java:27:16:27:18 | "E" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [Box b2, ... (2)] | | A.java:27:16:27:18 | "E" : String | A.java:15:16:15:16 | a : new A(...) { ... } [Box b2, ... (2)] | | A.java:27:16:27:18 | "E" : String | A.java:15:16:15:22 | get(...) : String | | A.java:27:16:27:18 | "E" : String | A.java:27:5:27:6 | b2 [post update] : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:28:7:38:5 | SSA def(a) : new A(...) { ... } [Box b2, ... (2)] | | A.java:27:16:27:18 | "E" : String | A.java:28:11:38:5 | Box b2 : Box [elem] | | A.java:27:16:27:18 | "E" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [Box b2, ... (2)] | | A.java:27:16:27:18 | "E" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [Box b2, ... (2)] | diff --git a/java/ql/test/library-tests/dataflow/null/testnullflow.expected b/java/ql/test/library-tests/dataflow/null/testnullflow.expected index 532d64e81f8d..d73b00f0fef1 100644 --- a/java/ql/test/library-tests/dataflow/null/testnullflow.expected +++ b/java/ql/test/library-tests/dataflow/null/testnullflow.expected @@ -1,4 +1,6 @@ | A.java:5:18:5:21 | null | A.java:2:13:2:20 | o | +| A.java:5:18:5:21 | null | A.java:5:12:5:21 | SSA def(src) | | A.java:5:18:5:21 | null | A.java:5:18:5:21 | null | +| A.java:5:18:5:21 | null | A.java:6:12:6:18 | SSA def(x) | | A.java:5:18:5:21 | null | A.java:6:16:6:18 | src | | A.java:5:18:5:21 | null | A.java:7:10:7:10 | x | diff --git a/java/ql/test/library-tests/dataflow/partial/test.expected b/java/ql/test/library-tests/dataflow/partial/test.expected index ea7da3a96902..700c1b31dcfb 100644 --- a/java/ql/test/library-tests/dataflow/partial/test.expected +++ b/java/ql/test/library-tests/dataflow/partial/test.expected @@ -3,12 +3,14 @@ edges | A.java:12:14:12:18 | src(...) : Object | A.java:12:5:12:5 | b [post update] : Box [elem] | | A.java:12:14:12:18 | src(...) : Object | A.java:12:5:12:18 | ...=... : Object | | A.java:13:12:13:12 | b : Box [elem] | A.java:17:13:17:16 | f1(...) : Box [elem] | -| A.java:17:13:17:16 | f1(...) : Box [elem] | A.java:18:8:18:8 | b : Box [elem] | +| A.java:17:9:17:16 | SSA def(b) : Box [elem] | A.java:18:8:18:8 | b : Box [elem] | +| A.java:17:13:17:16 | f1(...) : Box [elem] | A.java:17:9:17:16 | SSA def(b) : Box [elem] | | A.java:18:8:18:8 | b : Box [elem] | A.java:21:11:21:15 | b : Box [elem] | #select | 0 | A.java:12:5:12:5 | b [post update] : Box [elem] | | 0 | A.java:12:5:12:18 | ...=... : Object | | 0 | A.java:13:12:13:12 | b : Box [elem] | +| 1 | A.java:17:9:17:16 | SSA def(b) : Box [elem] | | 1 | A.java:17:13:17:16 | f1(...) : Box [elem] | | 1 | A.java:18:8:18:8 | b : Box [elem] | | 2 | A.java:21:11:21:15 | b : Box [elem] | diff --git a/java/ql/test/library-tests/dataflow/partial/testRev.expected b/java/ql/test/library-tests/dataflow/partial/testRev.expected index 15ce5d56acef..d798f4434805 100644 --- a/java/ql/test/library-tests/dataflow/partial/testRev.expected +++ b/java/ql/test/library-tests/dataflow/partial/testRev.expected @@ -2,7 +2,8 @@ edges | A.java:4:16:4:18 | parameter this [Return] [elem] | A.java:22:17:22:25 | new Box(...) [elem] | | A.java:4:16:4:18 | this [post update] [elem] | A.java:4:16:4:18 | parameter this [Return] [elem] | | A.java:5:19:5:22 | elem | A.java:24:10:24:19 | other.elem | -| A.java:22:17:22:25 | new Box(...) [elem] | A.java:23:13:23:17 | other [elem] | +| A.java:22:9:22:25 | SSA def(other) [elem] | A.java:23:13:23:17 | other [elem] | +| A.java:22:17:22:25 | new Box(...) [elem] | A.java:22:9:22:25 | SSA def(other) [elem] | | A.java:23:13:23:17 | other [elem] | A.java:24:10:24:14 | other [elem] | | A.java:23:13:23:17 | other [post update] [elem] | A.java:24:10:24:14 | other [elem] | | A.java:24:10:24:14 | other [elem] | A.java:24:10:24:19 | other.elem | @@ -10,6 +11,7 @@ edges | A.java:28:5:28:5 | b [post update] [elem] | A.java:27:16:27:20 | b [Return] [elem] | | A.java:28:14:28:25 | new Object(...) | A.java:28:5:28:5 | b [post update] [elem] | #select +| 0 | A.java:22:9:22:25 | SSA def(other) [elem] | | 0 | A.java:22:17:22:25 | new Box(...) [elem] | | 0 | A.java:23:13:23:17 | other [elem] | | 0 | A.java:23:13:23:17 | other [post update] [elem] | diff --git a/java/ql/test/library-tests/dataflow/switchexpr/switchexprflow.expected b/java/ql/test/library-tests/dataflow/switchexpr/switchexprflow.expected index d444bae3cc77..551c836889da 100644 --- a/java/ql/test/library-tests/dataflow/switchexpr/switchexprflow.expected +++ b/java/ql/test/library-tests/dataflow/switchexpr/switchexprflow.expected @@ -1,9 +1,13 @@ | TestSwitchExpr.java:4:15:4:22 | o | +| TestSwitchExpr.java:7:16:7:28 | SSA def(x1) | | TestSwitchExpr.java:7:21:7:28 | source(...) | +| TestSwitchExpr.java:8:16:8:30 | SSA def(x2) | | TestSwitchExpr.java:8:21:8:30 | switch (...) | | TestSwitchExpr.java:10:24:10:25 | x1 | +| TestSwitchExpr.java:12:16:12:30 | SSA def(x3) | | TestSwitchExpr.java:12:21:12:30 | switch (...) | | TestSwitchExpr.java:13:38:13:39 | x2 | +| TestSwitchExpr.java:16:16:16:30 | SSA def(x4) | | TestSwitchExpr.java:16:21:16:30 | switch (...) | | TestSwitchExpr.java:19:23:19:24 | x3 | | TestSwitchExpr.java:23:14:23:15 | x4 | diff --git a/java/ql/test/library-tests/dataflow/taint-ioutils/dataFlow.expected b/java/ql/test/library-tests/dataflow/taint-ioutils/dataFlow.expected index 1902605e618b..ffd641a45d70 100644 --- a/java/ql/test/library-tests/dataflow/taint-ioutils/dataFlow.expected +++ b/java/ql/test/library-tests/dataflow/taint-ioutils/dataFlow.expected @@ -1,19 +1,24 @@ +| Test.java:12:15:12:47 | SSA def(inp) | | Test.java:12:21:12:47 | new FileInputStream(...) | | Test.java:14:21:14:39 | buffer(...) | | Test.java:14:36:14:38 | inp | +| Test.java:15:16:15:54 | SSA def(lines) | | Test.java:15:24:15:54 | readLines(...) | | Test.java:15:42:15:44 | inp | | Test.java:16:18:16:45 | readFully(...) | | Test.java:16:36:16:38 | inp | | Test.java:17:22:17:55 | toBufferedInputStream(...) | | Test.java:17:52:17:54 | inp | +| Test.java:18:10:18:71 | SSA def(bufread) | | Test.java:18:20:18:71 | toBufferedReader(...) | | Test.java:18:45:18:70 | new InputStreamReader(...) | | Test.java:18:67:18:69 | inp | | Test.java:19:19:19:48 | toByteArray(...) | | Test.java:19:39:19:41 | inp | +| Test.java:20:10:20:50 | SSA def(chars) | | Test.java:20:18:20:50 | toCharArray(...) | | Test.java:20:38:20:40 | inp | +| Test.java:21:10:21:43 | SSA def(s) | | Test.java:21:14:21:43 | toString(...) | | Test.java:21:31:21:33 | inp | | Test.java:22:20:22:52 | toInputStream(...) | diff --git a/java/ql/test/library-tests/dataflow/this-flow/this-flow.expected b/java/ql/test/library-tests/dataflow/this-flow/this-flow.expected index 1f28514b664d..5b59c2167074 100644 --- a/java/ql/test/library-tests/dataflow/this-flow/this-flow.expected +++ b/java/ql/test/library-tests/dataflow/this-flow/this-flow.expected @@ -10,11 +10,13 @@ | A.java:20:16:20:16 | this <.field> | | A.java:21:12:21:20 | getThis(...) | | A.java:21:12:21:20 | this <.method> | +| A.java:25:7:25:17 | SSA def(a) | | A.java:25:11:25:17 | new A(...) | | A.java:25:11:25:17 | new A(...) [pre constructor] | | A.java:26:12:26:12 | a | | A.java:26:12:26:22 | getThis(...) | | A.java:26:12:26:36 | getThisWrap(...) | +| A.java:27:7:27:17 | SSA def(c) | | A.java:27:11:27:17 | new C(...) | | A.java:27:11:27:17 | new C(...) [pre constructor] | | A.java:28:5:28:5 | c | diff --git a/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java b/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java index d0550e4b695f..e0d3494aa208 100644 --- a/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java +++ b/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java @@ -25,6 +25,12 @@ case String s when isSafe(s): } + String s2 = "string"; + + if (!isSafe(s2)) { + s2 = null; + } + sink(s2); } } diff --git a/java/ql/test/library-tests/ssa/ssaDef.expected b/java/ql/test/library-tests/ssa/ssaDef.expected index 899ca19301ad..cac2ada3c519 100644 --- a/java/ql/test/library-tests/ssa/ssaDef.expected +++ b/java/ql/test/library-tests/ssa/ssaDef.expected @@ -1,7 +1,4 @@ -| Fields.java:13:5:13:17 | x | Fields.java:13:11:13:16 | x | SSA def(x) | | Fields.java:13:5:13:17 | x | Fields.java:15:5:15:10 | ...=... | SSA def(x) | -| Fields.java:13:5:13:17 | x | Fields.java:18:5:18:15 | ...=... | SSA def(x) | -| Fields.java:13:5:13:17 | x | Fields.java:20:5:20:10 | ...=... | SSA def(x) | | Fields.java:13:15:13:16 | this.xs | Fields.java:12:19:21:3 | { ... } | SSA init(this.xs) | | Fields.java:13:15:13:16 | this.xs | Fields.java:14:5:14:9 | upd(...) | SSA impl upd[nonlocal](this.xs) | | Fields.java:13:15:13:16 | this.xs | Fields.java:17:7:17:11 | upd(...) | SSA impl upd[nonlocal](this.xs) | @@ -10,33 +7,17 @@ | Fields.java:24:5:24:28 | f | Fields.java:24:12:24:27 | f | SSA def(f) | | Fields.java:24:5:24:28 | f | Fields.java:43:7:43:22 | ...=... | SSA def(f) | | Fields.java:24:5:24:28 | f | Fields.java:44:5:44:13 | ; | SSA phi(f) | -| Fields.java:25:5:25:19 | y | Fields.java:25:11:25:18 | y | SSA def(y) | -| Fields.java:25:5:25:19 | y | Fields.java:29:5:29:12 | ...=... | SSA def(y) | -| Fields.java:25:5:25:19 | y | Fields.java:33:5:33:12 | ...=... | SSA def(y) | -| Fields.java:25:5:25:19 | y | Fields.java:37:5:37:12 | ...=... | SSA def(y) | -| Fields.java:25:5:25:19 | y | Fields.java:40:5:40:12 | ...=... | SSA def(y) | -| Fields.java:25:5:25:19 | y | Fields.java:44:5:44:12 | ...=... | SSA def(y) | -| Fields.java:25:5:25:19 | y | Fields.java:46:5:46:12 | ...=... | SSA def(y) | | Fields.java:25:15:25:18 | f.xs | Fields.java:24:12:24:27 | f | SSA impl upd[explicit qualifier](f.xs) | | Fields.java:25:15:25:18 | f.xs | Fields.java:28:5:28:12 | f(...) | SSA impl upd[nonlocal](f.xs) | | Fields.java:25:15:25:18 | f.xs | Fields.java:32:5:32:9 | f(...) | SSA impl upd[nonlocal](f.xs) | | Fields.java:25:15:25:18 | f.xs | Fields.java:39:5:39:21 | ...=... | SSA def(f.xs) | | Fields.java:25:15:25:18 | f.xs | Fields.java:43:7:43:22 | ...=... | SSA impl upd[explicit qualifier](f.xs) | | Fields.java:25:15:25:18 | f.xs | Fields.java:44:5:44:13 | ; | SSA phi(f.xs) | -| Fields.java:26:5:26:17 | z | Fields.java:26:11:26:16 | z | SSA def(z) | -| Fields.java:26:5:26:17 | z | Fields.java:30:5:30:10 | ...=... | SSA def(z) | -| Fields.java:26:5:26:17 | z | Fields.java:34:5:34:10 | ...=... | SSA def(z) | -| Fields.java:26:5:26:17 | z | Fields.java:38:5:38:10 | ...=... | SSA def(z) | | Fields.java:26:5:26:17 | z | Fields.java:41:5:41:10 | ...=... | SSA def(z) | -| Fields.java:26:5:26:17 | z | Fields.java:47:5:47:10 | ...=... | SSA def(z) | | Fields.java:26:15:26:16 | this.xs | Fields.java:23:19:49:3 | { ... } | SSA init(this.xs) | | Fields.java:26:15:26:16 | this.xs | Fields.java:28:5:28:12 | f(...) | SSA impl upd[nonlocal](this.xs) | | Fields.java:26:15:26:16 | this.xs | Fields.java:32:5:32:9 | f(...) | SSA impl upd[nonlocal](this.xs) | | Fields.java:26:15:26:16 | this.xs | Fields.java:36:5:36:19 | ...=... | SSA def(this.xs) | -| Fields.java:27:5:27:19 | w | Fields.java:27:11:27:18 | w | SSA def(w) | -| Fields.java:27:5:27:19 | w | Fields.java:31:5:31:12 | ...=... | SSA def(w) | -| Fields.java:27:5:27:19 | w | Fields.java:35:5:35:12 | ...=... | SSA def(w) | -| Fields.java:27:5:27:19 | w | Fields.java:48:5:48:12 | ...=... | SSA def(w) | | Fields.java:27:15:27:18 | Fields.stat | Fields.java:23:19:49:3 | { ... } | SSA init(Fields.stat) | | Fields.java:27:15:27:18 | Fields.stat | Fields.java:24:16:24:27 | new Fields(...) | SSA impl upd[nonlocal](Fields.stat) | | Fields.java:27:15:27:18 | Fields.stat | Fields.java:28:5:28:12 | f(...) | SSA impl upd[nonlocal](Fields.stat) | @@ -58,15 +39,12 @@ | Nested.java:18:5:23:6 | h2 | Nested.java:18:15:23:5 | h2 | SSA def(h2) | | Nested.java:20:9:20:40 | hnest | Nested.java:20:19:20:39 | hnest | SSA def(hnest) | | Nested.java:24:5:24:31 | getInt(..).obj2 | Nested.java:30:23:30:36 | { ... } | SSA init(getInt(..).obj2) | -| Nested.java:24:5:24:31 | obj2 | Nested.java:24:12:24:30 | obj2 | SSA def(obj2) | | Nested.java:24:5:24:31 | obj2 | Nested.java:26:7:26:25 | ...=... | SSA def(obj2) | | Nested.java:24:5:24:31 | obj2 | Nested.java:28:7:28:25 | ...=... | SSA def(obj2) | | Nested.java:24:5:24:31 | obj2 | Nested.java:30:5:30:37 | var ...; | SSA phi(obj2) | -| Nested.java:30:5:30:37 | hash2 | Nested.java:30:15:30:36 | hash2 | SSA def(hash2) | | Nested.java:33:21:33:26 | p3 | Nested.java:33:29:42:3 | { ... } | SSA init(p3) | | Nested.java:34:5:34:11 | getInt(..).x3 | Nested.java:37:20:37:25 | { ... } | SSA init(getInt(..).x3) | | Nested.java:34:5:34:11 | getInt(..).x3 | Nested.java:40:20:40:25 | { ... } | SSA init(getInt(..).x3) | -| Nested.java:34:5:34:11 | x3 | Nested.java:34:9:34:10 | x3 | SSA def(x3) | | Nested.java:34:5:34:11 | x3 | Nested.java:36:7:36:12 | ...=... | SSA def(x3) | | Nested.java:34:5:34:11 | x3 | Nested.java:39:7:39:12 | ...=... | SSA def(x3) | | Test.java:4:8:4:16 | param | Test.java:4:19:32:2 | { ... } | SSA init(param) | @@ -78,16 +56,12 @@ | Test.java:6:3:6:12 | x | Test.java:19:3:19:3 | ; | SSA phi(x) | | Test.java:6:3:6:12 | x | Test.java:27:19:27:19 | i | SSA phi(x) | | Test.java:6:3:6:12 | x | Test.java:28:4:28:9 | ...+=... | SSA def(x) | -| Test.java:7:3:7:8 | y | Test.java:7:7:7:7 | y | SSA def(y) | | Test.java:7:3:7:8 | y | Test.java:11:4:11:10 | ...=... | SSA def(y) | | Test.java:7:3:7:8 | y | Test.java:14:4:14:8 | ...=... | SSA def(y) | | Test.java:7:3:7:8 | y | Test.java:15:4:15:9 | ...+=... | SSA def(y) | | Test.java:7:3:7:8 | y | Test.java:19:3:19:3 | ; | SSA phi(y) | | Test.java:7:3:7:8 | y | Test.java:20:10:20:10 | x | SSA phi(y) | | Test.java:7:3:7:8 | y | Test.java:24:4:24:9 | ...-=... | SSA def(y) | -| Test.java:8:3:8:8 | z | Test.java:8:7:8:7 | z | SSA def(z) | -| Test.java:8:3:8:8 | z | Test.java:12:4:12:8 | ...=... | SSA def(z) | -| Test.java:8:3:8:8 | z | Test.java:17:4:17:8 | ...=... | SSA def(z) | | Test.java:27:8:27:16 | i | Test.java:27:12:27:16 | i | SSA def(i) | | Test.java:27:8:27:16 | i | Test.java:27:19:27:19 | i | SSA phi(i) | | Test.java:27:8:27:16 | i | Test.java:27:25:27:27 | ...++ | SSA def(i) | @@ -101,5 +75,4 @@ | TestInstanceOfPattern.java:18:22:18:29 | s | TestInstanceOfPattern.java:18:29:18:29 | s | SSA def(s) | | TestInstanceOfPattern.java:21:8:21:8 | this.s | TestInstanceOfPattern.java:21:8:21:8 | s | SSA impl upd[untracked](this.s) | | TestInstanceOfPattern.java:24:13:24:22 | obj | TestInstanceOfPattern.java:24:25:30:2 | { ... } | SSA init(obj) | -| TestInstanceOfPattern.java:25:22:25:29 | s | TestInstanceOfPattern.java:25:29:25:29 | s | SSA def(s) | | TestInstanceOfPattern.java:25:34:25:34 | this.s | TestInstanceOfPattern.java:24:25:30:2 | { ... } | SSA init(this.s) |