Files
codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/BaseSSA.qll
2025-12-11 14:17:12 +01:00

159 lines
5.2 KiB
Plaintext

import csharp
/**
* Provides a simple SSA implementation for local scope variables.
*/
module BaseSsa {
private import AssignableDefinitions
private import semmle.code.csharp.controlflow.BasicBlocks as BasicBlocks
private import codeql.ssa.Ssa as SsaImplCommon
/**
* Holds if the `i`th node of basic block `bb` is assignable definition `def`,
* targeting local scope variable `v`.
*/
private predicate definitionAt(
AssignableDefinition def, ControlFlow::BasicBlock bb, int i, SsaInput::SourceVariable v
) {
bb.getNode(i) = def.getExpr().getAControlFlowNode() and
v = def.getTarget() and
// In cases like `(x, x) = (0, 1)`, we discard the first (dead) definition of `x`
not exists(TupleAssignmentDefinition first, TupleAssignmentDefinition second | first = def |
second.getAssignment() = first.getAssignment() and
second.getEvaluationOrder() > first.getEvaluationOrder() and
second.getTarget() = v
)
}
private predicate implicitEntryDef(
Callable c, ControlFlow::BasicBlocks::EntryBlock bb, SsaInput::SourceVariable v
) {
exists(ControlFlow::BasicBlocks::EntryBlock entry |
c = entry.getCallable() and
// In case `c` has multiple bodies, we want each body to get its own implicit
// entry definition. In case `c` doesn't have multiple bodies, the line below
// is simply the same as `bb = entry`, because `entry.getFirstNode().getASuccessor()`
// will be in the entry block.
bb = entry.getFirstNode().getASuccessor().getBasicBlock() and
c = v.getCallable()
|
v.isReadonlyCapturedBy(c)
or
v instanceof Parameter
)
}
/** Holds if `a` is assigned in callable `c`. */
pragma[nomagic]
private predicate assignableDefinition(Assignable a, Callable c) {
exists(AssignableDefinition def |
def.getTarget() = a and
c = def.getEnclosingCallable()
|
not c instanceof Constructor or
a instanceof LocalScopeVariable
)
}
pragma[nomagic]
private predicate assignableUniqueWriter(Assignable a, Callable c) {
c = unique(Callable c0 | assignableDefinition(a, c0) | c0)
}
/** Holds if `a` is accessed in callable `c`. */
pragma[nomagic]
private predicate assignableAccess(Assignable a, Callable c) {
exists(AssignableAccess aa | aa.getTarget() = a | c = aa.getEnclosingCallable())
}
/**
* A local scope variable that is amenable to SSA analysis.
*
* This is either a local variable that is not captured, or one
* where all writes happen in the defining callable.
*/
class SimpleLocalScopeVariable extends LocalScopeVariable {
SimpleLocalScopeVariable() { assignableUniqueWriter(this, this.getCallable()) }
/** Holds if this local scope variable is read-only captured by `c`. */
predicate isReadonlyCapturedBy(Callable c) {
assignableAccess(this, c) and
c != this.getCallable()
}
}
private module SsaInput implements SsaImplCommon::InputSig<Location, ControlFlow::BasicBlock> {
class SourceVariable = SimpleLocalScopeVariable;
predicate variableWrite(ControlFlow::BasicBlock bb, int i, SourceVariable v, boolean certain) {
exists(AssignableDefinition def |
definitionAt(def, bb, i, v) and
if def.isCertain() then certain = true else certain = false
)
or
implicitEntryDef(_, bb, v) and
i = -1 and
certain = true
}
predicate variableRead(ControlFlow::BasicBlock bb, int i, SourceVariable v, boolean certain) {
exists(AssignableRead read |
read.getAControlFlowNode() = bb.getNode(i) and
read.getTarget() = v and
certain = true
)
}
}
private module SsaImpl = SsaImplCommon::Make<Location, BasicBlocks::Cfg, SsaInput>;
class Definition extends SsaImpl::Definition {
final AssignableRead getARead() {
exists(ControlFlow::BasicBlock bb, int i |
SsaImpl::ssaDefReachesRead(_, this, bb, i) and
result.getAControlFlowNode() = bb.getNode(i)
)
}
final AssignableDefinition getDefinition() {
exists(ControlFlow::BasicBlock bb, int i, SsaInput::SourceVariable v |
this.definesAt(v, bb, i) and
definitionAt(result, bb, i, v)
)
}
final predicate isImplicitEntryDefinition(SsaInput::SourceVariable v) {
exists(ControlFlow::BasicBlock bb |
this.definesAt(v, bb, -1) and
implicitEntryDef(_, bb, v)
)
}
private Definition getAPhiInputOrPriorDefinition() {
result = this.(PhiNode).getAnInput() or
SsaImpl::uncertainWriteDefinitionInput(this, result)
}
final Definition getAnUltimateDefinition() {
result = this.getAPhiInputOrPriorDefinition*() and
not result instanceof PhiNode
}
override Location getLocation() {
result = this.getDefinition().getLocation()
or
exists(Callable c, ControlFlow::BasicBlock bb, SsaInput::SourceVariable v |
this.definesAt(v, bb, -1) and
implicitEntryDef(c, bb, v) and
result = c.getLocation()
)
}
}
class PhiNode extends SsaImpl::PhiNode, Definition {
override Location getLocation() { result = this.getBasicBlock().getLocation() }
final Definition getAnInput() { SsaImpl::phiHasInputFromBlock(this, result, _) }
}
}