mirror of
https://github.com/github/codeql.git
synced 2026-05-30 11:01:24 +02:00
420 lines
14 KiB
Plaintext
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)
|
|
}
|