Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c4f0868
Java: Move SSA entry defs to index -1.
aschackmull Oct 21, 2025
f2181ec
Java: Get rid of untracked SSA definitions.
aschackmull Oct 21, 2025
374c772
Java: Remove getAFirstUse in BaseSSA.
aschackmull Oct 21, 2025
79b2f21
SSA: Fix phi defs.
aschackmull Oct 27, 2025
289d337
SSA: Improve toString.
aschackmull Oct 27, 2025
551944b
Java: Add VariableWrite class.
aschackmull Oct 24, 2025
942dc2b
Java: Replace BaseSSA class wrappers with shared code.
aschackmull Oct 23, 2025
d5708fd
Java: Instantiate shared SSA wrappers for main SSA.
aschackmull Nov 5, 2025
154f077
Java: Simplify instantiation of Guards and ControlFlowReachability.
aschackmull Nov 6, 2025
99aa033
Java: Replace usages of isParameterDefinition.
aschackmull Nov 6, 2025
07e6356
Java: Replace getAFirstUse with top-level predicate.
aschackmull Nov 7, 2025
483b2d8
Java: Replace uses of SsaExplicitUpdate.
aschackmull Nov 7, 2025
06df5c0
Java: Introduce SsaCapturedDefinition and replace uses of getAnUltima…
aschackmull Nov 7, 2025
3e43c53
Java: Update some qldoc deprecation notices.
aschackmull Nov 7, 2025
35caede
Java: Replace SsaPhiNode with SsaPhiDefinition.
aschackmull Nov 7, 2025
f4b9efc
Java: Replace getAUse with getARead.
aschackmull Nov 7, 2025
8594ae0
Java: Replace remaining SsaImplicitInit.
aschackmull Nov 7, 2025
f0bd034
Java: Replace usages of SsaVariable.
aschackmull Nov 7, 2025
ee5d65e
Java: Update toString for implicit writes.
aschackmull Nov 7, 2025
5849d85
Java: Deprecate two more SSA classes.
aschackmull Nov 7, 2025
95ac61d
Java: Drop caching of deprecated predicates.
aschackmull Oct 27, 2025
e059ded
Java: Accept toString changes in qltest.
aschackmull Nov 6, 2025
109a5eb
Java: Accept qltest changes due to dropped UntrackedDef.
aschackmull Nov 6, 2025
437ca58
Java: Add change note.
aschackmull Nov 7, 2025
4a58a01
Java: Reinstate useless null check results for fields that are no lon…
aschackmull Nov 11, 2025
9ddf8b4
Java: Add missing deprecated annotations.
aschackmull Nov 7, 2025
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
4 changes: 4 additions & 0 deletions java/ql/lib/change-notes/2025-10-07-ssa-api-updates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: deprecated
---
* The SSA interface has been updated and all classes and several predicates have been renamed. See the qldoc for more specific migration information.
46 changes: 46 additions & 0 deletions java/ql/lib/semmle/code/java/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,52 @@ class VariableAssign extends VariableUpdate {
}
}

private newtype TVariableWrite =
TParamInit(Parameter p) or
TVarWriteExpr(VariableUpdate u)

/**
* A write to a variable. This is either a local variable declaration,
* including parameter declarations, or an update to a variable.
*/
class VariableWrite extends TVariableWrite {
/** Gets the expression representing this write, if any. */
Expr asExpr() { this = TVarWriteExpr(result) }

/**
* Gets the expression with the value being written, if any.
*
* This can be the same expression as returned by `asExpr()`, which is the
* case for, for example, `++x` and `x += e`. For simple assignments like
* `x = e`, `asExpr()` gets the whole assignment expression while
* `getValue()` gets the right-hand side `e`. Post-crement operations like
* `x++` do not have an expression with the value being written.
*/
Expr getValue() {
this.asExpr().(VariableAssign).getSource() = result or
this.asExpr().(AssignOp) = result or
this.asExpr().(PreIncExpr) = result or
this.asExpr().(PreDecExpr) = result
}

/** Holds if this write is an initialization of parameter `p`. */
predicate isParameterInit(Parameter p) { this = TParamInit(p) }

/** Gets a textual representation of this write. */
string toString() {
exists(Parameter p | this = TParamInit(p) and result = p.toString())
or
result = this.asExpr().toString()
}

/** Gets the location of this write. */
Location getLocation() {
exists(Parameter p | this = TParamInit(p) and result = p.getLocation())
or
result = this.asExpr().getLocation()
}
}

