Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/codeql/codeql-language-guides/codeql-library-for-go.rst
Original file line number Diff line number Diff line change
Expand Up @@ -583,12 +583,13 @@ Note that the program variable ``x`` has been mapped onto three distinct SSA var
``x2``, and ``x3``. In this case there is not much benefit to such a representation, but in general
SSA form has well-known advantages for data-flow analysis for which we refer to the literature.

If you do need to work with raw SSA variables, they are represented by the class ``SsaVariable``.
Class ``SsaDefinition`` represents definitions of SSA variables, which have a one-to-one
correspondence with ``SsaVariable``\ s. Member predicates ``getDefinition()`` and ``getVariable()``
exist to map from one to the other. You can use member predicate ``getAUse()`` of ``SsaVariable`` to
look for uses of an SSA variable. To access the program variable underlying an SSA variable, use
member predicate ``getSourceVariable()``.
If you do need to work with SSA directly, use class ``SsaDefinition`` to represent an SSA value by
its definition. Member predicate ``getARead()`` returns expression reads of that value. To include
implicit reads, such as reads of named result variables at function exit, use
``DataFlow::ReadNode.readsSsaDefinition()`` to associate data-flow read nodes with their reaching
definitions. To access the underlying program variable, use member predicate ``getSourceVariable()``.

The older ``SsaVariable`` class and its ``getAUse()`` member are deprecated.

Global value numbering
~~~~~~~~~~~~~~~~~~~~~~
Expand Down
10 changes: 10 additions & 0 deletions go/ql/lib/change-notes/2026-10-20-shared-ssa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
category: breaking
---
* The Go SSA API now uses the shared SSA classes (`SsaDefinition`, `SsaWriteDefinition`, `SsaExplicitWrite`, `SsaParameterInit`, `SsaImplicitWrite`, `SsaUncertainWrite`, and `SsaPhiDefinition`):
- `SsaVariable`, `SsaExplicitDefinition`, `SsaImplicitDefinition`, `SsaVariableCapture`, `SsaPseudoDefinition`, and `SsaPhiNode` have been deprecated.
Comment thread
owen-mc marked this conversation as resolved.
Resolved
- `ControlFlow::WriteNode.definesSsaVariable`, `IR::WriteTarget.asSsaVariable`, `SsaWithFields.getBaseVariable`, and `DataFlow::ReadNode.readsSsaVariable` have been deprecated in favor of `definesSsaDefinition`, `asSsaDefinition`, `getBaseDefinition`, and `readsSsaDefinition`, respectively.
- `SsaDefinition` no longer defines `getVariable()`, `getRoot()`, or `hasLocationInfo(...)`. Use `SsaDefinition` directly, `getBasicBlock().getScope()`, and `getLocation().hasLocationInfo(...)` instead.
- `SsaDefinition.getAFirstUse()` is no longer available. First-use access remains available through `getAFirstUse()` on the deprecated `SsaVariable` class. `DataFlow::ReadNode.readsSsaDefinition()` matches all reads, not just first uses, and is not an equivalent replacement.
- `SsaDefinition.getARead()` returns expression reads only. Use `DataFlow::ReadNode.readsSsaDefinition()` to include implicit reads, such as reads of result variables at function exit.
- `IR::TypeSwitchImplicitVariableInstruction` now extends `IR::WriteInstruction`, with target class `IR::ImplicitVariableTarget`.
15 changes: 12 additions & 3 deletions go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,21 @@ module ControlFlow {
/** Holds if this node sets variable or constant `v` to `rhs`. */
predicate writes(ValueEntity v, DataFlow::Node rhs) { super.writes(v, rhs.asInstruction()) }

/** Holds if this node defines SSA variable `v` to be `rhs`. */
predicate definesSsaVariable(SsaVariable v, DataFlow::Node rhs) {
super.getLhs().asSsaVariable() = v and
/** Holds if this node defines SSA definition `def` to be `rhs`. */
predicate definesSsaDefinition(SsaDefinition def, DataFlow::Node rhs) {
super.getLhs().asSsaDefinition() = def and
super.getRhs() = rhs.asInstruction()
}

/**
* DEPRECATED: Use `definesSsaDefinition` instead.
*
* Holds if this node defines SSA variable `v` to be `rhs`.
*/
deprecated predicate definesSsaVariable(SsaVariable v, DataFlow::Node rhs) {
this.definesSsaDefinition(v, rhs)
}

/**
* Holds if this node sets the value of field `f` on `base` (or its implicit dereference) to
* `rhs`, where `base` represents the post-update value.
Expand Down
42 changes: 8 additions & 34 deletions go/ql/lib/semmle/go/controlflow/Guards.qll
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,9 @@ private module GuardsInput implements
}

private predicate extractedCallResult(Expr use, G::CallExpr call, int resultIndex) {
exists(GoSsa::SsaDefinition def, IR::ExtractTupleElementInstruction extract |
use = def.getVariable().getAUse().(IR::EvalInstruction).getExpr() and
def.(GoSsa::SsaExplicitDefinition).getInstruction() = extract and
exists(GoSsa::Ssa::SsaDefinition def, IR::ExtractTupleElementInstruction extract |
use = def.getARead() and
def.(GoSsa::Ssa::SsaExplicitWrite).getDefinition() = extract and
extract.extractsElement(IR::evalExprInstruction(call), resultIndex)
)
}
Expand Down Expand Up @@ -398,46 +398,20 @@ private module GuardsInput implements
private module GuardsImpl = SharedGuards::Make<Location, CfgImpl::Cfg, GuardsInput>;

private module LogicInput implements GuardsImpl::LogicInputSig {
final private class FinalSsaDefinition = GoSsa::SsaDefinition;

class SsaDefinition extends FinalSsaDefinition {
GuardsInput::Expr getARead() {
result = super.getVariable().getAUse().(IR::EvalInstruction).getExpr()
}
}

class SsaExplicitWrite extends SsaDefinition instanceof GoSsa::SsaExplicitDefinition {
GuardsInput::Expr getValue() { result = super.getRhs().(IR::EvalInstruction).getExpr() }
}

class SsaPhiDefinition extends SsaDefinition instanceof GoSsa::SsaPhiNode {
/** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */
predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) {
SsaImpl::phiHasInputFromBlock(this, inp, bb)
}
}

