Java: Replace nullness implementation.

This commit is contained in:
Anders Schack-Mulligen
2025-08-14 14:17:19 +02:00
parent 4a8ffea0f6
commit 03321ff910
5 changed files with 41 additions and 577 deletions

View File

@@ -9,40 +9,14 @@
overlay[local?]
module;
/*
* Implementation details:
*
* The three exported predicates, `nullDeref`, `alwaysNullDeref`, and
* `superfluousNullGuard`, compute potential null dereferences, definite null
* dereferences, and superfluous null checks, respectively. The bulk of the
* library supports `nullDeref`, while the latter two are fairly simple in
* comparison.
*
* The NPE (NullPointerException) candidates are computed by
* `nullDerefCandidate` and consist of three parts: A variable definition that
* might be null as computed by `varMaybeNull`, a dereference that can cause a
* NPE as computed by `firstVarDereferenceInBlock`, and a control flow path
* between the two points. The path is computed by `varMaybeNullInBlock`,
* which is the transitive closure of the step relation `nullVarStep`
* originating in a definition given by `varMaybeNull`. The step relation
* `nullVarStep` is essentially just the successor relation on basic blocks
* restricted to exclude edges along which the variable cannot be null.
*
* The step relation `nullVarStep` is then reused twice to produce two
* refinements of the path reachability predicate `varMaybeNullInBlock` in
* order to prune impossible paths that would otherwise lead to a potential
* NPE. These two refinements are `varMaybeNullInBlock_corrCond` and
* `varMaybeNullInBlock_trackVar` and are described in further detail below.
*/
import java
private import SSA
private import semmle.code.java.controlflow.Guards
private import RangeUtils
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.ControlFlow as Cf
/** Gets an expression that may be `null`. */
Expr nullExpr() { result = nullExpr(_) }
@@ -295,536 +269,29 @@ private predicate impossibleEdge(BasicBlock bb1, BasicBlock bb2) {
)
}
/** A control flow edge that leaves a finally-block. */
private predicate leavingFinally(BasicBlock bb1, BasicBlock bb2, boolean normaledge) {
exists(TryStmt try, BlockStmt finally |
try.getFinally() = finally and
bb1.getASuccessor() = bb2 and
bb1.getFirstNode().getEnclosingStmt().getEnclosingStmt*() = finally and
not bb2.getFirstNode().getEnclosingStmt().getEnclosingStmt*() = finally and
if bb1.getLastNode().getANormalSuccessor() = bb2.getFirstNode()
then normaledge = true
else normaledge = false
)
private module NullnessConfig implements Cf::ControlFlow::ConfigSig {
predicate source(ControlFlowNode node, SsaVariable def) { varMaybeNull(def, node, _, _) }
predicate sink(ControlFlowNode node, SsaVariable def) { varDereference(def, _) = node }
predicate barrierValue(GuardValue gv) { gv.isNullness(false) }
predicate barrierEdge(BasicBlock bb1, BasicBlock bb2) { impossibleEdge(bb1, bb2) }
predicate uncertainFlow() { none() }
}
private predicate ssaSourceVarMaybeNull(SsaSourceVariable v) {
varMaybeNull(v.getAnSsaVariable(), _, _, _)
}
private module NullnessFlow = Cf::ControlFlow::Flow<NullnessConfig>;
/**
* The step relation for propagating that a given SSA variable might be `null` in a given `BasicBlock`.
*
* If `midssa` is null in `mid` then `ssa` might be null in `bb`. The SSA variables share the same
* `SsaSourceVariable`.
*
* A boolean flag tracks whether a non-normal completion is waiting to resume upon the exit of a finally-block.
* If the flag is set, then the normal edge out of the finally-block is prohibited, but if it is not set then
* no knowledge is assumed of any potentially waiting completions. `midstoredcompletion` is the flag before
* the step and `storedcompletion` is the flag after the step.
*/
private predicate nullVarStep(
SsaVariable midssa, BasicBlock mid, boolean midstoredcompletion, SsaVariable ssa, BasicBlock bb,
boolean storedcompletion
) {
exists(SsaSourceVariable v |
ssaSourceVarMaybeNull(v) and
midssa.getSourceVariable() = v
|
ssa.(SsaPhiNode).getAPhiInput() = midssa and ssa.getBasicBlock() = bb
or
ssa = midssa and
not exists(SsaPhiNode phi | phi.getSourceVariable() = v and phi.getBasicBlock() = bb)
) and
(midstoredcompletion = true or midstoredcompletion = false) and
midssa.isLiveAtEndOfBlock(mid) and
not ensureNotNull(midssa).getBasicBlock() = mid and
not assertFail(mid, _) and
bb = mid.getASuccessor() and
not impossibleEdge(mid, bb) and
not nullGuardControlsBranchEdge(midssa, false, mid, bb) and
not (leavingFinally(mid, bb, true) and midstoredcompletion = true) and
if bb.getFirstNode().asStmt() = any(TryStmt try | | try.getFinally())
then
if bb.getFirstNode() = mid.getLastNode().getANormalSuccessor()
then storedcompletion = false
else storedcompletion = true
else
if leavingFinally(mid, bb, _)
then storedcompletion = false
else storedcompletion = midstoredcompletion
}
/**
* The transitive closure of `nullVarStep` originating from `varMaybeNull`. That is, those `BasicBlock`s
* for which the SSA variable is suspected of being `null`.
*/
private predicate varMaybeNullInBlock(
SsaVariable ssa, SsaSourceVariable v, BasicBlock bb, boolean storedcompletion
) {
varMaybeNull(ssa, _, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
v = ssa.getSourceVariable()
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
varMaybeNullInBlock(midssa, v, mid, midstoredcompletion) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
)
}
/**
* Holds if `v` is a source variable that might reach a potential `null`
* dereference.
*/
private predicate nullDerefCandidateVariable(SsaSourceVariable v) {
exists(SsaVariable ssa, BasicBlock bb |
firstVarDereferenceInBlock(bb, ssa, _) and
varMaybeNullInBlock(ssa, v, bb, _)
)
}
private predicate varMaybeNullInBlock_origin(
SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion
) {
nullDerefCandidateVariable(ssa.getSourceVariable()) and
varMaybeNull(ssa, _, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
origin = ssa
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
varMaybeNullInBlock_origin(origin, midssa, mid, midstoredcompletion) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
)
}
/**
* A potential `null` dereference. That is, the first dereference of a variable in a block
* where it is suspected of being `null`.
*/
private predicate nullDerefCandidate(SsaVariable origin, VarAccess va) {
exists(SsaVariable ssa, BasicBlock bb |
firstVarDereferenceInBlock(bb, ssa, va) and
varMaybeNullInBlock_origin(origin, ssa, bb, _)
)
}
/*
* In the following, the potential `null` dereference candidates are pruned by proving that
* a `NullPointerException` (NPE) cannot occur. This is done by pruning the control-flow paths
* that lead to the NPE candidate in two ways:
*
* 1. For each set of correlated conditions that are passed by the path, consistent
* branches must be taken. For example, the following code is safe due to the two tests on
* `flag` begin correlated.
* ```
* x = null;
* if (flag) x = new A();
* if (flag) x.m();
* ```
*
* 2. For each other variable that changes its value alongside the potential NPE candidate,
* the passed conditions must be consistent with its value. For example, the following
* code is safe due to the value of `t`.
* ```
* x = null;
* t = null;
* if (...) { x = new A(); t = new B(); }
* if (t != null) x.m();
* ```
* We call such a variable a _tracking variable_ as it tracks the null-ness of `x`.
*/
/** A variable that is assigned `null` if the given condition takes the given branch. */
private predicate varConditionallyNull(SsaExplicitUpdate v, ConditionBlock cond, boolean branch) {
exists(ConditionalExpr condexpr |
v.getDefiningExpr().(VariableAssign).getSource() = condexpr and
condexpr.getCondition() = cond.getCondition()
|
condexpr.getBranchExpr(branch) = nullExpr() and
not condexpr.getBranchExpr(branch.booleanNot()) = nullExpr()
)
or
v.getDefiningExpr().(VariableAssign).getSource() = nullExpr() and
cond.controls(v.getBasicBlock(), branch)
}
/**
* A condition that might be useful in proving an NPE candidate safe.
*
* This is a condition along the path that found the NPE candidate.
*/
private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond) {
nullDerefCandidateVariable(npecand) and
(
varMaybeNullInBlock(_, npecand, cond, _) or
varConditionallyNull(npecand.getAnSsaVariable(), cond, _)
) and
not cond.getCondition().(Expr).getAChildExpr*() = npecand.getAnAccess()
}
pragma[nomagic]
private ConditionBlock ssaIntegerGuard(SsaVariable v, boolean branch, int k, boolean is_k) {
result.getCondition() = integerGuard(v.getAUse(), branch, k, is_k)
}
pragma[nomagic]
private ConditionBlock ssaIntBoundGuard(SsaVariable v, boolean branch_with_lower_bound_k, int k) {
result.getCondition() = intBoundGuard(v.getAUse(), branch_with_lower_bound_k, k)
}
pragma[nomagic]
private ConditionBlock ssaEnumConstEquality(SsaVariable v, boolean polarity, EnumConstant c) {
result.getCondition() = enumConstEquality(v.getAUse(), polarity, c)
}
private predicate conditionChecksNull(ConditionBlock cond, SsaVariable v, boolean branchIsNull) {
nullGuardControlsBranchEdge(v, true, cond, cond.getTestSuccessor(branchIsNull)) and
nullGuardControlsBranchEdge(v, false, cond, cond.getTestSuccessor(branchIsNull.booleanNot()))
}
/** A pair of correlated conditions for a given NPE candidate. */
private predicate correlatedConditions(
SsaSourceVariable npecand, ConditionBlock cond1, ConditionBlock cond2, boolean inverted
) {
interestingCond(npecand, cond1) and
interestingCond(npecand, cond2) and
cond1 != cond2 and
(
exists(SsaVariable v |
cond1.getCondition() = v.getAUse() and
cond2.getCondition() = v.getAUse() and
inverted = false
)
or
exists(SsaVariable v, boolean branch1, boolean branch2 |
conditionChecksNull(cond1, v, branch1) and
conditionChecksNull(cond2, v, branch2) and
inverted = branch1.booleanXor(branch2)
)
or
exists(SsaVariable v, int k, boolean branch1, boolean branch2 |
cond1 = ssaIntegerGuard(v, branch1, k, true) and
cond1 = ssaIntegerGuard(v, branch1.booleanNot(), k, false) and
cond2 = ssaIntegerGuard(v, branch2, k, true) and
cond2 = ssaIntegerGuard(v, branch2.booleanNot(), k, false) and
inverted = branch1.booleanXor(branch2)
)
or
exists(SsaVariable v, int k, boolean branch1, boolean branch2 |
cond1 = ssaIntBoundGuard(v, branch1, k) and
cond2 = ssaIntBoundGuard(v, branch2, k) and
inverted = branch1.booleanXor(branch2)
)
or
exists(SsaVariable v, EnumConstant c, boolean pol1, boolean pol2 |
cond1 = ssaEnumConstEquality(v, pol1, c) and
cond2 = ssaEnumConstEquality(v, pol2, c) and
inverted = pol1.booleanXor(pol2)
)
or
exists(SsaVariable v, RefType type |
cond1.getCondition() = instanceofExpr(v, type) and
cond2.getCondition() = instanceofExpr(v, type) and
inverted = false
)
or
exists(SsaVariable v1, SsaVariable v2, boolean branch1, boolean branch2 |
cond1.getCondition() = varEqualityTestExpr(v1, v2, branch1) and
cond2.getCondition() = varEqualityTestExpr(v1, v2, branch2) and
inverted = branch1.booleanXor(branch2)
)
)
}
/**
* This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but
* this time restricted based on pairs of correlated conditions consistent with `cond1`
* evaluating to `branch`.
*/
private predicate varMaybeNullInBlock_corrCond(
SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion,
ConditionBlock cond1, boolean branch
) {
exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() |
nullDerefCandidateVariable(npecand) and correlatedConditions(npecand, cond1, _, _)
) and
(
varConditionallyNull(ssa, cond1, branch)
or
not varConditionallyNull(ssa, cond1, _) and
(branch = true or branch = false)
) and
varMaybeNull(ssa, _, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
origin = ssa
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
varMaybeNullInBlock_corrCond(origin, midssa, mid, midstoredcompletion, cond1, branch) and
(
cond1 = mid and cond1.getTestSuccessor(branch) = bb
or
exists(ConditionBlock cond2, boolean inverted, boolean branch2 |
cond2 = mid and
correlatedConditions(_, cond1, cond2, inverted) and
cond2.getTestSuccessor(branch2) = bb and
branch = branch2.booleanXor(inverted)
)
or
cond1 != mid and
not exists(ConditionBlock cond2 | cond2 = mid and correlatedConditions(_, cond1, cond2, _))
) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
)
}
/*
* A tracking variable has its possible values divided into two sets, A and B, for
* which we can attribute at least one direct assignment to be contained in either
* A or B.
* Four kinds are supported:
* - null: A means null and B means non-null.
* - boolean: A means true and B means false.
* - enum: A means a specific enum constant and B means any other value.
* - int: A means a specific integer value and B means any other value.
*/
private newtype TrackVarKind =
TrackVarKindNull() or
TrackVarKindBool() or
TrackVarKindEnum() or
TrackVarKindInt()
/** A variable that might be relevant as a tracking variable for the NPE candidate. */
private predicate trackingVar(
SsaSourceVariable npecand, SsaExplicitUpdate trackssa, SsaSourceVariable trackvar,
TrackVarKind kind, Expr init
) {
exists(ConditionBlock cond |
interestingCond(npecand, cond) and
varMaybeNullInBlock(_, npecand, cond, _) and
cond.getCondition().(Expr).getAChildExpr*() = trackvar.getAnAccess() and
trackssa.getSourceVariable() = trackvar and
trackssa.getDefiningExpr().(VariableAssign).getSource() = init
|
init instanceof NullLiteral and kind = TrackVarKindNull()
or
init = clearlyNotNullExpr() and kind = TrackVarKindNull()
or
init instanceof BooleanLiteral and kind = TrackVarKindBool()
or
init.(VarAccess).getVariable() instanceof EnumConstant and kind = TrackVarKindEnum()
or
exists(init.(ConstantIntegerExpr).getIntValue()) and kind = TrackVarKindInt()
)
}
/** Gets an expression that tests the value of a given tracking variable. */
private Expr trackingVarGuard(
SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, boolean branch, boolean isA
) {
exists(Expr init | trackingVar(_, trackssa, trackvar, kind, init) |
result = basicNullGuard(trackvar.getAnAccess(), branch, isA) and
kind = TrackVarKindNull()
or
result = trackvar.getAnAccess() and
kind = TrackVarKindBool() and
(branch = true or branch = false) and
isA = branch
or
exists(boolean polarity, EnumConstant c, EnumConstant initc |
initc.getAnAccess() = init and
kind = TrackVarKindEnum() and
result = enumConstEquality(trackvar.getAnAccess(), polarity, c) and
(
initc = c and branch = polarity.booleanNot() and isA = false
or
initc = c and branch = polarity and isA = true
or
initc != c and branch = polarity and isA = false
)
)
or
exists(int k |
init.(ConstantIntegerExpr).getIntValue() = k and
kind = TrackVarKindInt()
|
result = integerGuard(trackvar.getAnAccess(), branch, k, isA)
or
exists(int k2 |
result = integerGuard(trackvar.getAnAccess(), branch, k2, true) and
isA = false and
k2 != k
)
or
exists(int bound, boolean branch_with_lower_bound |
result = intBoundGuard(trackvar.getAnAccess(), branch_with_lower_bound, bound) and
isA = false
|
branch = branch_with_lower_bound and k < bound
or
branch = branch_with_lower_bound.booleanNot() and bound <= k
)
)
)
or
exists(EqualityTest eqtest, boolean branch0, boolean polarity, BooleanLiteral boollit |
eqtest = result and
eqtest.hasOperands(trackingVarGuard(trackssa, trackvar, kind, branch0, isA), boollit) and
eqtest.polarity() = polarity and
branch = branch0.booleanXor(polarity).booleanXor(boollit.getBooleanValue())
)
}
/** An update to a tracking variable that is contained fully in either A or B. */
private predicate isReset(
SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, SsaExplicitUpdate update,
boolean isA
) {
exists(Expr init, Expr e |
trackingVar(_, trackssa, trackvar, kind, init) and
update.getSourceVariable() = trackvar and
e = update.getDefiningExpr().(VariableAssign).getSource()
|
e instanceof NullLiteral and kind = TrackVarKindNull() and isA = true
or
e = clearlyNotNullExpr() and kind = TrackVarKindNull() and isA = false
or
e.(BooleanLiteral).getBooleanValue() = isA and kind = TrackVarKindBool()
or
e.(VarAccess).getVariable().(EnumConstant) = init.(VarAccess).getVariable() and
kind = TrackVarKindEnum() and
isA = true
or
e.(VarAccess).getVariable().(EnumConstant) != init.(VarAccess).getVariable() and
kind = TrackVarKindEnum() and
isA = false
or
e.(ConstantIntegerExpr).getIntValue() = init.(ConstantIntegerExpr).getIntValue() and
kind = TrackVarKindInt() and
isA = true
or
e.(ConstantIntegerExpr).getIntValue() != init.(ConstantIntegerExpr).getIntValue() and
kind = TrackVarKindInt() and
isA = false
)
}
/** The abstract value of the tracked variable. */
private newtype TrackedValue =
TrackedValueA() or
TrackedValueB() or
TrackedValueUnknown()
private TrackedValue trackValAorB(boolean isA) {
isA = true and result = TrackedValueA()
or
isA = false and result = TrackedValueB()
}
/** A control flow edge passing through a condition that implies a specific value for a tracking variable. */
private predicate stepImplies(
BasicBlock bb1, BasicBlock bb2, SsaVariable trackssa, SsaSourceVariable trackvar,
TrackVarKind kind, boolean isA
) {
exists(ConditionBlock cond, boolean branch |
cond = bb1 and
cond.getTestSuccessor(branch) = bb2 and
cond.getCondition() = trackingVarGuard(trackssa, trackvar, kind, branch, isA)
)
}
/**
* This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but
* this time restricted based on a tracking variable.
*/
private predicate varMaybeNullInBlock_trackVar(
SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion,
SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, TrackedValue trackvalue
) {
exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() |
nullDerefCandidateVariable(npecand) and trackingVar(npecand, trackssa, trackvar, kind, _)
) and
(
exists(SsaVariable init, boolean isA |
init.getSourceVariable() = trackvar and
init.isLiveAtEndOfBlock(bb) and
isReset(trackssa, trackvar, kind, init, isA) and
trackvalue = trackValAorB(isA)
)
or
trackvalue = TrackedValueUnknown() and
not exists(SsaVariable init |
init.getSourceVariable() = trackvar and
init.isLiveAtEndOfBlock(bb) and
isReset(trackssa, trackvar, kind, init, _)
)
) and
varMaybeNull(ssa, _, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
origin = ssa
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion, TrackedValue trackvalue0 |
varMaybeNullInBlock_trackVar(origin, midssa, mid, midstoredcompletion, trackssa, trackvar, kind,
trackvalue0) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) and
(
trackvalue0 = TrackedValueUnknown()
or
// A step that implies a value that contradicts the current value is not allowed.
exists(boolean isA | trackvalue0 = trackValAorB(isA) |
not stepImplies(mid, bb, trackssa, trackvar, kind, isA.booleanNot())
)
) and
(
// If no update occurs then the tracked value is unchanged unless the step implies a given value via a condition.
not exists(SsaUpdate update |
update.getSourceVariable() = trackvar and
update.getBasicBlock() = bb
) and
(
exists(boolean isA | stepImplies(mid, bb, trackssa, trackvar, kind, isA) |
trackvalue = trackValAorB(isA)
)
or
not stepImplies(mid, bb, trackssa, trackvar, kind, _) and trackvalue = trackvalue0
)
or
// If an update occurs then the tracked value is set accordingly.
exists(SsaUpdate update |
update.getSourceVariable() = trackvar and
update.getBasicBlock() = bb
|
exists(boolean isA | isReset(trackssa, trackvar, kind, update, isA) |
trackvalue = trackValAorB(isA)
)
or
not isReset(trackssa, trackvar, kind, update, _) and trackvalue = TrackedValueUnknown()
)
)
)
}
/**
* A potential `null` dereference that has not been proven safe.
* 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, BasicBlock bb |
nullDerefCandidate(origin, va) and
varMaybeNull(origin, _, msg, reason) and
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
firstVarDereferenceInBlock(bb, ssa, va) and
forall(ConditionBlock cond | correlatedConditions(v, cond, _, _) |
varMaybeNullInBlock_corrCond(origin, ssa, bb, _, cond, _)
) and
forall(SsaVariable guardssa, SsaSourceVariable guardvar, TrackVarKind kind |
trackingVar(v, guardssa, guardvar, kind, _)
|
varMaybeNullInBlock_trackVar(origin, ssa, bb, _, guardssa, guardvar, kind, _)
)
varDereference(ssa, va) = sink
)
}

View File

@@ -301,7 +301,7 @@ public class A {
public void assertThatTest() {
Object assertThat_ok1 = maybe() ? null : new Object();
assertThat(assertThat_ok1, notNullValue());
assertThat_ok1.toString();
assertThat_ok1.toString(); // FP
}
private boolean m;

View File

@@ -276,7 +276,7 @@ public class B {
int[] a = null;
if (iters > 0) a = new int[iters];
for (int i = 0; i < iters; ++i)
a[i] = 0; // NPE - false positive
a[i] = 0; // OK
if (iters > 0) {
String last = null;
@@ -289,7 +289,7 @@ public class B {
throw new RuntimeException();
}
for (int i = 0; i < iters; ++i) {
b[i] = 0; // NPE - false positive
b[i] = 0; // OK
}
}
@@ -331,7 +331,7 @@ public class B {
x = new Object();
}
if(y instanceof String) {
x.hashCode(); // OK
x.hashCode(); // Spurious NPE - false positive
}
}
@@ -341,7 +341,7 @@ public class B {
x = new Object();
}
if(!(y instanceof String)) {
x.hashCode(); // OK
x.hashCode(); // Spurious NPE - false positive
}
}
@@ -351,7 +351,7 @@ public class B {
x = new Object();
}
if(y == z) {
x.hashCode(); // OK
x.hashCode(); // Spurious NPE - false positive
}
Object x2 = null;
@@ -359,7 +359,7 @@ public class B {
x2 = new Object();
}
if(y != z) {
x2.hashCode(); // OK
x2.hashCode(); // Spurious NPE - false positive
}
Object x3 = null;
@@ -367,7 +367,7 @@ public class B {
x3 = new Object();
}
if(!(y == z)) {
x3.hashCode(); // OK
x3.hashCode(); // Spurious NPE - false positive
}
}
@@ -417,7 +417,7 @@ public class B {
x = null;
}
if (!b) {
x.hashCode(); // NPE - false negative
x.hashCode(); // NPE
}
// flow can loop around from one iteration to the next
}
@@ -445,7 +445,7 @@ public class B {
if (!ready) {
x = null;
} else {
x.hashCode(); // Spurious NPE - false positive
x.hashCode(); // OK
}
if ((a[i] & 1) != 0) {
ready = (a[i] & 2) != 0;
@@ -547,14 +547,14 @@ public class B {
if (n > 21) return;
if (maybe) {}
for (int i = 0; i < n; ++i) {
xs[i]++; // Spurious NPE - false positive
xs[i]++; // OK
}
}
public void rangetest(int n) {
String s = null;
if (n < 0 || n > 10) s = "A";
if (n > 100) s.hashCode(); // Spurious NPE - false positive
if (n == 42) s.hashCode(); // Spurious NPE - false positive
if (n > 100) s.hashCode(); // OK
if (n == 42) s.hashCode(); // OK
}
}

View File

@@ -97,7 +97,7 @@ public class C {
arr2 = new int[arr1.length];
}
for (int i = 0; i < arr1.length; i++)
arr2[i] = arr1[i]; // NPE - false positive
arr2[i] = arr1[i]; // OK
}
public void ex8(int x, int lim) {
@@ -107,7 +107,7 @@ public class C {
while (!stop) {
int j = 0;
while (!stop && j < lim) {
int step = (j * obj.hashCode()) % 10; // NPE - false positive
int step = (j * obj.hashCode()) % 10; // OK
if (step == 0) {
obj.hashCode();
i += 1;
@@ -134,7 +134,7 @@ public class C {
cond = true;
}
if (cond) {
obj2.hashCode(); // NPE - false positive
obj2.hashCode(); // OK
}
}
@@ -185,7 +185,7 @@ public class C {
b = true;
} else if (a[i] == 2) {
verifyBool(b);
obj.hashCode(); // NPE - false positive
obj.hashCode(); // OK
}
}
}

View File

@@ -4,6 +4,7 @@
| A.java:215:24:215:31 | if_maybe | Variable $@ may be null at this access because of $@ assignment. | A.java:211:5:211:25 | String if_maybe | if_maybe | A.java:213:7:213:21 | ...=... | this |
| A.java:230:26:230:34 | for_maybe | Variable $@ may be null at this access because of $@ assignment. | A.java:229:10:229:30 | String for_maybe | for_maybe | A.java:229:35:229:50 | ...=... | this |
| A.java:273:24:273:32 | for_maybe | Variable $@ may be null at this access because of $@ assignment. | A.java:268:5:268:63 | List<String> for_maybe | for_maybe | A.java:271:7:271:22 | ...=... | this |
| A.java:304:5:304:18 | assertThat_ok1 | Variable $@ may be null at this access because of $@ assignment. | A.java:302:5:302:58 | Object assertThat_ok1 | assertThat_ok1 | A.java:302:12:302:57 | assertThat_ok1 | this |
| B.java:16:5:16:9 | param | Variable $@ may be null at this access because of $@ null argument. | B.java:15:23:15:34 | param | param | B.java:11:13:11:16 | null | this |
| B.java:23:5:23:9 | param | Variable $@ may be null at this access as suggested by $@ null guard. | B.java:19:23:19:34 | param | param | B.java:20:9:20:21 | ... != ... | this |
| B.java:57:7:57:8 | o7 | Variable $@ may be null at this access because of $@ assignment. | B.java:52:5:52:34 | Object o7 | o7 | B.java:52:12:52:33 | o7 | this |
@@ -15,28 +16,24 @@
| B.java:118:5:118:7 | obj | Variable $@ may be null at this access as suggested by $@ null guard. | B.java:117:27:117:36 | obj | obj | B.java:119:13:119:23 | ... != ... | this |
| B.java:133:5:133:7 | obj | Variable $@ may be null at this access because of $@ assignment. | B.java:128:5:128:22 | Object obj | obj | B.java:128:12:128:21 | obj | this |
| B.java:190:7:190:7 | o | Variable $@ may be null at this access because of $@ assignment. | B.java:178:5:178:20 | Object o | o | B.java:186:5:186:12 | ...=... | this |
| B.java:279:7:279:7 | a | Variable $@ may be null at this access because of $@ assignment. | B.java:276:5:276:19 | int[] a | a | B.java:276:11:276:18 | a | this |
| B.java:292:7:292:7 | b | Variable $@ may be null at this access because of $@ assignment. | B.java:287:5:287:44 | int[] b | b | B.java:287:11:287:43 | b | this |
| B.java:334:7:334:7 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:329:5:329:20 | Object x | x | B.java:329:12:329:19 | x | this |
| B.java:344:7:344:7 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:339:5:339:20 | Object x | x | B.java:339:12:339:19 | x | this |
| B.java:354:7:354:7 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:349:5:349:20 | Object x | x | B.java:349:12:349:19 | x | this |
| B.java:362:7:362:8 | x2 | Variable $@ may be null at this access because of $@ assignment. | B.java:357:5:357:21 | Object x2 | x2 | B.java:357:12:357:20 | x2 | this |
| B.java:370:7:370:8 | x3 | Variable $@ may be null at this access because of $@ assignment. | B.java:365:5:365:21 | Object x3 | x3 | B.java:365:12:365:20 | x3 | this |
| B.java:408:7:408:7 | x | Variable $@ may be null at this access as suggested by $@ null guard. | B.java:374:23:374:30 | x | x | B.java:375:23:375:31 | ... != ... | this |
| B.java:448:9:448:9 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:442:5:442:28 | Object x | x | B.java:446:9:446:16 | ...=... | this |
| B.java:420:9:420:9 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:413:5:413:28 | Object x | x | B.java:417:9:417:16 | ...=... | this |
| B.java:465:9:465:9 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:458:5:458:28 | Object x | x | B.java:470:9:470:16 | ...=... | this |
| B.java:487:9:487:9 | x | Variable $@ may be null at this access because of $@ assignment. | B.java:476:5:476:20 | Object x | x | B.java:476:12:476:19 | x | this |
| B.java:516:5:516:5 | o | Variable $@ may be null at this access as suggested by $@ null guard. | B.java:511:25:511:32 | o | o | B.java:512:22:512:30 | ... == ... | this |
| B.java:535:7:535:8 | s2 | Variable $@ may be null at this access because of $@ assignment. | B.java:523:5:523:21 | String s2 | s2 | B.java:523:12:523:20 | s2 | this |
| B.java:550:7:550:8 | xs | Variable $@ may be null at this access as suggested by $@ null guard. | B.java:540:24:540:31 | xs | xs | B.java:543:19:543:28 | ... == ... | this |
| B.java:557:18:557:18 | s | Variable $@ may be null at this access because of $@ assignment. | B.java:555:5:555:20 | String s | s | B.java:555:12:555:19 | s | this |
| B.java:558:18:558:18 | s | Variable $@ may be null at this access because of $@ assignment. | B.java:555:5:555:20 | String s | s | B.java:555:12:555:19 | s | this |
| C.java:9:44:9:45 | a2 | Variable $@ may be null at this access as suggested by $@ null guard. | C.java:6:5:6:23 | long[][] a2 | a2 | C.java:7:34:7:54 | ... != ... | this |
| C.java:9:44:9:45 | a2 | Variable $@ may be null at this access because of $@ assignment. | C.java:6:5:6:23 | long[][] a2 | a2 | C.java:6:14:6:22 | a2 | this |
| C.java:10:17:10:18 | a3 | Variable $@ may be null at this access as suggested by $@ null guard. | C.java:8:5:8:21 | long[] a3 | a3 | C.java:9:38:9:58 | ... != ... | this |
| C.java:10:17:10:18 | a3 | Variable $@ may be null at this access because of $@ assignment. | C.java:8:5:8:21 | long[] a3 | a3 | C.java:8:12:8:20 | a3 | this |
| C.java:21:7:21:8 | s1 | Variable $@ may be null at this access because of $@ assignment. | C.java:14:5:14:30 | String s1 | s1 | C.java:17:7:17:24 | ...=... | this |
| C.java:51:7:51:11 | slice | Variable $@ may be null at this access because of $@ assignment. | C.java:43:5:43:30 | List<String> slice | slice | C.java:43:18:43:29 | slice | this |
| C.java:100:7:100:10 | arr2 | Variable $@ may be null at this access because of $@ assignment. | C.java:95:5:95:22 | int[] arr2 | arr2 | C.java:95:11:95:21 | arr2 | this |
| C.java:110:25:110:27 | obj | Variable $@ may be null at this access because of $@ assignment. | C.java:106:5:106:30 | Object obj | obj | C.java:118:13:118:22 | ...=... | this |
| C.java:137:7:137:10 | obj2 | Variable $@ may be null at this access as suggested by $@ null guard. | C.java:131:5:131:23 | Object obj2 | obj2 | C.java:132:9:132:20 | ... != ... | this |
| C.java:144:15:144:15 | a | Variable $@ may be null at this access as suggested by $@ null guard. | C.java:141:20:141:26 | a | a | C.java:142:13:142:21 | ... == ... | this |
| C.java:188:9:188:11 | obj | Variable $@ may be null at this access because of $@ assignment. | C.java:181:5:181:22 | Object obj | obj | C.java:181:12:181:21 | obj | this |
| C.java:219:9:219:10 | o1 | Variable $@ may be null at this access as suggested by $@ null guard. | C.java:212:20:212:28 | o1 | o1 | C.java:213:9:213:18 | ... == ... | this |
| C.java:233:7:233:8 | xs | Variable $@ may be null at this access because of $@ assignment. | C.java:231:5:231:56 | int[] xs | xs | C.java:231:11:231:55 | xs | this |
| F.java:11:5:11:7 | obj | Variable $@ may be null at this access as suggested by $@ null guard. | F.java:8:18:8:27 | obj | obj | F.java:9:9:9:19 | ... == ... | this |