mirror of
https://github.com/github/codeql.git
synced 2025-12-17 17:23:36 +01:00
744 lines
25 KiB
Plaintext
744 lines
25 KiB
Plaintext
/**
|
|
* Provides classes for working with static single assignment form (SSA).
|
|
*
|
|
* We compute SSA form based on the intra-procedural CFG, without
|
|
* any call graph information. This means that we have to make worst-case
|
|
* assumptions about the possible effects of function calls and `yield`:
|
|
*
|
|
* - For a variable `x` declared in a function `f`, if `x` has assignments
|
|
* in a function other than `f`, then any function call and `yield`
|
|
* expression is assumed to write `x`.
|
|
* - If `x` is not written outside `f`, then function calls can never
|
|
* affect `x`, while `yield` expressions in functions other than `f`
|
|
* still may affect it.
|
|
*
|
|
* This is modeled as follows.
|
|
*
|
|
* Within each function `g` that accesses a variable `x` declared in an
|
|
* enclosing function `f`, we introduce a pseudo-assignment to `x` called
|
|
* a _capture_ of `x` at the beginning of `g` that (conceptually) captures
|
|
* the current value of `x`.
|
|
*
|
|
* Additionally, we introduce _re-captures_ for `x` in the following
|
|
* places:
|
|
*
|
|
* - At any function call and `yield`, if `x` is assigned outside `f`.
|
|
* - At any `yield` outside `f`, if `x` is not assigned outside `f`.
|
|
*
|
|
* Re-captures are introduced only where needed, that is, where there
|
|
* is a live use of `x` after the re-capture.
|
|
*
|
|
* To see why re-captures need to be placed at `yield` expressions,
|
|
* consider the following function:
|
|
*
|
|
* ```
|
|
* function k() {
|
|
* var x = 0;
|
|
*
|
|
* function* iter() {
|
|
* console.log(x);
|
|
* yield;
|
|
* console.log(x);
|
|
* }
|
|
*
|
|
* var gen = iter();
|
|
* gen.next();
|
|
* ++x;
|
|
* gen.next();
|
|
* }
|
|
* ```
|
|
*
|
|
* Here, `iter` has a capture for `x` at its beginning, and a re-capture
|
|
* at the `yield` to reflect the fact that `x` is incremented between the
|
|
* two `console.log` calls.
|
|
*
|
|
* In the above example, `x` is only assigned inside its declaring function
|
|
* `k`, so function calls and `yield` expressions inside `k` cannot affect it.
|
|
*
|
|
* Consider another example:
|
|
*
|
|
* ```
|
|
* function* k() {
|
|
* var x = 0;
|
|
* console.log(x);
|
|
* yield () => ++x;
|
|
* console.log(x);
|
|
* }
|
|
* var gen = k();
|
|
* gen.next().value();
|
|
* gen.next();
|
|
* ```
|
|
*
|
|
* Here, `x` is assigned outside its declaring function `k`, so the `yield`
|
|
* expression in `k` induces a re-capture of `x` to reflect the fact that `x`
|
|
* is incremented between the two `console.log` calls.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.dataflow.Refinements
|
|
private import semmle.javascript.internal.CachedStages
|
|
|
|
/**
|
|
* A variable that can be SSA converted, that is, a local variable.
|
|
*/
|
|
class SsaSourceVariable extends LocalVariable { }
|
|
|
|
cached
|
|
private module Internal {
|
|
/**
|
|
* A data type representing SSA definitions.
|
|
*
|
|
* We distinguish five kinds of SSA definitions:
|
|
*
|
|
* 1. Explicit definitions wrapping a `VarDef` node in the CFG.
|
|
* 2. Implicit initializations of locals (including `arguments`) at
|
|
* the start of a function, which do not correspond directly to
|
|
* CFG nodes.
|
|
* 3. Pseudo-definitions for captured variables at the beginning of
|
|
* the capturing function as well as after `yield` expressions
|
|
* and calls.
|
|
* 4. Phi nodes.
|
|
* 5. Refinement nodes at points in the CFG where additional information
|
|
* about a variable becomes available, which may constrain the set of
|
|
* its potential values.
|
|
*
|
|
* SSA definitions are only introduced where necessary. In particular,
|
|
* unreachable code has no SSA definitions associated with it, and neither
|
|
* have dead assignments (that is, assignments whose value is never read).
|
|
*/
|
|
cached
|
|
newtype TSsaDefinition =
|
|
TExplicitDef(ReachableBasicBlock bb, int i, VarDef d, SsaSourceVariable v, VarRef lhs) {
|
|
bb.defAt(i, v, d, lhs) and
|
|
(
|
|
liveAfterDef(bb, i, v) or
|
|
v.isCaptured()
|
|
)
|
|
} or
|
|
TImplicitInit(EntryBasicBlock bb, SsaSourceVariable v) {
|
|
bb.getContainer() = v.getDeclaringContainer().getFunctionBoundary() and
|
|
(liveAtEntry(bb, v) or v.isCaptured())
|
|
} or
|
|
TCapture(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
mayCapture(bb, i, v) and liveAfterDef(bb, i, v)
|
|
} or
|
|
TPhi(ReachableJoinBlock bb, SsaSourceVariable v) {
|
|
liveAtEntry(bb, v) and
|
|
inDefDominanceFrontier(bb, v)
|
|
} or
|
|
TRefinement(ReachableBasicBlock bb, int i, GuardControlFlowNode guard, SsaSourceVariable v) {
|
|
bb.getNode(i) = guard and
|
|
guard.getTest().(Refinement).getRefinedVar() = v and
|
|
liveAtEntry(bb, v)
|
|
}
|
|
|
|
/**
|
|
* Holds if `bb` is in the dominance frontier of a block containing a definition of `v`.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate inDefDominanceFrontier(ReachableJoinBlock bb, SsaSourceVariable v) {
|
|
exists(ReachableBasicBlock defbb, SsaDefinition def |
|
|
def.definesAt(defbb, _, v) and
|
|
bb.inDominanceFrontierOf(defbb)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `v` is a captured variable which is declared in `declContainer` and read in
|
|
* `useContainer`.
|
|
*/
|
|
private predicate readsCapturedVar(
|
|
StmtContainer useContainer, SsaSourceVariable v, StmtContainer declContainer
|
|
) {
|
|
declContainer = v.getDeclaringContainer() and
|
|
useContainer = any(VarUse u | u.getVariable() = v).getContainer() and
|
|
v.isCaptured()
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th node of `bb` in container `sc` is entry node `nd`.
|
|
*/
|
|
private predicate entryNode(
|
|
StmtContainer sc, ReachableBasicBlock bb, int i, ControlFlowEntryNode nd
|
|
) {
|
|
sc = bb.getContainer() and bb.getNode(i) = nd
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th node of `bb` in container `sc` is yield expression `nd`.
|
|
*/
|
|
private predicate yieldNode(StmtContainer sc, ReachableBasicBlock bb, int i, YieldExpr nd) {
|
|
sc = bb.getContainer() and bb.getNode(i) = nd
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th node of `bb` in container `sc` is invocation expression `nd`.
|
|
*/
|
|
private predicate invokeNode(StmtContainer sc, ReachableBasicBlock bb, int i, InvokeExpr nd) {
|
|
sc = bb.getContainer() and bb.getNode(i) = nd
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th node of basic block `bb` may induce a pseudo-definition for
|
|
* modeling updates to captured variable `v`. Whether the definition is actually
|
|
* introduced depends on whether `v` is live at this point in the program.
|
|
*/
|
|
private predicate mayCapture(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
exists(ControlFlowNode nd, StmtContainer capturingContainer, StmtContainer declContainer |
|
|
// capture initial value of variable declared in enclosing scope
|
|
readsCapturedVar(capturingContainer, v, declContainer) and
|
|
capturingContainer != declContainer and
|
|
entryNode(capturingContainer, bb, i, nd)
|
|
or
|
|
// re-capture value of variable after `yield` if it is declared in enclosing scope
|
|
// or assigned non-locally
|
|
readsCapturedVar(capturingContainer, v, declContainer) and
|
|
(capturingContainer != declContainer or assignedThroughClosure(v)) and
|
|
yieldNode(capturingContainer, bb, i, nd)
|
|
or
|
|
// re-capture value of variable after a call if it is assigned non-locally
|
|
readsCapturedVar(capturingContainer, v, declContainer) and
|
|
assignedThroughClosure(v) and
|
|
invokeNode(capturingContainer, bb, i, nd)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A classification of variable references into reads and writes.
|
|
*/
|
|
cached
|
|
newtype RefKind =
|
|
Read() or
|
|
Write()
|
|
|
|
/**
|
|
* Holds if the `i`th node of basic block `bb` is a reference to `v`, either a read
|
|
* (when `tp` is `Read()`) or a direct or indirect write (when `tp` is `Write()`).
|
|
*/
|
|
private predicate ref(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind tp) {
|
|
bb.useAt(i, v, _) and tp = Read()
|
|
or
|
|
(mayCapture(bb, i, v) or bb.defAt(i, v, _)) and
|
|
tp = Write()
|
|
}
|
|
|
|
/**
|
|
* Gets the (1-based) rank of the reference to `v` at the `i`th node of basic block `bb`,
|
|
* which has the given reference kind `tp`.
|
|
*/
|
|
private int refRank(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind tp) {
|
|
i = rank[result](int j | ref(bb, j, v, _)) and
|
|
ref(bb, i, v, tp)
|
|
}
|
|
|
|
/**
|
|
* Gets the maximum rank among all references to `v` in basic block `bb`.
|
|
*/
|
|
private int maxRefRank(ReachableBasicBlock bb, SsaSourceVariable v) {
|
|
result = max(refRank(bb, _, v, _))
|
|
}
|
|
|
|
/**
|
|
* Holds if variable `v` is live after the `i`th node of basic block `bb`, where
|
|
* `i` is the index of a node that may assign or capture `v`.
|
|
*
|
|
* For the purposes of this predicate, `yield` expressions and function invocations
|
|
* are considered as writes of captured variables.
|
|
*/
|
|
private predicate liveAfterDef(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
exists(int r | r = refRank(bb, i, v, Write()) |
|
|
// the next reference to `v` inside `bb` is a read
|
|
r + 1 = refRank(bb, _, v, Read())
|
|
or
|
|
// this is the last reference to `v` inside `bb`, but `v` is live at entry
|
|
// to a successor basic block of `bb`
|
|
r = maxRefRank(bb, v) and
|
|
liveAtSuccEntry(bb, v)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if variable `v` is live at the beginning of basic block `bb`.
|
|
*
|
|
* For the purposes of this predicate, `yield` expressions and function invocations
|
|
* are considered as writes of captured variables.
|
|
*/
|
|
private predicate liveAtEntry(ReachableBasicBlock bb, SsaSourceVariable v) {
|
|
// the first reference to `v` inside `bb` is a read
|
|
refRank(bb, _, v, Read()) = 1
|
|
or
|
|
// there is no reference to `v` inside `bb`, but `v` is live at entry
|
|
// to a successor basic block of `bb`
|
|
not exists(refRank(bb, _, v, _)) and
|
|
liveAtSuccEntry(bb, v)
|
|
}
|
|
|
|
/**
|
|
* Holds if `v` is live at the beginning of any successor of basic block `bb`.
|
|
*/
|
|
private predicate liveAtSuccEntry(ReachableBasicBlock bb, SsaSourceVariable v) {
|
|
liveAtEntry(bb.getASuccessor(), v)
|
|
}
|
|
|
|
/**
|
|
* Holds if `v` is assigned outside its declaring function.
|
|
*/
|
|
private predicate assignedThroughClosure(SsaSourceVariable v) {
|
|
v.getAnAccess().(LValue).getContainer() != v.getDeclaringContainer()
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th node of `bb` is a use or an SSA definition of variable `v`, with
|
|
* `k` indicating whether it is the former or the latter.
|
|
*/
|
|
private predicate ssaRef(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind k) {
|
|
bb.useAt(i, v, _) and k = Read()
|
|
or
|
|
any(SsaDefinition def).definesAt(bb, i, v) and k = Write()
|
|
}
|
|
|
|
/**
|
|
* Gets the (1-based) rank of the `i`th node of `bb` among all SSA definitions
|
|
* and uses of `v` in `bb`, with `k` indicating whether it is a definition or a use.
|
|
*
|
|
* For example, if `bb` is a basic block with a phi node for `v` (considered
|
|
* to be at index -1), uses `v` at node 2 and defines it at node 5, we have:
|
|
*
|
|
* ```
|
|
* ssaRefRank(bb, -1, v, Write()) = 1 // phi node
|
|
* ssaRefRank(bb, 2, v, Read()) = 2 // use at node 2
|
|
* ssaRefRank(bb, 5, v, Write()) = 3 // definition at node 5
|
|
* ```
|
|
*/
|
|
private int ssaRefRank(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind k) {
|
|
i = rank[result](int j | ssaRef(bb, j, v, _)) and
|
|
ssaRef(bb, i, v, k)
|
|
}
|
|
|
|
/**
|
|
* Gets the minimum rank of a read in `bb` such that all references to `v` between that
|
|
* read and the read at index `i` are reads (and not writes).
|
|
*/
|
|
private int rewindReads(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
exists(int r | r = ssaRefRank(bb, i, v, Read()) |
|
|
exists(int j, RefKind k | r - 1 = ssaRefRank(bb, j, v, k) |
|
|
k = Read() and result = rewindReads(bb, j, v)
|
|
or
|
|
k = Write() and result = r
|
|
)
|
|
or
|
|
r = 1 and result = r
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the SSA definition of `v` in `bb` that reaches the read of `v` at node `i`, if any.
|
|
*/
|
|
private SsaDefinition getLocalDefinition(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
exists(int r | r = rewindReads(bb, i, v) |
|
|
exists(int j | result.definesAt(bb, j, v) and ssaRefRank(bb, j, v, _) = r - 1)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets an SSA definition of `v` that reaches the end of the immediate dominator of `bb`.
|
|
*/
|
|
pragma[noinline]
|
|
private SsaDefinition getDefReachingEndOfImmediateDominator(
|
|
ReachableBasicBlock bb, SsaSourceVariable v
|
|
) {
|
|
result = getDefReachingEndOf(bb.getImmediateDominator(), v)
|
|
}
|
|
|
|
/**
|
|
* Gets an SSA definition of `v` that reaches the end of basic block `bb`.
|
|
*/
|
|
cached
|
|
SsaDefinition getDefReachingEndOf(ReachableBasicBlock bb, SsaSourceVariable v) {
|
|
Stages::DataFlowStage::ref() and
|
|
exists(int lastRef | lastRef = max(int i | ssaRef(bb, i, v, _)) |
|
|
result = getLocalDefinition(bb, lastRef, v)
|
|
or
|
|
result.definesAt(bb, lastRef, v) and
|
|
liveAtSuccEntry(bb, v)
|
|
)
|
|
or
|
|
/*
|
|
* In SSA form, the (unique) reaching definition of a use is the closest
|
|
* definition that dominates the use. If two definitions dominate a node
|
|
* then one must dominate the other, so we can find the reaching definition
|
|
* by following the idominance relation backwards.
|
|
*/
|
|
|
|
result = getDefReachingEndOfImmediateDominator(bb, v) and
|
|
not exists(SsaDefinition ssa | ssa.definesAt(bb, _, v)) and
|
|
liveAtSuccEntry(bb, v)
|
|
}
|
|
|
|
/**
|
|
* Gets the unique SSA definition of `v` whose value reaches the `i`th node of `bb`,
|
|
* which is a use of `v`.
|
|
*/
|
|
cached
|
|
SsaDefinition getDefinition(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
result = getLocalDefinition(bb, i, v)
|
|
or
|
|
rewindReads(bb, i, v) = 1 and result = getDefReachingEndOf(bb.getImmediateDominator(), v)
|
|
}
|
|
}
|
|
|
|
private import Internal
|
|
|
|
/**
|
|
* An SSA variable.
|
|
*/
|
|
class SsaVariable extends TSsaDefinition {
|
|
/** Gets the source variable corresponding to this SSA variable. */
|
|
SsaSourceVariable getSourceVariable() { result = this.(SsaDefinition).getSourceVariable() }
|
|
|
|
/** Gets the (unique) definition of this SSA variable. */
|
|
SsaDefinition getDefinition() { result = this }
|
|
|
|
/** Gets a use in basic block `bb` that refers to this SSA variable. */
|
|
VarUse getAUseIn(ReachableBasicBlock bb) {
|
|
exists(int i, SsaSourceVariable v | v = this.getSourceVariable() |
|
|
bb.useAt(i, v, result) and this = getDefinition(bb, i, v)
|
|
)
|
|
}
|
|
|
|
/** Gets a use that refers to this SSA variable. */
|
|
VarUse getAUse() { result = this.getAUseIn(_) }
|
|
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { result = this.getDefinition().prettyPrintRef() }
|
|
|
|
/** Gets the location of this SSA variable. */
|
|
Location getLocation() { result = this.getDefinition().getLocation() }
|
|
|
|
/**
|
|
* DEPRECATED. Use `getLocation().hasLocationInfo()` instead.
|
|
*
|
|
* Holds if this element is at the specified location.
|
|
* The location spans column `startcolumn` of line `startline` to
|
|
* column `endcolumn` of line `endline` in file `filepath`.
|
|
* For more information, see
|
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
deprecated predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An SSA definition.
|
|
*/
|
|
class SsaDefinition extends TSsaDefinition {
|
|
/** Gets the SSA variable defined by this definition. */
|
|
SsaVariable getVariable() { result = this }
|
|
|
|
/** Gets the source variable defined by this definition. */
|
|
abstract SsaSourceVariable getSourceVariable();
|
|
|
|
/**
|
|
* Gets the basic block to which this definition belongs.
|
|
*
|
|
* Currently, all SSA definitions belong to a basic block, but the representation of
|
|
* implicit definitions might change in the future, making this no longer true.
|
|
*/
|
|
abstract ReachableBasicBlock getBasicBlock();
|
|
|
|
/**
|
|
* INTERNAL: Use `getBasicBlock()` and `getSourceVariable()` instead.
|
|
*
|
|
* Holds if this is a definition of source variable `v` at index `idx` in basic block `bb`.
|
|
*
|
|
* Phi nodes are considered to be at index `-1`, all other definitions at the index of
|
|
* the control flow node they correspond to.
|
|
*/
|
|
abstract predicate definesAt(ReachableBasicBlock bb, int idx, SsaSourceVariable v);
|
|
|
|
/**
|
|
* Gets a variable definition node whose value may end up contributing
|
|
* to the SSA variable defined by this definition.
|
|
*/
|
|
abstract VarDef getAContributingVarDef();
|
|
|
|
/**
|
|
* INTERNAL: Use `toString()` instead.
|
|
*
|
|
* Gets a pretty-printed representation of this SSA definition.
|
|
*/
|
|
abstract string prettyPrintDef();
|
|
|
|
/**
|
|
* INTERNAL: Do not use.
|
|
*
|
|
* Gets a pretty-printed representation of a reference to this SSA definition.
|
|
*/
|
|
abstract string prettyPrintRef();
|
|
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { result = this.prettyPrintDef() }
|
|
|
|
/**
|
|
* DEPRECATED. Use `getLocation().hasLocationInfo()` instead.
|
|
*
|
|
* Holds if this element is at the specified location.
|
|
* The location spans column `startcolumn` of line `startline` to
|
|
* column `endcolumn` of line `endline` in file `filepath`.
|
|
* For more information, see
|
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
deprecated predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
/** Gets the location of this element. */
|
|
Location getLocation() { result = this.getBasicBlock().getLocation() }
|
|
|
|
/** Gets the function or toplevel to which this definition belongs. */
|
|
StmtContainer getContainer() { result = this.getBasicBlock().getContainer() }
|
|
}
|
|
|
|
/**
|
|
* An SSA definition that corresponds to an explicit assignment or other variable definition.
|
|
*/
|
|
class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
|
|
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
this = TExplicitDef(bb, i, _, v, _)
|
|
}
|
|
|
|
/** This SSA definition corresponds to the definition of `v` at `def`. */
|
|
predicate defines(VarDef def, SsaSourceVariable v) { this = TExplicitDef(_, _, def, v, _) }
|
|
|
|
/** Gets the variable definition wrapped by this SSA definition. */
|
|
VarDef getDef() { this = TExplicitDef(_, _, result, _, _) }
|
|
|
|
/** Gets the variable reference appearing on the left-hand side of this assignment. */
|
|
VarRef getLhs() { this = TExplicitDef(_, _, _, _, result) }
|
|
|
|
/** Gets the basic block to which this definition belongs. */
|
|
override ReachableBasicBlock getBasicBlock() { this.definesAt(result, _, _) }
|
|
|
|
override SsaSourceVariable getSourceVariable() { this = TExplicitDef(_, _, _, result, _) }
|
|
|
|
override VarDef getAContributingVarDef() { result = this.getDef() }
|
|
|
|
override string prettyPrintRef() {
|
|
exists(int l, int c | this.getLocation().hasLocationInfo(_, l, c, _, _) |
|
|
result = "def@" + l + ":" + c
|
|
)
|
|
}
|
|
|
|
override string prettyPrintDef() { result = this.getDef().toString() }
|
|
|
|
override Location getLocation() { result = this.getLhs().getLocation() }
|
|
|
|
/**
|
|
* Gets the data flow node representing the incoming value assigned at this definition,
|
|
* if any.
|
|
*/
|
|
DataFlow::Node getRhsNode() {
|
|
result = DataFlow::ssaDefinitionNode(this).getImmediatePredecessor()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An SSA definition that does not correspond to an explicit variable definition.
|
|
*/
|
|
abstract class SsaImplicitDefinition extends SsaDefinition {
|
|
/**
|
|
* INTERNAL: Do not use.
|
|
*
|
|
* Gets the definition kind to include in `prettyPrintRef`.
|
|
*/
|
|
abstract string getKind();
|
|
|
|
override string prettyPrintRef() {
|
|
exists(int l, int c | this.getLocation().hasLocationInfo(_, l, c, _, _) |
|
|
result = this.getKind() + "@" + l + ":" + c
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An SSA definition representing the implicit initialization of a variable
|
|
* at the beginning of its scope.
|
|
*/
|
|
class SsaImplicitInit extends SsaImplicitDefinition, TImplicitInit {
|
|
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
bb = this.getBasicBlock() and v = this.getSourceVariable() and i = 0
|
|
}
|
|
|
|
override ReachableBasicBlock getBasicBlock() { this = TImplicitInit(result, _) }
|
|
|
|
override SsaSourceVariable getSourceVariable() { this = TImplicitInit(_, result) }
|
|
|
|
override string getKind() { result = "implicitInit" }
|
|
|
|
override VarDef getAContributingVarDef() { none() }
|
|
|
|
override string prettyPrintDef() {
|
|
result = "implicit initialization of " + this.getSourceVariable()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An SSA definition representing the capturing of an SSA-convertible variable
|
|
* in the closure of a nested function.
|
|
*
|
|
* Capturing definitions appear at the beginning of such functions, as well as
|
|
* at any `yield` expressions or calls that may affect the value of the variable.
|
|
*/
|
|
class SsaVariableCapture extends SsaImplicitDefinition, TCapture {
|
|
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
this = TCapture(bb, i, v)
|
|
}
|
|
|
|
override ReachableBasicBlock getBasicBlock() { this.definesAt(result, _, _) }
|
|
|
|
override SsaSourceVariable getSourceVariable() { this.definesAt(_, _, result) }
|
|
|
|
override VarDef getAContributingVarDef() { result.getAVariable() = this.getSourceVariable() }
|
|
|
|
override string getKind() { result = "capture" }
|
|
|
|
override string prettyPrintDef() { result = "capture variable " + this.getSourceVariable() }
|
|
}
|
|
|
|
/**
|
|
* An SSA definition that has no actual semantics, but simply serves to
|
|
* merge or filter data flow.
|
|
*
|
|
* Phi nodes are the canonical example.
|
|
*/
|
|
abstract class SsaPseudoDefinition extends SsaImplicitDefinition {
|
|
/**
|
|
* Gets an input of this pseudo-definition.
|
|
*/
|
|
cached
|
|
abstract SsaVariable getAnInput();
|
|
|
|
override VarDef getAContributingVarDef() {
|
|
result = this.getAnInput().getDefinition().getAContributingVarDef()
|
|
}
|
|
|
|
/**
|
|
* Gets a textual representation of the inputs of this pseudo-definition
|
|
* in lexicographical order.
|
|
*/
|
|
string ppInputs() { result = concat(this.getAnInput().getDefinition().prettyPrintRef(), ", ") }
|
|
}
|
|
|
|
/**
|
|
* An SSA phi node, that is, a pseudo-definition for a variable at a point
|
|
* in the flow graph where otherwise two or more definitions for the variable
|
|
* would be visible.
|
|
*/
|
|
class SsaPhiNode extends SsaPseudoDefinition, TPhi {
|
|
/**
|
|
* Gets the input to this phi node coming from the given predecessor block.
|
|
*/
|
|
cached
|
|
SsaVariable getInputFromBlock(BasicBlock bb) {
|
|
bb = this.getBasicBlock().getAPredecessor() and
|
|
result = getDefReachingEndOf(bb, this.getSourceVariable())
|
|
}
|
|
|
|
override SsaVariable getAnInput() { result = this.getInputFromBlock(_) }
|
|
|
|
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
bb = this.getBasicBlock() and v = this.getSourceVariable() and i = -1
|
|
}
|
|
|
|
override ReachableBasicBlock getBasicBlock() { this = TPhi(result, _) }
|
|
|
|
override SsaSourceVariable getSourceVariable() { this = TPhi(_, result) }
|
|
|
|
override string getKind() { result = "phi" }
|
|
|
|
override string prettyPrintDef() {
|
|
result = this.getSourceVariable() + " = phi(" + this.ppInputs() + ")"
|
|
}
|
|
|
|
/**
|
|
* If all inputs to this phi node are (transitive) refinements of the same variable,
|
|
* gets that variable.
|
|
*/
|
|
SsaVariable getRephinedVariable() {
|
|
forex(SsaVariable input | input = this.getAnInput() | result = getRefinedVariable(input))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the input to the given refinement node or rephinement node.
|
|
*/
|
|
private SsaVariable getRefinedVariable(SsaVariable v) {
|
|
result = getRefinedVariable(v.(SsaRefinementNode).getAnInput())
|
|
or
|
|
result = getRefinedVariable(v.(SsaPhiNode).getRephinedVariable())
|
|
or
|
|
not v instanceof SsaRefinementNode and
|
|
not v instanceof SsaPhiNode and
|
|
result = v
|
|
}
|
|
|
|
/**
|
|
* A refinement node, that is, a pseudo-definition for a variable at a point
|
|
* in the flow graph where additional information about this variable becomes
|
|
* available that may restrict its possible set of values.
|
|
*/
|
|
class SsaRefinementNode extends SsaPseudoDefinition, TRefinement {
|
|
/**
|
|
* Gets the guard that induces this refinement.
|
|
*/
|
|
GuardControlFlowNode getGuard() { this = TRefinement(_, _, result, _) }
|
|
|
|
/**
|
|
* Gets the refinement associated with this definition.
|
|
*/
|
|
Refinement getRefinement() { result = this.getGuard().getTest() }
|
|
|
|
override SsaVariable getAnInput() {
|
|
exists(SsaSourceVariable v, BasicBlock bb |
|
|
v = this.getSourceVariable() and bb = this.getBasicBlock()
|
|
|
|
|
if exists(SsaPhiNode phi | phi.definesAt(bb, _, v))
|
|
then result.(SsaPhiNode).definesAt(bb, _, v)
|
|
else result = getDefReachingEndOf(bb.getAPredecessor(), v)
|
|
)
|
|
}
|
|
|
|
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
|
|
this = TRefinement(bb, i, _, v)
|
|
}
|
|
|
|
override ReachableBasicBlock getBasicBlock() { this = TRefinement(result, _, _, _) }
|
|
|
|
override SsaSourceVariable getSourceVariable() { this = TRefinement(_, _, _, result) }
|
|
|
|
override string getKind() { result = "refine[" + this.getGuard() + "]" }
|
|
|
|
override string prettyPrintDef() {
|
|
result =
|
|
this.getSourceVariable() + " = refine[" + this.getGuard() + "](" + this.ppInputs() + ")"
|
|
}
|
|
|
|
override Location getLocation() { result = this.getGuard().getLocation() }
|
|
}
|
|
|
|
module Ssa {
|
|
/** Gets the SSA definition corresponding to the implicit initialization of `v`. */
|
|
SsaImplicitInit implicitInit(SsaSourceVariable v) { result.getSourceVariable() = v }
|
|
|
|
/** Gets the SSA definition corresponding to `d`. */
|
|
SsaExplicitDefinition definition(VarDef d) { result.getDef() = d }
|
|
|
|
/** Gets the SSA variable corresponding to `d`. */
|
|
SsaVariable variable(VarDef d) { result.getDefinition() = definition(d) }
|
|
}
|