Files
codeql/java/ql/lib/semmle/code/java/controlflow/Guards.qll
2026-05-07 13:46:52 +02:00

420 lines
14 KiB
Plaintext

/**
* Provides classes and predicates for reasoning about guards and the control
* flow elements controlled by those guards.
*/
overlay[local?]
module;
import java
private import semmle.code.java.controlflow.Dominance
private import semmle.code.java.controlflow.internal.Preconditions
private import semmle.code.java.controlflow.internal.SwitchCases
private import codeql.controlflow.Guards as SharedGuards
private import codeql.controlflow.SuccessorType
/**
* A basic block that terminates in a condition, splitting the subsequent control flow.
*/
class ConditionBlock extends BasicBlock {
ConditionBlock() { this.getLastNode() instanceof ConditionNode }
/** Gets the last node of this basic block. */
ConditionNode getConditionNode() { result = this.getLastNode() }
/** Gets the condition of the last node of this basic block. */
ExprParent getCondition() { result = this.getConditionNode().getCondition() }
/** Gets a `true`- or `false`-successor of the last node of this basic block. */
BasicBlock getTestSuccessor(boolean testIsTrue) {
result.getFirstNode() = this.getConditionNode().getABranchSuccessor(testIsTrue)
}
/*
* For this block to control the block `controlled` with `testIsTrue` the following must be true:
* Execution must have passed through the test i.e. `this` must strictly dominate `controlled`.
* Execution must have passed through the `testIsTrue` edge leaving `this`.
*
* Although "passed through the true edge" implies that `this.getATrueSuccessor()` dominates `controlled`,
* the reverse is not true, as flow may have passed through another edge to get to `this.getATrueSuccessor()`
* so we need to assert that `this.getATrueSuccessor()` dominates `controlled` *and* that
* all predecessors of `this.getATrueSuccessor()` are either `this` or dominated by `this.getATrueSuccessor()`.
*
* For example, in the following java snippet:
* ```
* if (x)
* controlled;
* false_successor;
* uncontrolled;
* ```
* `false_successor` dominates `uncontrolled`, but not all of its predecessors are `this` (`if (x)`)
* or dominated by itself. Whereas in the following code:
* ```
* if (x)
* while (controlled)
* also_controlled;
* false_successor;
* uncontrolled;
* ```
* the block `while controlled` is controlled because all of its predecessors are `this` (`if (x)`)
* or (in the case of `also_controlled`) dominated by itself.
*
* The additional constraint on the predecessors of the test successor implies
* that `this` strictly dominates `controlled` so that isn't necessary to check
* directly.
*/
/**
* Holds if `controlled` is a basic block controlled by this condition, that
* is, a basic blocks for which the condition is `testIsTrue`.
*/
predicate controls(BasicBlock controlled, boolean testIsTrue) {
exists(BasicBlock succ |
succ = this.getTestSuccessor(testIsTrue) and
dominatingEdge(this, succ) and
succ.dominates(controlled)
)
}
}
private module GuardsInput implements SharedGuards::InputSig<Location, ControlFlowNode, BasicBlock> {
private import java as J
private import semmle.code.java.dataflow.internal.BaseSSA as Base
private import semmle.code.java.dataflow.NullGuards as NullGuards
class NormalExitNode = ControlFlow::NormalExitNode;
class AstNode = ExprParent;
class Expr = J::Expr;
private newtype TConstantValue =
TCharValue(string c) { any(CharacterLiteral lit).getValue() = c } or
TStringValue(string value) { any(CompileTimeConstantExpr c).getStringValue() = value } or
TEnumValue(EnumConstant c)
class ConstantValue extends TConstantValue {
string toString() {
this = TCharValue(result)
or
this = TStringValue(result)
or
exists(EnumConstant c | this = TEnumValue(c) and result = c.toString())
}
}
abstract class ConstantExpr extends Expr {
predicate isNull() { none() }
boolean asBooleanValue() { none() }
int asIntegerValue() { none() }
ConstantValue asConstantValue() { none() }
}
private class NullConstant extends ConstantExpr instanceof J::NullLiteral {
override predicate isNull() { any() }
}
private class BooleanConstant extends ConstantExpr instanceof J::BooleanLiteral {
override boolean asBooleanValue() { result = super.getBooleanValue() }
}
private class IntegerConstant extends ConstantExpr instanceof J::CompileTimeConstantExpr {
override int asIntegerValue() { result = super.getIntValue() }
}
private class CharConstant extends ConstantExpr instanceof J::CharacterLiteral {
override ConstantValue asConstantValue() { result = TCharValue(super.getValue()) }
}
private class StringConstant extends ConstantExpr instanceof J::CompileTimeConstantExpr {
override ConstantValue asConstantValue() { result = TStringValue(super.getStringValue()) }
}
private class EnumConstantExpr extends ConstantExpr instanceof J::VarAccess {
override ConstantValue asConstantValue() {
exists(EnumConstant c | this = c.getAnAccess() and result = TEnumValue(c))
}
}
class NonNullExpr extends Expr {
NonNullExpr() {
this = NullGuards::baseNotNullExpr()
or
exists(Field f |
this = f.getAnAccess() and
f.isFinal() and
f.getInitializer() = NullGuards::baseNotNullExpr()
)
or
exists(CatchClause cc, LocalVariableDeclExpr decl, Base::SsaExplicitWrite v |
decl = cc.getVariable() and
decl = v.getDefiningExpr() and
this = v.getARead()
)
}
}
class Case extends ExprParent instanceof J::SwitchCase {
Expr getSwitchExpr() { result = super.getSelectorExpr() }
predicate isDefaultCase() { this instanceof DefaultCase }
ConstantExpr asConstantCase() {
exists(ConstCase cc | this = cc |
cc.getValue() = result and
strictcount(cc.getValue(_)) = 1
)
}
private ControlFlowNode getPatternNode() {
result = this.(J::PatternCase).getUniquePattern().getControlFlowNode()
or
result = unique(Expr e | this.(J::ConstCase).getValue(_) = e).getControlFlowNode()
}
predicate matchEdge(BasicBlock bb1, BasicBlock bb2) {
bb1.getASuccessor(any(MatchingSuccessor s | s.getValue() = true)) = bb2 and
bb1.getLastNode() = this.getPatternNode()
}
predicate nonMatchEdge(BasicBlock bb1, BasicBlock bb2) {
bb1.getASuccessor(any(MatchingSuccessor s | s.getValue() = false)) = bb2 and
bb1.getLastNode() = this.getPatternNode()
}
}
class AndExpr extends BinaryExpr {
AndExpr() {
this instanceof AndBitwiseExpr or
this instanceof AndLogicalExpr or
this instanceof AssignAndExpr
}
}
class OrExpr extends BinaryExpr {
OrExpr() {
this instanceof OrBitwiseExpr or
this instanceof OrLogicalExpr or
this instanceof AssignOrExpr
}
}
class NotExpr = J::LogNotExpr;
class IdExpr extends Expr {
IdExpr() { this instanceof AssignExpr or this instanceof CastExpr }
Expr getEqualChildExpr() {
result = this.(AssignExpr).getSource()
or
result = this.(CastExpr).getExpr()
}
}
private predicate objectsEquals(Method equals) {
equals.hasQualifiedName("java.util", "Objects", "equals") and
equals.getNumberOfParameters() = 2
}
pragma[nomagic]
predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) {
exists(EqualityTest eq | eq = eqtest |
eq.getLeftOperand() = left and
eq.getRightOperand() = right and
eq.polarity() = polarity
)
or
exists(MethodCall call | call = eqtest and polarity = true |
call.getMethod() instanceof EqualsMethod and
call.getQualifier() = left and
call.getAnArgument() = right
or
objectsEquals(call.getMethod()) and
call.getArgument(0) = left and
call.getArgument(1) = right
)
}
class ConditionalExpr = J::ConditionalExpr;
class Parameter = J::Parameter;
private int parameterPosition() { result in [-1, any(Parameter p).getPosition()] }
/** A parameter position represented by an integer. */
class ParameterPosition extends int {
ParameterPosition() { this = parameterPosition() }
}
/** An argument position represented by an integer. */
class ArgumentPosition extends int {
ArgumentPosition() { this = parameterPosition() }
}
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
overlay[caller?]
pragma[inline]
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
final private class FinalMethod = Method;
class NonOverridableMethod extends FinalMethod {
NonOverridableMethod() { not super.isOverridable() }
Parameter getParameter(ParameterPosition ppos) {
super.getParameter(ppos) = result and
not result.isVarargs()
}
GuardsInput::Expr getAReturnExpr() {
exists(ReturnStmt ret |
this = ret.getEnclosingCallable() and
ret.getExpr() = result
)
}
}
private predicate nonOverridableMethodCall(MethodCall call, NonOverridableMethod m) {
call.getMethod().getSourceDeclaration() = m
}
class NonOverridableMethodCall extends GuardsInput::Expr instanceof MethodCall {
NonOverridableMethodCall() { nonOverridableMethodCall(this, _) }
NonOverridableMethod getMethod() { nonOverridableMethodCall(this, result) }
GuardsInput::Expr getArgument(ArgumentPosition apos) { result = super.getArgument(apos) }
}
}
private module GuardsImpl = SharedGuards::Make<Location, Cfg, GuardsInput>;
private module LogicInputCommon {
private import semmle.code.java.dataflow.NullGuards as NullGuards
predicate additionalNullCheck(
GuardsImpl::PreGuard guard, GuardValue val, GuardsInput::Expr e, boolean isNull
) {
guard.(InstanceOfExpr).getExpr() = e and val.asBooleanValue() = true and isNull = false
or
exists(MethodCall call |
call = guard and
call.getAnArgument() = e and
NullGuards::nullCheckMethod(call.getMethod(), val.asBooleanValue(), isNull)
)
}
predicate additionalImpliesStep(
GuardsImpl::PreGuard g1, GuardValue v1, GuardsImpl::PreGuard g2, GuardValue v2
) {
exists(MethodCall check |
g1 = check and
v1.getDualValue().isThrowsException()
|
methodCallChecksBoolean(check, g2, v2.asBooleanValue())
or
methodCallChecksNotNull(check, g2) and v2.isNonNullValue()
)
}
}
private module LogicInput_v1 implements GuardsImpl::LogicInputSig {
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
import Ssa
predicate additionalNullCheck = LogicInputCommon::additionalNullCheck/4;
predicate additionalImpliesStep = LogicInputCommon::additionalImpliesStep/4;
}
private module LogicInput_v3 implements GuardsImpl::LogicInputSig {
private import semmle.code.java.dataflow.IntegerGuards as IntegerGuards
import LogicInput_v2
predicate rangeGuard(GuardsImpl::PreGuard guard, GuardValue val, Expr e, int k, boolean upper) {
IntegerGuards::rangeGuard(guard, val.asBooleanValue(), e, k, upper)
}
predicate additionalNullCheck = LogicInputCommon::additionalNullCheck/4;
predicate additionalImpliesStep = LogicInputCommon::additionalImpliesStep/4;
}
class GuardValue = GuardsImpl::GuardValue;
/** INTERNAL: Don't use. */
module Guards_v1 = GuardsImpl::Logic<LogicInput_v1>;
/** INTERNAL: Don't use. */
module Guards_v2 = GuardsImpl::Logic<LogicInput_v2>;
/** INTERNAL: Don't use. */
module Guards_v3 = GuardsImpl::Logic<LogicInput_v3>;
/**
* A guard. This may be any expression whose value determines subsequent
* control flow. It may also be a switch case, which as a guard is considered
* to evaluate to either true or false depending on whether the case matches.
*/
final class Guard extends Guards_v3::Guard {
/** Gets the immediately enclosing callable whose body contains this guard. */
Callable getEnclosingCallable() {
result = this.(Expr).getEnclosingCallable() or
result = this.(SwitchCase).getEnclosingCallable()
}
/**
* Holds if this guard tests whether `testedExpr` has type `testedType`.
*
* `restricted` is true if the test applies additional restrictions on top of just `testedType`, and so
* this guard failing does not guarantee `testedExpr` is *not* a `testedType`-- for example,
* matching `record R(Object o)` with `case R(String s)` is a guard with an additional restriction on the
* type of field `o`, so the guard passing guarantees `testedExpr` is an `R`, but it failing does not
* guarantee `testedExpr` is not an `R`.
*/
predicate appliesTypeTest(Expr testedExpr, Type testedType, boolean restricted) {
(
exists(InstanceOfExpr ioe | this = ioe |
testedExpr = ioe.getExpr() and
testedType = ioe.getSyntacticCheckedType()
)
or
exists(PatternCase pc | this = pc |
pc.getSelectorExpr() = testedExpr and
testedType = pc.getUniquePattern().getType()
)
) and
(
if
exists(RecordPatternExpr rpe |
rpe = [this.(InstanceOfExpr).getPattern(), this.(PatternCase).getAPattern()]
|
not rpe.isUnrestricted()
)
then restricted = true
else restricted = false
)
}
}
/**
* INTERNAL: Use `Guard.controls` instead.
*
* Holds if `guard.controls(controlled, branch)`, except this only relies on
* BaseSSA-based reasoning.
*/
predicate guardControls_v1(Guards_v1::Guard guard, BasicBlock controlled, boolean branch) {
guard.controls(controlled, branch)
}