PS: Make SSA compile again.

This commit is contained in:
Mathias Vorreiter Pedersen
2025-03-25 17:12:17 +00:00
parent c2e24ea3a4
commit 8f9bc1e4b2
2 changed files with 133 additions and 193 deletions

View File

@@ -6,11 +6,10 @@
* Provides classes for working with static single assignment (SSA) form.
*/
module Ssa {
private import semmle.code.powershell.Cfg
private import powershell
private import semmle.code.powershell.Cfg
private import internal.SsaImpl as SsaImpl
private import CfgNodes
private import ExprNodes
private import CfgNodes::ExprNodes
/** A static single assignment (SSA) definition. */
class Definition extends SsaImpl::Definition {
@@ -23,8 +22,10 @@ module Ssa {
exists(BasicBlock bb, int i | this.definesAt(_, bb, i) | result = bb.getNode(i))
}
/** Gets a control-flow node that reads the value of this SSA definition. */
final AstCfgNode getARead() { result = SsaImpl::getARead(this) }
/**
* Gets a control-flow node that reads the value of this SSA definition.
*/
final VarReadAccessCfgNode getARead() { result = SsaImpl::getARead(this) }
/**
* Gets a first control-flow node that reads the value of this SSA definition.
@@ -38,16 +39,14 @@ module Ssa {
* That is, a read that can reach the end of the enclosing CFG scope, or another
* SSA definition for the source variable, without passing through any other read.
*/
final VarReadAccessCfgNode getALastRead() { SsaImpl::lastRead(this, result) }
deprecated final VarReadAccessCfgNode getALastRead() { SsaImpl::lastRead(this, result) }
/**
* Holds if `read1` and `read2` are adjacent reads of this SSA definition.
* That is, `read2` can be reached from `read1` without passing through
* another read.
*/
final predicate hasAdjacentReads(
VarReadAccessCfgNode read1, VarReadAccessCfgNode read2
) {
final predicate hasAdjacentReads(VarReadAccessCfgNode read1, VarReadAccessCfgNode read2) {
SsaImpl::adjacentReadPair(this, read1, read2)
}
@@ -91,7 +90,7 @@ module Ssa {
/**
* Holds if this SSA definition assigns `value` to the underlying variable.
*/
predicate assigns(CfgNodes::StmtCfgNode value) {
predicate assigns(CfgNodes::ExprCfgNode value) {
exists(CfgNodes::StmtNodes::AssignStmtCfgNode a, BasicBlock bb, int i |
this.definesAt(_, bb, i) and
a = bb.getNode(i) and
@@ -104,19 +103,19 @@ module Ssa {
final override Location getLocation() { result = write.getLocation() }
}
class ParameterDefinition extends Definition, SsaImpl::WriteDefinition {
private Variable v;
/**
* An SSA definition that corresponds to the value of `this` upon entry to a method.
*/
class ThisDefinition extends Definition, SsaImpl::WriteDefinition {
private ThisParameter v;
ParameterDefinition() {
exists(BasicBlock bb, int i |
this.definesAt(v, bb, i) and
SsaImpl::parameterWrite(bb, i, v)
)
}
ThisDefinition() { exists(BasicBlock bb, int i | this.definesAt(v, bb, i)) }
final override string toString() { result = "<parameter> " + v }
override ThisParameter getSourceVariable() { result = v }
final override Location getLocation() { result = v.getLocation() }
final override string toString() { result = "self (" + v.getDeclaringScope() + ")" }
final override Location getLocation() { result = this.getControlFlowNode().getLocation() }
}
/**
@@ -138,7 +137,7 @@ module Ssa {
final override Location getLocation() { result = this.getBasicBlock().getLocation() }
}
/** A phi node. */
/** phi node. */
class PhiNode extends Definition, SsaImpl::PhiNode {
/** Gets an input of this phi node. */
final Definition getAnInput() { this.hasInputFromBlock(result, _) }
@@ -148,7 +147,20 @@ module Ssa {
inp = SsaImpl::phiHasInputFromBlock(this, bb)
}
override string toString() { result = "phi" }
private string getSplitString() {
result = this.getBasicBlock().getFirstNode().(CfgNodes::AstCfgNode).getSplitsString()
}
override string toString() {
exists(string prefix |
prefix = "[" + this.getSplitString() + "] "
or
not exists(this.getSplitString()) and
prefix = ""
|
result = prefix + "phi"
)
}
/**
* The location of a phi node is the same as the location of the first node

View File

@@ -19,7 +19,7 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
class SourceVariable = LocalScopeVariable;
class SourceVariable = Variable;
/**
* Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`.
@@ -27,23 +27,21 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
*/
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
(
exists(Scope scope | scope = v.(ThisParameter).getDeclaringScope() |
// We consider the `this` variable to have a single write at the entry to a method block
scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and
i = 0
)
or
uninitializedWrite(bb, i, v)
or
parameterWrite(bb, i, v)
or
variableWriteActual(bb, i, v, _)
or
pipelineIteratorWrite(bb, i, v)
) and
certain = true
}
predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) {
(
variableReadActual(bb, i, v)
or
pipelineRead(bb, i, v)
) and
predicate variableRead(BasicBlock bb, int i, Variable v, boolean certain) {
variableReadActual(bb, i, v) and
certain = true
}
}
@@ -61,101 +59,20 @@ class PhiNode = Impl::PhiNode;
module Consistency = Impl::Consistency;
/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */
predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, LocalVariable v) {
v.getDeclaringScope() = bb.getScope() and
i = -1
}
predicate pipelineIteratorWrite(Cfg::BasicBlock bb, int i, LocalScopeVariable v) {
exists(ProcessBlockCfgNode process | process = bb.getNode(i) |
v.(PipelineIteratorVariable).getProcessBlock() = process.getAstNode()
or
v.(PipelineByPropertyNameIteratorVariable).getProcessBlock() = process.getAstNode()
)
}
/**
* Gets index of `p` in a version of the enclosing function where the parameter
* list is reversed.
*
* For example, given
* ```ps
* function f($a, $b) { ... }
* ```
* the inverted index of `$a` is 1, and the inverted index of `$b` is 0.
*
* The inverted index of `$this` is always always the number of
* parameters (excluding `this`).
*/
private int getInvertedIndex(Parameter p) {
p.isThis() and
result = p.getFunction().getNumberOfParameters()
or
exists(int i |
p.getIndex() = i or
p.hasParameterBlock(_, i)
|
result = p.getFunction().getNumberOfParameters() - i - 1
)
}
/**
* Holds if the the SSA definition of `p` should be placed at index `i` in
* block `bb`. Note that the index may be negative.
*/
predicate parameterWrite(Cfg::EntryBasicBlock bb, int i, Parameter p) {
exists(Function f |
f.getEntryBasicBlock() = bb and
p.getFunction() = f and
// If the enclosing function has 2 parameters we map the index of parameter
// 0 to -2, the index of parameter 1 to -1.
i = -getInvertedIndex(p) - 1
)
}
private LocalScopeVariable getPipelineIterator(LocalScopeVariable pipelineParam) {
result.(PipelineIteratorVariable).getProcessBlock().getScriptBlock() =
pipelineParam.(PipelineParameter).getDeclaringScope()
or
result.(PipelineByPropertyNameIteratorVariable).getParameter() =
pipelineParam.(PipelineByPropertyNameParameter)
}
private predicate isPipelineIteratorVarAccess(VarAccessCfgNode va) {
(
va.getVariable() instanceof PipelineParameter or
va.getVariable() instanceof PipelineByPropertyNameParameter
) and
va.getAstNode().getParent*() instanceof ProcessBlock
predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, Variable v) {
bb.getNode(i).getAstNode() = v
}
/** Holds if `v` is read at index `i` in basic block `bb`. */
private predicate variableReadActual(Cfg::BasicBlock bb, int i, SsaInput::SourceVariable v) {
exists(VarReadAccessCfgNode read, SsaInput::SourceVariable w |
read.getVariable() = w and read = bb.getNode(i)
|
if isPipelineIteratorVarAccess(read) then v = getPipelineIterator(w) else v = w
)
}
private predicate pipelineRead(Cfg::BasicBlock bb, int i, SsaInput::SourceVariable v) {
exists(ProcessBlockCfgNode process | process = bb.getNode(i) |
v = process.getPipelineParameter() or
v = process.getAPipelineByPropertyNameParameter()
private predicate variableReadActual(Cfg::BasicBlock bb, int i, Variable v) {
exists(VarReadAccess read |
read.getVariable() = v and
read = bb.getNode(i).getAstNode()
)
}
pragma[noinline]
private predicate adjacentDefRead(
Definition def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2,
SsaInput::SourceVariable v
) {
Impl::adjacentDefRead(def, bb1, i1, bb2, i2) and
v = def.getSourceVariable()
}
pragma[noinline]
private predicate adjacentDefReadExt(
deprecated private predicate adjacentDefReadExt(
DefinitionExt def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2,
SsaInput::SourceVariable v
) {
@@ -163,23 +80,7 @@ private predicate adjacentDefReadExt(
v = def.getSourceVariable()
}
private predicate adjacentDefReachesRead(
Definition def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2
) {
exists(SsaInput::SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
def.definesAt(v, bb1, i1)
or
SsaInput::variableRead(bb1, i1, v, true)
)
or
exists(SsaInput::BasicBlock bb3, int i3 |
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
SsaInput::variableRead(bb3, i3, _, false) and
Impl::adjacentDefRead(def, bb3, i3, bb2, i2)
)
}
private predicate adjacentDefReachesReadExt(
deprecated private predicate adjacentDefReachesReadExt(
DefinitionExt def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2
) {
exists(SsaInput::SourceVariable v | adjacentDefReadExt(def, bb1, i1, bb2, i2, v) |
@@ -195,16 +96,7 @@ private predicate adjacentDefReachesReadExt(
)
}
/** Same as `adjacentDefRead`, but skips uncertain reads. */
pragma[nomagic]
private predicate adjacentDefSkipUncertainReads(
Definition def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2
) {
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
SsaInput::variableRead(bb2, i2, _, true)
}
private predicate adjacentDefReachesUncertainReadExt(
deprecated private predicate adjacentDefReachesUncertainReadExt(
DefinitionExt def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2
) {
adjacentDefReachesReadExt(def, bb1, i1, bb2, i2) and
@@ -213,7 +105,9 @@ private predicate adjacentDefReachesUncertainReadExt(
/** Same as `lastRefRedef`, but skips uncertain reads. */
pragma[nomagic]
private predicate lastRefSkipUncertainReadsExt(DefinitionExt def, SsaInput::BasicBlock bb, int i) {
deprecated private predicate lastRefSkipUncertainReadsExt(
DefinitionExt def, SsaInput::BasicBlock bb, int i
) {
Impl::lastRef(def, bb, i) and
not SsaInput::variableRead(bb, i, def.getSourceVariable(), false)
or
@@ -223,6 +117,20 @@ private predicate lastRefSkipUncertainReadsExt(DefinitionExt def, SsaInput::Basi
)
}
/**
* Holds if the read of `def` at `read` may be a last read. That is, `read`
* can either reach another definition of the underlying source variable or
* the end of the CFG scope, without passing through another non-pseudo read.
*/
pragma[nomagic]
deprecated predicate lastRead(Definition def, VarReadAccessCfgNode read) {
exists(Cfg::BasicBlock bb, int i |
lastRefSkipUncertainReadsExt(def, bb, i) and
variableReadActual(bb, i, _) and
read = bb.getNode(i)
)
}
cached
private module Cached {
/**
@@ -231,12 +139,11 @@ private module Cached {
*/
cached
predicate variableWriteActual(
Cfg::BasicBlock bb, int i, SsaInput::SourceVariable v, VarWriteAccessCfgNode write
Cfg::BasicBlock bb, int i, Variable v, VarWriteAccessCfgNode write
) {
exists(Cfg::CfgNode n, SsaInput::SourceVariable w |
write.getVariable() = w and
n = bb.getNode(i) and
if isPipelineIteratorVarAccess(write) then v = getPipelineIterator(w) else v = w
exists(Cfg::CfgNode n |
write.getVariable() = v and
n = bb.getNode(i)
|
write.isExplicitWrite(n)
or
@@ -246,10 +153,10 @@ private module Cached {
}
cached
AstCfgNode getARead(Definition def) {
exists(SsaInput::SourceVariable v, Cfg::BasicBlock bb, int i |
VarReadAccessCfgNode getARead(Definition def) {
exists(Variable v, Cfg::BasicBlock bb, int i |
Impl::ssaDefReachesRead(v, def, bb, i) and
SsaInput::variableRead(bb, i, v, true) and
variableReadActual(bb, i, v) and
result = bb.getNode(i)
)
}
@@ -267,11 +174,7 @@ private module Cached {
*/
cached
predicate firstRead(Definition def, VarReadAccessCfgNode read) {
exists(Cfg::BasicBlock bb1, int i1, Cfg::BasicBlock bb2, int i2 |
def.definesAt(_, bb1, i1) and
adjacentDefSkipUncertainReads(def, bb1, i1, bb2, i2) and
read = bb2.getNode(i2)
)
exists(Cfg::BasicBlock bb, int i | Impl::firstUse(def, bb, i, true) and read = bb.getNode(i))
}
/**
@@ -281,28 +184,14 @@ private module Cached {
*/
cached
predicate adjacentReadPair(Definition def, VarReadAccessCfgNode read1, VarReadAccessCfgNode read2) {
exists(Cfg::BasicBlock bb1, int i1, Cfg::BasicBlock bb2, int i2 |
exists(Cfg::BasicBlock bb1, int i1, Cfg::BasicBlock bb2, int i2, Variable v |
Impl::ssaDefReachesRead(v, def, bb1, i1) and
Impl::adjacentUseUse(bb1, i1, bb2, i2, v, true) and
read1 = bb1.getNode(i1) and
variableReadActual(bb1, i1, _) and
adjacentDefSkipUncertainReads(def, bb1, i1, bb2, i2) and
read2 = bb2.getNode(i2)
)
}
/**
* Holds if the read of `def` at `read` may be a last read. That is, `read`
* can either reach another definition of the underlying source variable or
* the end of the CFG scope, without passing through another non-pseudo read.
*/
cached
predicate lastRead(Definition def, VarReadAccessCfgNode read) {
exists(Cfg::BasicBlock bb, int i |
lastRefSkipUncertainReadsExt(def, bb, i) and
variableReadActual(bb, i, _) and
read = bb.getNode(i)
)
}
cached
Definition uncertainWriteDefinitionInput(UncertainWriteDefinition def) {
Impl::uncertainWriteDefinitionInput(def, result)
@@ -326,8 +215,14 @@ private module Cached {
cached // nothing is actually cached
module BarrierGuard<guardChecksSig/3 guardChecks> {
private predicate guardChecksAdjTypes(
DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e, boolean branch
) {
guardChecks(g, e, branch)
}
private Node getABarrierNodeImpl() {
none() // TODO
result = DataFlowIntegrationImpl::BarrierGuard<guardChecksAdjTypes/3>::getABarrierNode()
}
predicate getABarrierNode = getABarrierNodeImpl/0;
@@ -346,7 +241,7 @@ import Cached
* Only intended for internal use.
*/
class DefinitionExt extends Impl::DefinitionExt {
AstCfgNode getARead() { result = getARead(this) }
VarReadAccessCfgNode getARead() { result = getARead(this) }
override string toString() { result = this.(Ssa::Definition).toString() }
@@ -367,29 +262,64 @@ class PhiReadNode extends DefinitionExt, Impl::PhiReadNode {
/** Gets the SSA definition node corresponding to parameter `p`. */
pragma[nomagic]
DefinitionExt getParameterDef(Parameter p) {
exists(Cfg::EntryBasicBlock bb, int i |
parameterWrite(bb, i, p) and
result.definesAt(p, bb, i, _)
exists(Cfg::BasicBlock bb, int i |
bb.getNode(i).getAstNode() = p and
result.definesAt(_, bb, i, _)
)
}
private newtype TParameterExt = TNormalParameter(Parameter p)
private Parameter getANonPipelineParameter(FunctionBase f) {
result = f.getAParameter() and
not result instanceof PipelineParameter and
not result instanceof PipelineByPropertyNameParameter
}
class NormalParameter extends Parameter {
NormalParameter() {
not this instanceof PipelineParameter and
not this instanceof PipelineByPropertyNameParameter
}
int getIndexExcludingPipelines() {
exists(FunctionBase f |
f = this.getFunction() and
this =
rank[result + 1](int index, Parameter p |
p = getANonPipelineParameter(f) and index = p.getIndex()
|
p order by index
)
)
}
}
private newtype TParameterExt =
TNormalParameter(NormalParameter p) or
TSelfMethodParameter(Method m)
/** A normal parameter or an implicit `self` parameter. */
class ParameterExt extends TParameterExt {
Parameter asParameter() { this = TNormalParameter(result) }
NormalParameter asParameter() { this = TNormalParameter(result) }
predicate isInitializedBy(WriteDefinition def) { def = getParameterDef(this.asParameter()) }
Method asThis() { this = TSelfMethodParameter(result) }
string toString() { result = this.asParameter().toString() }
predicate isInitializedBy(WriteDefinition def) {
def = getParameterDef(this.asParameter())
or
def.(Ssa::ThisDefinition).getSourceVariable().getDeclaringScope() = this.asThis().(Scope)
}
Location getLocation() { result = this.asParameter().getLocation() }
string toString() { result = [this.asParameter().toString(), this.asThis().toString()] }
Location getLocation() {
result = [this.asParameter().getLocation(), this.asThis().getLocation()]
}
}
private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInputSig {
class Parameter = ParameterExt;
class Expr extends Cfg::CfgNodes::AstCfgNode {
class Expr extends Cfg::CfgNodes::ExprCfgNode {
predicate hasCfgNode(SsaInput::BasicBlock bb, int i) { this = bb.getNode(i) }
}
@@ -406,9 +336,7 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu
}
/** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */
predicate guardControlsBlock(Guard guard, SsaInput::BasicBlock bb, boolean branch) {
none() // TODO
}
predicate guardControlsBlock(Guard guard, SsaInput::BasicBlock bb, boolean branch) { none() }
/** Gets an immediate conditional successor of basic block `bb`, if any. */
SsaInput::BasicBlock getAConditionalBasicBlockSuccessor(SsaInput::BasicBlock bb, boolean branch) {