From 6932e000c6026ba4ed8c14ff77f4c6bf2c81e6fa Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 20 Feb 2025 15:06:45 +0100 Subject: [PATCH] Java: Switch BaseSSA to use shared SSA lib. --- .../code/java/dataflow/internal/BaseSSA.qll | 439 ++++-------------- 1 file changed, 103 insertions(+), 336 deletions(-) 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 cf62fed552b3..2c37efea1a46 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll @@ -12,6 +12,7 @@ */ import java +private import codeql.ssa.Ssa as SsaImplCommon private newtype TBaseSsaSourceVariable = TLocalVar(Callable c, LocalScopeVariable v) { @@ -56,10 +57,8 @@ class BaseSsaSourceVariable extends TBaseSsaSourceVariable { Type getType() { result = this.getVariable().getType() } } -cached private module BaseSsaImpl { /** Gets the destination variable of an update of a tracked variable. */ - cached BaseSsaSourceVariable getDestVar(VariableUpdate upd) { result.getAnAccess() = upd.(Assignment).getDest() or @@ -71,7 +70,6 @@ private module BaseSsaImpl { } /** Holds if `n` updates the local variable `v`. */ - cached predicate variableUpdate(BaseSsaSourceVariable v, ControlFlowNode n, BasicBlock b, int i) { exists(VariableUpdate a | a.getControlFlowNode() = n | getDestVar(a) = v) and b.getNode(i) = n and @@ -119,50 +117,14 @@ private module BaseSsaImpl { ) } - /** Holds if `VarAccess` `use` of `v` occurs in `b` at index `i`. */ - private predicate variableUse(BaseSsaSourceVariable 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( + predicate variableCapture( BaseSsaSourceVariable capturedvar, BaseSsaSourceVariable 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(BaseSsaSourceVariable 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(BaseSsaSourceVariable v, BasicBlock b) { - exists(int i | variableUseOrCapture(v, b, i) | - not exists(int j | variableUpdate(v, _, b, j) | j < i) - ) - or - liveAtExit(v, b) and not variableUpdate(v, _, b, _) - } - - private predicate liveAtExit(BaseSsaSourceVariable v, BasicBlock b) { - liveAtEntry(v, b.getABBSuccessor()) - } - - /** Holds if a phi node for `v` is needed at the beginning of basic block `b`. */ - cached - predicate phiNode(BaseSsaSourceVariable 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(BaseSsaSourceVariable v, BasicBlock b) { exists(LocalScopeVariable l, Callable c | v = TLocalVar(c, l) and c.getBody().getControlFlowNode() = b @@ -171,236 +133,110 @@ private module BaseSsaImpl { l.getCallable() != c ) } +} - /** - * 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(BaseSsaSourceVariable 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(BaseSsaSourceVariable v, BasicBlock b) { - result = max(int rankix | defUseRank(v, b, rankix, _)) - } +private import BaseSsaImpl - /** Holds if a definition of an SSA variable occurs at the specified rank index in basic block `b`. */ - private predicate ssaDefRank( - BaseSsaSourceVariable v, TrackedSsaDef def, BasicBlock b, int rankix - ) { - exists(int i | - def.definesAt(v, b, i) and - defUseRank(v, b, rankix, i) - ) - } +private module SsaInput implements SsaImplCommon::InputSig { + private import java as J + private import semmle.code.java.controlflow.Dominance as Dom - /** Holds if the SSA definition reaches the rank index `rankix` in its own basic block `b`. */ - private predicate ssaDefReachesRank( - BaseSsaSourceVariable 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) - } + class BasicBlock = J::BasicBlock; - /** - * 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(BaseSsaSourceVariable 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, _) - ) - ) - } + class ControlFlowNode = J::ControlFlowNode; - /** - * 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( - BaseSsaSourceVariable 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) - ) - } + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { Dom::bbIDominates(result, bb) } - /** - * Holds if the SSA definition of `v` at `def` reaches `use` without crossing another - * SSA definition of `v`. - */ - cached - predicate ssaDefReachesUse(BaseSsaSourceVariable 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) - ) - } + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getABBSuccessor() } - /** - * 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( - BaseSsaSourceVariable v, TrackedSsaDef def, BaseSsaSourceVariable 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) - ) - } + class SourceVariable = BaseSsaSourceVariable; - /** - * Holds if the SSA definition of `v` at `def` reaches capture point of - * `closurevar` without crossing another SSA definition of `v`. - */ - cached - predicate ssaDefReachesCapture( - BaseSsaSourceVariable v, TrackedSsaDef def, BaseSsaSourceVariable 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 `i`th node of basic block `bb` is a write to source variable + * `v`. + */ + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + variableUpdate(v, _, bb, i) and + certain = true + or + hasEntryDef(v, bb) and + i = 0 and + certain = true } - 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(BaseSsaSourceVariable 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(BaseSsaSourceVariable v, BasicBlock b) { - result = max(int rankix | defUseRank(v, b, rankix, _)) - } + /** + * Holds if the `i`th of basic block `bb` reads source variable `v`. + */ + 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 + } +} - /** Holds if `v` is defined or used in `b`. */ - private predicate varOccursInBlock(BaseSsaSourceVariable v, BasicBlock b) { - defUseRank(v, b, _, _) - } +private module Impl = SsaImplCommon::Make; - /** Holds if `v` occurs in `b` or one of `b`'s transitive successors. */ - private predicate blockPrecedesVar(BaseSsaSourceVariable v, BasicBlock b) { - varOccursInBlock(v, b) - or - ssaDefReachesEndOfBlock(v, _, b) - } +private import Cached - /** - * 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(BaseSsaSourceVariable 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) - ) - } +cached +private module Cached { + cached + VarRead getAUse(Impl::Definition def) { + exists(BaseSsaSourceVariable v, BasicBlock bb, int i | + Impl::ssaDefReachesRead(v, def, bb, i) and + result.getControlFlowNode() = bb.getNode(i) and + result = v.getAnAccess() + ) + } - /** - * 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(BaseSsaSourceVariable v, BasicBlock b1, BasicBlock b2) { - varBlockReaches(v, b1, b2) and - varOccursInBlock(v, b2) - } + cached + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Impl::Definition def) { + Impl::ssaDefReachesEndOfBlock(bb, def, _) + } - /** - * 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(BaseSsaSourceVariable 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) - } + cached + predicate firstUse(Impl::Definition def, VarRead use) { + exists(BasicBlock bb, int i | + Impl::firstUse(def, bb, i, _) and + use.getControlFlowNode() = bb.getNode(i) + ) } - private import AdjacentUsesImpl + cached + predicate ssaUpdate(Impl::Definition def, VariableUpdate upd) { + exists(BaseSsaSourceVariable v, BasicBlock bb, int i | + def.definesAt(v, bb, i) and + variableUpdate(v, upd.getControlFlowNode(), bb, i) and + getDestVar(upd) = v + ) + } - /** - * Holds if the value defined at `def` can reach `use` without passing through - * any other uses, but possibly through phi nodes. - */ cached - predicate firstUse(TrackedSsaDef def, VarRead use) { - exists(BaseSsaSourceVariable 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) + predicate ssaImplicitInit(Impl::WriteDefinition def) { + exists(BaseSsaSourceVariable v, BasicBlock bb, int i | + def.definesAt(v, bb, i) and + hasEntryDef(v, bb) and + i = 0 ) - or - exists( - BaseSsaSourceVariable v, TrackedSsaDef redef, BasicBlock b1, int i1, BasicBlock b2, int i2 - | - redef instanceof BaseSsaPhiNode - | - adjacentVarRefs(v, b1, i1, b2, i2) and - def.definesAt(v, b1, i1) and - redef.definesAt(v, b2, i2) and - firstUse(redef, use) + } + + /** Holds if `init` is a closure variable that captures the value of `capturedvar`. */ + cached + predicate captures(BaseSsaImplicitInit init, BaseSsaVariable capturedvar) { + exists(BasicBlock bb, int i | + Impl::ssaDefReachesRead(_, capturedvar, bb, i) and + variableCapture(capturedvar.getSourceVariable(), init.getSourceVariable(), bb, i) ) } + cached + predicate phiHasInputFromBlock(Impl::PhiNode phi, Impl::Definition inp, BasicBlock bb) { + Impl::phiHasInputFromBlock(phi, inp, bb) + } + cached module SsaPublic { /** @@ -410,10 +246,10 @@ private module BaseSsaImpl { */ cached predicate baseSsaAdjacentUseUseSameVar(VarRead use1, VarRead use2) { - exists(BaseSsaSourceVariable 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) + exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + use1.getControlFlowNode() = bb1.getNode(i1) and + use2.getControlFlowNode() = bb2.getNode(i2) and + Impl::adjacentUseUse(bb1, i1, bb2, i2, _, true) ) } @@ -425,78 +261,28 @@ private module BaseSsaImpl { */ cached predicate baseSsaAdjacentUseUse(VarRead use1, VarRead use2) { - baseSsaAdjacentUseUseSameVar(use1, use2) - or - exists( - BaseSsaSourceVariable 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 BaseSsaPhiNode + exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + use1.getControlFlowNode() = bb1.getNode(i1) and + use2.getControlFlowNode() = bb2.getNode(i2) and + Impl::adjacentUseUse(bb1, i1, bb2, i2, _, _) ) } } } -private import BaseSsaImpl -private import SsaDefReaches import SsaPublic -private newtype TBaseSsaVariable = - TSsaPhiNode(BaseSsaSourceVariable v, BasicBlock b) { phiNode(v, b) } or - TSsaUpdate(BaseSsaSourceVariable v, ControlFlowNode n, BasicBlock b, int i) { - variableUpdate(v, n, b, i) - } or - TSsaEntryDef(BaseSsaSourceVariable v, BasicBlock b) { hasEntryDef(v, b) } - -/** - * An SSA definition excluding those variables that use a trivial SSA construction. - */ -private class TrackedSsaDef extends BaseSsaVariable { - /** - * Holds if this SSA definition occurs at the specified position. - * Phi nodes are placed at index -1. - */ - predicate definesAt(BaseSsaSourceVariable v, BasicBlock b, int i) { - this = TSsaPhiNode(v, b) and i = -1 - or - this = TSsaUpdate(v, _, b, i) - or - this = TSsaEntryDef(v, b) and i = 0 - } -} - /** * An SSA variable. */ -class BaseSsaVariable extends TBaseSsaVariable { - /** Gets the SSA source variable underlying this SSA variable. */ - BaseSsaSourceVariable getSourceVariable() { - this = TSsaPhiNode(result, _) or - this = TSsaUpdate(result, _, _, _) or - this = TSsaEntryDef(result, _) - } - +class BaseSsaVariable extends Impl::Definition { /** Gets the `ControlFlowNode` at which this SSA variable is defined. */ ControlFlowNode getCfgNode() { - this = TSsaPhiNode(_, result) or - this = TSsaUpdate(_, result, _, _) or - this = TSsaEntryDef(_, result) + exists(BasicBlock bb, int i | this.definesAt(_, bb, i) and result = bb.getNode(0.maximum(i))) } - /** Gets a textual representation of this element. */ - string toString() { none() } - - /** Gets the source location for this element. */ - Location getLocation() { result = this.getCfgNode().getLocation() } - - /** Gets the `BasicBlock` in which this SSA variable is defined. */ - BasicBlock getBasicBlock() { result = this.getCfgNode().getBasicBlock() } - /** Gets an access of this SSA variable. */ - VarRead getAUse() { ssaDefReachesUse(_, this, result) } + VarRead getAUse() { result = getAUse(this) } /** * Gets an access of the SSA source variable underlying this SSA variable @@ -509,7 +295,7 @@ class BaseSsaVariable extends TBaseSsaVariable { 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 input to the phi node defining the SSA variable. */ private BaseSsaVariable getAPhiInput() { result = this.(BaseSsaPhiNode).getAPhiInput() } @@ -536,33 +322,22 @@ class BaseSsaVariable extends TBaseSsaVariable { } /** An SSA variable that is defined by a `VariableUpdate`. */ -class BaseSsaUpdate extends BaseSsaVariable, TSsaUpdate { - BaseSsaUpdate() { - exists(VariableUpdate upd | - upd.getControlFlowNode() = this.getCfgNode() and getDestVar(upd) = this.getSourceVariable() - ) - } - - override string toString() { result = "SSA def(" + this.getSourceVariable() + ")" } +class BaseSsaUpdate extends BaseSsaVariable instanceof Impl::WriteDefinition { + BaseSsaUpdate() { ssaUpdate(this, _) } /** Gets the `VariableUpdate` defining the SSA variable. */ - VariableUpdate getDefiningExpr() { - result.getControlFlowNode() = this.getCfgNode() and - getDestVar(result) = this.getSourceVariable() - } + VariableUpdate getDefiningExpr() { ssaUpdate(this, result) } } /** * An SSA variable that is defined by its initial value in the callable. This * includes initial values of parameters, fields, and closure variables. */ -class BaseSsaImplicitInit extends BaseSsaVariable, TSsaEntryDef { - override string toString() { result = "SSA init(" + this.getSourceVariable() + ")" } +class BaseSsaImplicitInit extends BaseSsaVariable instanceof Impl::WriteDefinition { + BaseSsaImplicitInit() { ssaImplicitInit(this) } /** Holds if this is a closure variable that captures the value of `capturedvar`. */ - predicate captures(BaseSsaVariable capturedvar) { - ssaDefReachesCapture(_, capturedvar, this.getSourceVariable()) - } + predicate captures(BaseSsaVariable capturedvar) { captures(this, capturedvar) } /** * Holds if the SSA variable is a parameter defined by its initial value in the callable. @@ -574,15 +349,7 @@ class BaseSsaImplicitInit extends BaseSsaVariable, TSsaEntryDef { } /** An SSA phi node. */ -class BaseSsaPhiNode extends BaseSsaVariable, TSsaPhiNode { - override string toString() { result = "SSA phi(" + this.getSourceVariable() + ")" } - +class BaseSsaPhiNode extends BaseSsaVariable instanceof Impl::PhiNode { /** Gets an input to the phi node defining the SSA variable. */ - BaseSsaVariable getAPhiInput() { - exists(BasicBlock phiPred, BaseSsaSourceVariable v | - v = this.getSourceVariable() and - this.getCfgNode().(BasicBlock).getABBPredecessor() = phiPred and - ssaDefReachesEndOfBlock(v, result, phiPred) - ) - } + BaseSsaVariable getAPhiInput() { phiHasInputFromBlock(this, result, _) } }