mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
296 lines
10 KiB
Plaintext
296 lines
10 KiB
Plaintext
/**
|
|
* Provides classes and predicates for nullness analysis.
|
|
*
|
|
* Local variables that may be null are tracked to see if they might reach
|
|
* a dereference and cause a NullPointerException. Assertions are assumed to
|
|
* hold, so results guarded by, for example, `assert x != null;` or
|
|
* `if (x == null) { assert false; }` are excluded.
|
|
*/
|
|
overlay[local?]
|
|
module;
|
|
|
|
import java
|
|
private import SSA
|
|
private import semmle.code.java.controlflow.Guards
|
|
private import IntegerGuards
|
|
private import NullGuards
|
|
private import semmle.code.java.Collections
|
|
private import semmle.code.java.controlflow.internal.Preconditions
|
|
private import semmle.code.java.controlflow.ControlFlowReachability
|
|
|
|
/** Gets an expression that may be `null`. */
|
|
Expr nullExpr() { result = nullExpr(_) }
|
|
|
|
/** Gets an expression that may be `null`. */
|
|
private Expr nullExpr(Expr reason) {
|
|
result instanceof NullLiteral and reason = result
|
|
or
|
|
result.(ChooseExpr).getAResultExpr() = nullExpr(reason)
|
|
or
|
|
result.(AssignExpr).getSource() = nullExpr(reason)
|
|
or
|
|
result.(CastExpr).getExpr() = nullExpr(reason)
|
|
or
|
|
result.(ImplicitCastExpr).getExpr() = nullExpr(reason)
|
|
or
|
|
result instanceof SafeCastExpr and reason = result
|
|
}
|
|
|
|
/** An expression of a boxed type that is implicitly unboxed. */
|
|
private predicate unboxed(Expr e) {
|
|
e.getType() instanceof BoxedType and
|
|
(
|
|
exists(ArrayAccess aa | aa.getIndexExpr() = e)
|
|
or
|
|
exists(ArrayCreationExpr ace | ace.getADimension() = e)
|
|
or
|
|
exists(LocalVariableDeclExpr decl |
|
|
decl.getVariable().getType() instanceof PrimitiveType and decl.getInit() = e
|
|
)
|
|
or
|
|
exists(AssignExpr assign |
|
|
assign.getDest().getType() instanceof PrimitiveType and assign.getSource() = e
|
|
)
|
|
or
|
|
exists(AssignOp assign | assign.getSource() = e and assign.getType() instanceof PrimitiveType)
|
|
or
|
|
exists(EqualityTest eq |
|
|
eq.getAnOperand() = e and eq.getAnOperand().getType() instanceof PrimitiveType
|
|
)
|
|
or
|
|
exists(BinaryExpr bin |
|
|
bin.getAnOperand() = e and
|
|
not bin instanceof EqualityTest and
|
|
bin.getType() instanceof PrimitiveType
|
|
)
|
|
or
|
|
exists(UnaryExpr un | un.getExpr() = e)
|
|
or
|
|
exists(ChooseExpr cond | cond.getType() instanceof PrimitiveType | cond.getAResultExpr() = e)
|
|
or
|
|
exists(ConditionNode cond | cond.getCondition() = e)
|
|
or
|
|
exists(Parameter p | p.getType() instanceof PrimitiveType and p.getAnArgument() = e)
|
|
or
|
|
exists(ReturnStmt ret |
|
|
ret.getEnclosingCallable().getReturnType() instanceof PrimitiveType and ret.getResult() = e
|
|
)
|
|
)
|
|
}
|
|
|
|
/** An expression that is being dereferenced. These are the points where `NullPointerException`s can occur. */
|
|
predicate dereference(Expr e) {
|
|
exists(EnhancedForStmt for | for.getExpr() = e)
|
|
or
|
|
exists(SynchronizedStmt synch | synch.getExpr() = e)
|
|
or
|
|
exists(SwitchStmt switch | switch.getExpr() = e and not switch.hasNullCase())
|
|
or
|
|
exists(SwitchExpr switch | switch.getExpr() = e and not switch.hasNullCase())
|
|
or
|
|
exists(FieldAccess fa, Field f | fa.getQualifier() = e and fa.getField() = f and not f.isStatic())
|
|
or
|
|
exists(MethodCall ma, Method m |
|
|
ma.getQualifier() = e and ma.getMethod() = m and not m.isStatic()
|
|
)
|
|
or
|
|
exists(ClassInstanceExpr cie | cie.getQualifier() = e)
|
|
or
|
|
exists(ArrayAccess aa | aa.getArray() = e)
|
|
or
|
|
exists(CastingExpr cast |
|
|
(cast instanceof CastExpr or cast instanceof ImplicitCastExpr) and
|
|
cast.getExpr() = e and
|
|
e.getType() instanceof BoxedType and
|
|
cast.getType() instanceof PrimitiveType
|
|
)
|
|
or
|
|
unboxed(e)
|
|
}
|
|
|
|
/**
|
|
* Gets the `ControlFlowNode` in which the given SSA variable is being dereferenced.
|
|
*
|
|
* The `VarAccess` is included for nicer error reporting.
|
|
*/
|
|
private ControlFlowNode varDereference(SsaVariable v, VarAccess va) {
|
|
dereference(result.asExpr()) and
|
|
result.asExpr() = sameValue(v, va)
|
|
}
|
|
|
|
/**
|
|
* The first dereference of a variable in a given `BasicBlock`.
|
|
*/
|
|
private predicate firstVarDereferenceInBlock(BasicBlock bb, SsaVariable v, VarAccess va) {
|
|
exists(ControlFlowNode n |
|
|
varDereference(v, va) = n and
|
|
n.getBasicBlock() = bb and
|
|
n =
|
|
min(ControlFlowNode n0, int i |
|
|
varDereference(v, _) = n0 and bb.getNode(i) = n0
|
|
|
|
|
n0 order by i
|
|
)
|
|
)
|
|
}
|
|
|
|
/** A variable suspected of being `null`. */
|
|
private predicate varMaybeNull(SsaVariable v, ControlFlowNode node, string msg, Expr reason) {
|
|
// A variable compared to null might be null.
|
|
exists(Expr e |
|
|
reason = e and
|
|
msg = "as suggested by $@ null guard" and
|
|
guardSuggestsVarMaybeNull(e, v) and
|
|
node = v.getCfgNode() and
|
|
not v instanceof SsaPhiDefinition and
|
|
not clearlyNotNull(v) and
|
|
// Comparisons in finally blocks are excluded since missing exception edges in the CFG could otherwise yield FPs.
|
|
not exists(TryStmt try | try.getFinally() = e.getEnclosingStmt().getEnclosingStmt*()) and
|
|
(
|
|
e = any(ConditionalExpr c).getCondition().getAChildExpr*() or
|
|
not exists(MethodCall ma | ma.getAnArgument().getAChildExpr*() = e)
|
|
) and
|
|
// Don't use a guard as reason if there is a null assignment.
|
|
not v.(SsaExplicitWrite).getDefiningExpr().(VariableAssign).getSource() = nullExpr()
|
|
)
|
|
or
|
|
// A parameter might be null if there is a null argument somewhere.
|
|
exists(Parameter p, Expr arg |
|
|
v.(SsaParameterInit).getParameter() = p and
|
|
node = v.getCfgNode() and
|
|
p.getAnArgument() = arg and
|
|
reason = arg and
|
|
msg = "because of $@ null argument" and
|
|
arg = nullExpr() and
|
|
not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod
|
|
)
|
|
or
|
|
// If the source of a variable is null then the variable may be null.
|
|
exists(VariableAssign def |
|
|
v.(SsaExplicitWrite).getDefiningExpr() = def and
|
|
def.getSource() = nullExpr(node.asExpr()) and
|
|
reason = def and
|
|
msg = "because of $@ assignment"
|
|
)
|
|
}
|
|
|
|
/** Gets an array or collection that contains at least one element. */
|
|
private Expr nonEmptyExpr() {
|
|
// An array creation with a known positive size is trivially non-empty.
|
|
result.(ArrayCreationExpr).getFirstDimensionSize() > 0
|
|
or
|
|
exists(SsaVariable v |
|
|
// A use of an array variable is non-empty if...
|
|
result = v.getAUse() and
|
|
v.getSourceVariable().getType() instanceof Array
|
|
|
|
|
// ...its definition is non-empty...
|
|
v.(SsaExplicitWrite).getValue() = nonEmptyExpr()
|
|
or
|
|
// ...or it is guarded by a condition proving its length to be non-zero.
|
|
exists(ConditionBlock cond, boolean branch, FieldAccess length |
|
|
cond.controls(result.getBasicBlock(), branch) and
|
|
cond.getCondition() = nonZeroGuard(length, branch) and
|
|
length.getField().hasName("length") and
|
|
length.getQualifier() = v.getAUse()
|
|
)
|
|
)
|
|
or
|
|
exists(SsaVariable v |
|
|
// A use of a Collection variable is non-empty if...
|
|
result = v.getAUse() and
|
|
v.getSourceVariable().getType() instanceof CollectionType and
|
|
exists(ConditionBlock cond, boolean branch, Expr c |
|
|
// ...it is guarded by a condition...
|
|
cond.controls(result.getBasicBlock(), branch) and
|
|
// ...and it isn't modified in the scope of the condition...
|
|
forall(MethodCall ma, Method m |
|
|
m = ma.getMethod() and
|
|
ma.getQualifier() = v.getSourceVariable().getAnAccess() and
|
|
cond.controls(ma.getBasicBlock(), branch)
|
|
|
|
|
m instanceof CollectionQueryMethod
|
|
) and
|
|
cond.getCondition() = c
|
|
|
|
|
// ...and the condition proves that it is non-empty, either by using the `isEmpty` method...
|
|
c.(MethodCall).getMethod().hasName("isEmpty") and
|
|
branch = false and
|
|
c.(MethodCall).getQualifier() = v.getAUse()
|
|
or
|
|
// ...or a check on its `size`.
|
|
exists(MethodCall size |
|
|
c = nonZeroGuard(size, branch) and
|
|
size.getMethod().hasName("size") and
|
|
size.getQualifier() = v.getAUse()
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/** The control flow edge that exits an enhanced for loop if the `Iterable` is empty. */
|
|
private predicate enhancedForEarlyExit(EnhancedForStmt for, ControlFlowNode n1, ControlFlowNode n2) {
|
|
exists(Expr forExpr |
|
|
n1.getANormalSuccessor() = n2 and
|
|
for.getExpr() = forExpr and
|
|
forExpr.getAChildExpr*() = n1.asExpr() and
|
|
not forExpr.getAChildExpr*() = n2.asExpr() and
|
|
n1.getANormalSuccessor().asExpr() = for.getVariable() and
|
|
not n2.asExpr() = for.getVariable()
|
|
)
|
|
}
|
|
|
|
/** A control flow edge that cannot be taken. */
|
|
private predicate impossibleEdge(BasicBlock bb1, BasicBlock bb2) {
|
|
exists(EnhancedForStmt for |
|
|
enhancedForEarlyExit(for, bb1.getANode(), bb2.getANode()) and
|
|
for.getExpr() = nonEmptyExpr()
|
|
)
|
|
}
|
|
|
|
private module NullnessConfig implements ControlFlowReachability::ConfigSig {
|
|
predicate source(ControlFlowNode node, SsaDefinition def) { varMaybeNull(def, node, _, _) }
|
|
|
|
predicate sink(ControlFlowNode node, SsaDefinition def) { varDereference(def, _) = node }
|
|
|
|
predicate barrierValue(GuardValue gv) { gv.isNullness(false) }
|
|
|
|
predicate barrierEdge(BasicBlock bb1, BasicBlock bb2) { impossibleEdge(bb1, bb2) }
|
|
|
|
predicate uncertainFlow() { none() }
|
|
}
|
|
|
|
private module NullnessFlow = ControlFlowReachability::Flow<NullnessConfig>;
|
|
|
|
/**
|
|
* Holds if the dereference of `v` at `va` might be `null`.
|
|
*/
|
|
predicate nullDeref(SsaSourceVariable v, VarAccess va, string msg, Expr reason) {
|
|
exists(SsaVariable origin, SsaVariable ssa, ControlFlowNode src, ControlFlowNode sink |
|
|
varMaybeNull(origin, src, msg, reason) and
|
|
NullnessFlow::flow(src, origin, sink, ssa) and
|
|
ssa.getSourceVariable() = v and
|
|
varDereference(ssa, va) = sink
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A dereference of a variable that is always `null`.
|
|
*/
|
|
predicate alwaysNullDeref(SsaSourceVariable v, VarAccess va) {
|
|
exists(BasicBlock bb, SsaDefinition ssa |
|
|
forall(SsaDefinition def | def = ssa.getAnUltimateDefinition() |
|
|
def.(SsaExplicitWrite).getValue() = alwaysNullExpr()
|
|
)
|
|
or
|
|
nullGuardControls(ssa, true, bb) and
|
|
not clearlyNotNull(ssa)
|
|
|
|
|
// Exclude fields as they might not have an accurate ssa representation.
|
|
not v.getVariable() instanceof Field and
|
|
firstVarDereferenceInBlock(bb, ssa, va) and
|
|
ssa.getSourceVariable() = v and
|
|
not nullGuardControls(ssa, false, bb)
|
|
)
|
|
}
|