Skip to content
Draft
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
1 change: 1 addition & 0 deletions go/ql/consistency-queries/VariableCaptureConsistency.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import semmle.go.dataflow.internal.VariableCapture::Flow::ConsistencyChecks
4 changes: 4 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,4 @@
---
category: breaking
---
* The Go SSA API now uses the shared SSA classes. `SsaVariable`, `SsaExplicitDefinition`, `SsaVariableCapture`, and `SsaPhiNode` have been replaced by `SsaDefinition`, `SsaExplicitWrite`, `SsaUncertainWrite`, and `SsaPhiDefinition`, respectively. Deprecated compatibility classes are available for the old names.
4 changes: 4 additions & 0 deletions go/ql/lib/change-notes/2026-10-21-variable-capture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Data flow through captured variables now uses the shared variable capture library, keeping captures associated with their closure objects rather than using context-insensitive jumps.
6 changes: 3 additions & 3 deletions go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ 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()
}

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
43 changes: 34 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,8 @@ 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() }

/** Holds if `e` is the variable or field being written to. */
predicate refersTo(ValueEntity e) { none() }
Expand Down Expand Up @@ -1160,6 +1169,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