/** A type literal. For example, `String.class`. */
class TypeLiteral extends Expr, @typeliteral {
/** Gets the access to the type whose class is accessed. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ module;

import java
private import codeql.controlflow.ControlFlowReachability
private import semmle.code.java.dataflow.SSA as SSA
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.controlflow.Guards as Guards

private module ControlFlowInput implements InputSig<Location, ControlFlowNode, BasicBlock> {
private import java as J
import Ssa

AstNode getEnclosingAstNode(ControlFlowNode node) { node.getAstNode() = result }

Expand All @@ -27,23 +28,6 @@ private module ControlFlowInput implements InputSig<Location, ControlFlowNode, B

class Expr = J::Expr;

class SourceVariable = SSA::SsaSourceVariable;

class SsaDefinition = SSA::SsaVariable;

class SsaExplicitWrite extends SsaDefinition instanceof SSA::SsaExplicitUpdate {
Expr getValue() {
super.getDefiningExpr().(VariableAssign).getSource() = result or
super.getDefiningExpr().(AssignOp) = result
}
}

class SsaPhiDefinition = SSA::SsaPhiNode;

class SsaUncertainWrite extends SsaDefinition instanceof SSA::SsaUncertainImplicitUpdate {
SsaDefinition getPriorDefinition() { result = super.getPriorDef() }
}

class GuardValue = Guards::GuardValue;

predicate ssaControlsBranchEdge(SsaDefinition def, BasicBlock bb1, BasicBlock bb2, GuardValue v) {
Expand Down
58 changes: 7 additions & 51 deletions java/ql/lib/semmle/code/java/controlflow/Guards.qll
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ private predicate isNonFallThroughPredecessor(SwitchCase sc, ControlFlowNode pre

private module GuardsInput implements SharedGuards::InputSig<Location, ControlFlowNode, BasicBlock> {
private import java as J
private import semmle.code.java.dataflow.internal.BaseSSA
private import semmle.code.java.dataflow.internal.BaseSSA as Base
private import semmle.code.java.dataflow.NullGuards as NullGuards

class NormalExitNode = ControlFlow::NormalExitNode;
Expand Down Expand Up @@ -211,10 +211,10 @@ private module GuardsInput implements SharedGuards::InputSig<Location, ControlFl
f.getInitializer() = NullGuards::baseNotNullExpr()
)
or
exists(CatchClause cc, LocalVariableDeclExpr decl, BaseSsaUpdate v |
exists(CatchClause cc, LocalVariableDeclExpr decl, Base::SsaExplicitWrite v |
decl = cc.getVariable() and
decl = v.getDefiningExpr() and
this = v.getAUse()
this = v.getARead()
)
}
}
Expand Down Expand Up @@ -407,61 +407,17 @@ private module LogicInputCommon {
}

private module LogicInput_v1 implements GuardsImpl::LogicInputSig {
private import semmle.code.java.dataflow.internal.BaseSSA

final private class FinalBaseSsaVariable = BaseSsaVariable;

class SsaDefinition extends FinalBaseSsaVariable {
GuardsInput::Expr getARead() { result = this.getAUse() }
}

class SsaExplicitWrite extends SsaDefinition instanceof BaseSsaUpdate {
GuardsInput::Expr getValue() {
super.getDefiningExpr().(VariableAssign).getSource() = result or
super.getDefiningExpr().(AssignOp) = result
}
}

class SsaPhiDefinition extends SsaDefinition instanceof BaseSsaPhiNode {
predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) {
super.hasInputFromBlock(inp, bb)
}
}

class SsaParameterInit extends SsaDefinition instanceof BaseSsaImplicitInit {
Parameter getParameter() { super.isParameterDefinition(result) }
}
private import semmle.code.java.dataflow.internal.BaseSSA as Base
import Base::Ssa

predicate additionalNullCheck = LogicInputCommon::additionalNullCheck/4;

predicate additionalImpliesStep = LogicInputCommon::additionalImpliesStep/4;
}

private module LogicInput_v2 implements GuardsImpl::LogicInputSig {
private import semmle.code.java.dataflow.SSA as SSA

final private class FinalSsaVariable = SSA::SsaVariable;

class SsaDefinition extends FinalSsaVariable {
GuardsInput::Expr getARead() { result = this.getAUse() }
}

class SsaExplicitWrite extends SsaDefinition instanceof SSA::SsaExplicitUpdate {
GuardsInput::Expr getValue() {
super.getDefiningExpr().(VariableAssign).getSource() = result or
super.getDefiningExpr().(AssignOp) = result
}
}

class SsaPhiDefinition extends SsaDefinition instanceof SSA::SsaPhiNode {
predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) {
super.hasInputFromBlock(inp, bb)
}
}

class SsaParameterInit extends SsaDefinition instanceof SSA::SsaImplicitInit {
Parameter getParameter() { super.isParameterDefinition(result) }
}
private import semmle.code.java.dataflow.SSA
import Ssa

predicate additionalNullCheck = LogicInputCommon::additionalNullCheck/4;

Expand Down
14 changes: 10 additions & 4 deletions java/ql/lib/semmle/code/java/dataflow/DefUse.qll
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ predicate useUsePair(VarRead use1, VarRead use2) { adjacentUseUse+(use1, use2) }
* Other paths may also exist, so the SSA variables in `def` and `use` can be different.
*/
predicate defUsePair(VariableUpdate def, VarRead use) {
exists(SsaVariable v |
v.getAUse() = use and v.getAnUltimateDefinition().(SsaExplicitUpdate).getDefiningExpr() = def
exists(SsaDefinition v, SsaExplicitWrite write |
v.getARead() = use and write.getDefiningExpr() = def
|
v.getAnUltimateDefinition() = write
or
v.(SsaCapturedDefinition).getAnUltimateCapturedDefinition() = write
)
}

Expand All @@ -46,7 +50,9 @@ predicate defUsePair(VariableUpdate def, VarRead use) {
* Other paths may also exist, so the SSA variables can be different.
*/
predicate parameterDefUsePair(Parameter p, VarRead use) {
exists(SsaVariable v |
v.getAUse() = use and v.getAnUltimateDefinition().(SsaImplicitInit).isParameterDefinition(p)
exists(SsaDefinition v, SsaParameterInit init | v.getARead() = use and init.getParameter() = p |
v.getAnUltimateDefinition() = init
or
v.(SsaCapturedDefinition).getAnUltimateCapturedDefinition() = init
)
}
47 changes: 28 additions & 19 deletions java/ql/lib/semmle/code/java/dataflow/NullGuards.qll
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ Expr enumConstEquality(Expr e, boolean polarity, EnumConstant c) {
}

/** Gets an instanceof expression of `v` with type `type` */
InstanceOfExpr instanceofExpr(SsaVariable v, RefType type) {
InstanceOfExpr instanceofExpr(SsaDefinition v, RefType type) {
result.getCheckedType() = type and
result.getExpr() = v.getAUse()
result.getExpr() = v.getARead()
}

/**
Expand All @@ -37,8 +37,8 @@ InstanceOfExpr instanceofExpr(SsaVariable v, RefType type) {
*
* Note this includes Kotlin's `==` and `!=` operators, which are value-equality tests.
*/
EqualityTest varEqualityTestExpr(SsaVariable v1, SsaVariable v2, boolean isEqualExpr) {
result.hasOperands(v1.getAUse(), v2.getAUse()) and
EqualityTest varEqualityTestExpr(SsaDefinition v1, SsaDefinition v2, boolean isEqualExpr) {
result.hasOperands(v1.getARead(), v2.getARead()) and
isEqualExpr = result.polarity()
}

Expand Down Expand Up @@ -91,37 +91,44 @@ Expr clearlyNotNullExpr(Expr reason) {
(reason = r1 or reason = r2)
)
or
exists(SsaVariable v, boolean branch, VarRead rval, Guard guard |
exists(SsaDefinition v, boolean branch, VarRead rval, Guard guard |
guard = directNullGuard(v, branch, false) and
guard.controls(rval.getBasicBlock(), branch) and
reason = guard and
rval = v.getAUse() and
rval = v.getARead() and
result = rval and
not result = baseNotNullExpr()
)
or
exists(SsaVariable v |
exists(SsaDefinition v |
clearlyNotNull(v, reason) and
result = v.getAUse() and
result = v.getARead() and
not result = baseNotNullExpr()
)
or
exists(Field f |
result = f.getAnAccess() and
f.isFinal() and
f.getInitializer() = clearlyNotNullExpr(reason) and
not result = baseNotNullExpr()
)
}

/** Holds if `v` is an SSA variable that is provably not `null`. */
predicate clearlyNotNull(SsaVariable v, Expr reason) {
predicate clearlyNotNull(SsaDefinition v, Expr reason) {
exists(Expr src |
src = v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() and
src = v.(SsaExplicitWrite).getValue() and
src = clearlyNotNullExpr(reason)
)
or
exists(CatchClause cc, LocalVariableDeclExpr decl |
decl = cc.getVariable() and
decl = v.(SsaExplicitUpdate).getDefiningExpr() and
decl = v.(SsaExplicitWrite).getDefiningExpr() and
reason = decl
)
or
exists(SsaVariable captured |
v.(SsaImplicitInit).captures(captured) and
exists(SsaDefinition captured |
v.(SsaCapturedDefinition).captures(captured) and
clearlyNotNull(captured, reason)
)
or
Expand All @@ -136,7 +143,7 @@ predicate clearlyNotNull(SsaVariable v, Expr reason) {
Expr clearlyNotNullExpr() { result = clearlyNotNullExpr(_) }

/** Holds if `v` is an SSA variable that is provably not `null`. */
predicate clearlyNotNull(SsaVariable v) { clearlyNotNull(v, _) }
predicate clearlyNotNull(SsaDefinition v) { clearlyNotNull(v, _) }

/**
* Holds if the evaluation of a call to `m` resulting in the value `branch`
Expand Down Expand Up @@ -207,7 +214,7 @@ deprecated Expr basicOrCustomNullGuard(Expr e, boolean branch, boolean isnull) {
* If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
Expr directNullGuard(SsaVariable v, boolean branch, boolean isnull) {
Expr directNullGuard(SsaDefinition v, boolean branch, boolean isnull) {
result = basicNullGuard(sameValue(v, _), branch, isnull)
}

Expand All @@ -219,7 +226,7 @@ Expr directNullGuard(SsaVariable v, boolean branch, boolean isnull) {
* If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
deprecated Guard nullGuard(SsaVariable v, boolean branch, boolean isnull) {
deprecated Guard nullGuard(SsaDefinition v, boolean branch, boolean isnull) {
result = directNullGuard(v, branch, isnull)
}

Expand All @@ -228,7 +235,9 @@ deprecated Guard nullGuard(SsaVariable v, boolean branch, boolean isnull) {
* from `bb1` to `bb2` implies that `v` is guaranteed to be null if `isnull` is
* true, and non-null if `isnull` is false.
*/
predicate nullGuardControlsBranchEdge(SsaVariable v, boolean isnull, BasicBlock bb1, BasicBlock bb2) {
predicate nullGuardControlsBranchEdge(
SsaDefinition v, boolean isnull, BasicBlock bb1, BasicBlock bb2
) {
exists(GuardValue gv |
Guards_v3::ssaControlsBranchEdge(v, bb1, bb2, gv) and
gv.isNullness(isnull)
Expand All @@ -240,7 +249,7 @@ predicate nullGuardControlsBranchEdge(SsaVariable v, boolean isnull, BasicBlock
* `bb` `v` is guaranteed to be null if `isnull` is true, and non-null if
* `isnull` is false.
*/
predicate nullGuardControls(SsaVariable v, boolean isnull, BasicBlock bb) {
predicate nullGuardControls(SsaDefinition v, boolean isnull, BasicBlock bb) {
exists(GuardValue gv |
Guards_v3::ssaControls(v, bb, gv) and
gv.isNullness(isnull)
Expand All @@ -263,6 +272,6 @@ predicate guardSuggestsExprMaybeNull(Expr guard, Expr e) {
/**
* Holds if `guard` is a guard expression that suggests that `v` might be null.
*/
predicate guardSuggestsVarMaybeNull(Expr guard, SsaVariable v) {
predicate guardSuggestsVarMaybeNull(Expr guard, SsaDefinition v) {
guardSuggestsExprMaybeNull(guard, sameValue(v, _))
}
Loading
Loading