class SsaParameterInit extends SsaDefinition {
SsaParameterInit() {
this.(GoSsa::SsaExplicitDefinition).getInstruction() instanceof IR::InitParameterInstruction
}

GuardsInput::Parameter getParameter() {
this.(GoSsa::SsaExplicitDefinition).getInstruction() = IR::initParamInstruction(result)
}
}
import GoSsa::Ssa

predicate implicitReturnDefinition(GuardsInput::NonOverridableMethod method, SsaDefinition def) {
exists(IR::ReadResultInstruction read |
exists(IR::ReadResultInstruction read, BasicBlock bb, int i |
method.getResultIndex() >= 0 and
read.reads(method.getFunction().getResult(method.getResultIndex())) and
def.getVariable().getAUse() = read
read = bb.getNode(i) and
SsaImpl::ssaDefReachesRead(def.getSourceVariable(), def, bb, i)
)
}

predicate additionalSsaDefinitionValue(SsaDefinition def, GuardValue value) {
exists(IR::EvalImplicitInitInstruction init |
def.(GoSsa::SsaExplicitDefinition).getInstruction() = init and
def.(SsaExplicitWrite).getDefinition() = init and
value.asBooleanValue() = init.getBoolValue()
)
}
Expand Down
50 changes: 41 additions & 9 deletions go/ql/lib/semmle/go/controlflow/IR.qll
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ module IR {
lhs = MkLhs(this, _)
or
lhs = MkResultWriteTarget(this)
or
lhs = MkTypeSwitchImplicitVariableTarget(this, _)
) and
initialization = false
or
Expand Down Expand Up @@ -1012,14 +1014,13 @@ module IR {
* An instruction corresponding to the implicit declaration and assignment of a variable
* in a type switch case clause.
*/
class TypeSwitchImplicitVariableInstruction extends Instruction {
class TypeSwitchImplicitVariableInstruction extends WriteInstruction {
CaseClause cc;

TypeSwitchImplicitVariableInstruction() { typeSwitchCaseMatch(this, cc) }

override predicate writes(ValueEntity v, Instruction rhs) {
v = cc.getImplicitlyDeclaredVariable() and
exists(TypeSwitchStmt ts | cc = ts.getACase() | rhs = evalExprInstruction(ts.getExpr()))
override Instruction getRhs() {
exists(TypeSwitchStmt ts | cc = ts.getACase() and result = evalExprInstruction(ts.getExpr()))
}
}

Expand Down Expand Up @@ -1108,14 +1109,24 @@ module IR {
exists(ret.getEnclosingFunction().getResultVar(idx)) and
exists(ret.getAnExpr())
)
} or
/** An implicitly declared variable in a type-switch case. */
MkTypeSwitchImplicitVariableTarget(ControlFlow::Node w, LocalVariable v) {
exists(CaseClause cc | typeSwitchCaseMatch(w, cc) and v = cc.getImplicitlyDeclaredVariable())
}

/** A representation of the target of a write instruction. */
class WriteTarget extends TWriteTarget {
ControlFlow::Node w;

WriteTarget() {
this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w)
this = MkLhs(w, _)
or
this = MkLiteralElementTarget(w)
or
this = MkResultWriteTarget(w)
or
this = MkTypeSwitchImplicitVariableTarget(w, _)
}

/** Gets the write instruction of which this is the target. */
Expand All @@ -1127,10 +1138,15 @@ module IR {
/** Gets the name of the variable or field being written to, if any. */
string getName() { none() }

/** Gets the SSA variable being written to, if any. */
SsaVariable asSsaVariable() {
this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction()
}
/** Gets the SSA definition being written to, if any. */
SsaDefinition asSsaDefinition() { this.getWrite() = result.(SsaExplicitWrite).getDefinition() }

/**
* DEPRECATED: Use `asSsaDefinition` instead.
*
* Gets the SSA variable being written to, if any.
*/
deprecated SsaVariable asSsaVariable() { result = this.asSsaDefinition() }

/** Holds if `e` is the variable or field being written to. */
predicate refersTo(ValueEntity e) { none() }
Expand Down Expand Up @@ -1160,6 +1176,22 @@ module IR {
}
}

/** An implicitly declared variable used as the target of a type-switch case write. */
class ImplicitVariableTarget extends WriteTarget {
LocalVariable variable;

ImplicitVariableTarget() { this = MkTypeSwitchImplicitVariableTarget(_, variable) }

override predicate refersTo(ValueEntity e) { e = variable }

override string getName() { result = variable.getName() }

/** Gets the variable this target refers to. */
Variable getVariable() { result = variable }

override Location getLocation() { result = variable.getLocation() }
}

/** A reference to a variable or constant, used as the target of a write. */
class VarOrConstTarget extends WriteTarget {
Expr loc;
Expand Down
6 changes: 3 additions & 3 deletions go/ql/lib/semmle/go/dataflow/GlobalValueNumbering.qll
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private ControlFlow::Node nodeWithPossibleSideEffect() {
// If the lhs of an assignment is not analyzable by SSA, then
// we need to treat the assignment as having a possible side-effect.
result instanceof Write and
not exists(SsaExplicitDefinition ssa | result = ssa.getInstruction())
not exists(SsaExplicitWrite ssa | result = ssa.getDefinition())
}

private predicate isPureFn(Function f) {
Expand Down Expand Up @@ -527,8 +527,8 @@ private predicate mkDeref(DataFlow::PointerDereferenceNode deref, GVN p, Control
p = globalValueNumber(deref.getOperand())
}

private predicate ssaInit(SsaExplicitDefinition ssa, DataFlow::Node rhs) {
ssa.getRhs() = rhs.asInstruction()
private predicate ssaInit(SsaExplicitWrite ssa, DataFlow::Node rhs) {
ssa.getDefinition().getRhs() = rhs.asInstruction()
}

/** Gets the global value number of data-flow node `nd`. */
Expand Down
Loading
Loading