edu.umd.cs.findbugs.ba.npe.IsNullValue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
The newest version!
/*
* Bytecode Analysis Framework
* Copyright (C) 2003-2005 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.ba.npe;
import javax.annotation.Nonnull;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.Debug;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.XMethodParameter;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
/**
* A class to abstractly represent values in stack slots, indicating whether
* thoses values can be null, non-null, null on some incoming path, or unknown.
*
* @author David Hovemeyer
* @see IsNullValueFrame
* @see IsNullValueAnalysis
*/
public class IsNullValue implements IsNullValueAnalysisFeatures, Debug {
private static final boolean DEBUG_EXCEPTION = SystemProperties.getBoolean("inv.debugException");
private static final boolean DEBUG_KABOOM = SystemProperties.getBoolean("inv.debugKaboom");
/** Definitely null. */
private static final int NULL = 0;
/** Definitely null because of a comparison to a known null value. */
private static final int CHECKED_NULL = 1;
/** Definitely not null. */
private static final int NN = 2;
/** Definitely not null because of a comparison to a known null value. */
private static final int CHECKED_NN = 3;
/**
* Definitely not null an NPE would have occurred and we would not be here
* if it were null.
*/
private static final int NO_KABOOM_NN = 4;
/** Null on some simple path (at most one branch) to current location. */
private static final int NSP = 5;
/**
* Unknown value (method param, value read from heap, etc.), assumed not
* null.
*/
private static final int NN_UNKNOWN = 6;
/** Null on some complex path (at least two branches) to current location. */
private static final int NCP2 = 7;
/** Null on some complex path (at least three branches) to current location. */
private static final int NCP3 = 8;
private static final int FLAG_SHIFT = 8;
/** Value was propagated along an exception path. */
private static final int EXCEPTION = 1 << FLAG_SHIFT;
/** Value is (potentially) null because of a parameter passed to the method. */
private static final int PARAM = 2 << FLAG_SHIFT;
/**
* Value is (potentially) null because of a value returned from a called
* method.
*/
private static final int RETURN_VAL = 4 << FLAG_SHIFT;
private static final int FIELD_VAL = 8 << FLAG_SHIFT;
/** Value is (potentially) null because of a value returned from readline. */
private static final int READLINE_VAL = (16 << FLAG_SHIFT) | RETURN_VAL;
private static final int FLAG_MASK = EXCEPTION | PARAM | RETURN_VAL | FIELD_VAL | READLINE_VAL;
private static final int[][] mergeMatrix = {
// NULL, CHECKED_NULL, NN, CHECKED_NN, NO_KABOOM_NN, NSP,
// NN_UNKNOWN, NCP2, NCP3
{ NULL }, // NULL
{ NULL, CHECKED_NULL, }, // CHECKED_NULL
{ NSP, NSP, NN }, // NN
{ NSP, NSP, NN, CHECKED_NN, }, // CHECKED_NN
{ NSP, NSP, NN, NN, NO_KABOOM_NN }, // NO_KABOOM_NN
{ NSP, NSP, NSP, NSP, NSP, NSP }, // NSP
{ NSP, NSP, NN_UNKNOWN, NN_UNKNOWN, NN_UNKNOWN, NSP, NN_UNKNOWN, }, // NN_UNKNOWN
{ NSP, NSP, NCP2, NCP2, NCP2, NSP, NCP2, NCP2, }, // NCP2
{ NSP, NSP, NCP3, NCP3, NCP3, NSP, NCP3, NCP3, NCP3 } // NCP3
};
private static final IsNullValue[][] instanceByFlagsList = createInstanceByFlagList();
private static IsNullValue[][] createInstanceByFlagList() {
final int max = FLAG_MASK >>> FLAG_SHIFT;
IsNullValue[][] result = new IsNullValue[max + 1][];
for (int i = 0; i <= max; ++i) {
final int flags = i << FLAG_SHIFT;
result[i] = new IsNullValue[] { new IsNullValue(NULL | flags), new IsNullValue(CHECKED_NULL | flags),
new IsNullValue(NN | flags), new IsNullValue(CHECKED_NN | flags),
null, // NO_KABOOM_NN values must be allocated dynamically
new IsNullValue(NSP | flags), new IsNullValue(NN_UNKNOWN | flags), new IsNullValue(NCP2 | flags),
new IsNullValue(NCP3 | flags), };
}
return result;
}
// Fields
private final int kind;
private final Location locationOfKaBoom;
private IsNullValue(int kind) {
this.kind = kind;
locationOfKaBoom = null;
if (VERIFY_INTEGRITY) {
checkNoKaboomNNLocation();
}
}
private IsNullValue(int kind, Location ins) {
this.kind = kind;
locationOfKaBoom = ins;
if (VERIFY_INTEGRITY) {
checkNoKaboomNNLocation();
}
}
private void checkNoKaboomNNLocation() {
if (getBaseKind() == NO_KABOOM_NN && locationOfKaBoom == null) {
throw new IllegalStateException("construction of no-KaBoom NN without Location");
}
}
@Override
public boolean equals(Object o) {
if (o == null || this.getClass() != o.getClass()) {
return false;
}
IsNullValue other = (IsNullValue) o;
if (kind != other.kind) {
return false;
}
if (locationOfKaBoom == other.locationOfKaBoom) {
return true;
}
if (locationOfKaBoom == null || other.locationOfKaBoom == null) {
return false;
}
return locationOfKaBoom.equals(other.locationOfKaBoom);
}
@Override
public int hashCode() {
int hashCode = kind;
if (locationOfKaBoom != null) {
hashCode += locationOfKaBoom.hashCode();
}
return hashCode;
}
private int getBaseKind() {
return kind & ~FLAG_MASK;
}
private int getFlags() {
return kind & FLAG_MASK;
}
private boolean hasFlag(int flag) {
return (kind & flag) == flag;
}
/**
* Was this value propagated on an exception path?
*/
public boolean isException() {
return hasFlag(EXCEPTION);
}
/**
* Was this value marked as a possibly null return value?
*/
public boolean isReturnValue() {
return hasFlag(RETURN_VAL);
}
public boolean isReadlineValue() {
return hasFlag(READLINE_VAL);
}
public boolean isFieldValue() {
return hasFlag(FIELD_VAL);
}
/**
* Was this value marked as a possibly null parameter?
*/
public boolean isParamValue() {
return (kind & PARAM) != 0;
}
/**
* Is this value known because of an explicit null check?
*/
public boolean isChecked() {
return getBaseKind() == CHECKED_NULL || getBaseKind() == CHECKED_NN;
}
/**
* Is this value known to be non null because a NPE would have occurred
* otherwise?
*/
public boolean wouldHaveBeenAKaboom() {
return getBaseKind() == NO_KABOOM_NN;
}
/*
private IsNullValue toBaseValue() {
return instanceByFlagsList[0][getBaseKind()];
}
*/
/**
* Convert to an exception path value.
*/
public IsNullValue toExceptionValue() {
if (getBaseKind() == NO_KABOOM_NN) {
return new IsNullValue(kind | EXCEPTION, locationOfKaBoom);
}
return instanceByFlagsList[(getFlags() | EXCEPTION) >> FLAG_SHIFT][getBaseKind()];
}
/**
* Convert to a value known because it was returned from a method in a
* method property database.
*
* @param methodInvoked
* TODO
*/
public IsNullValue markInformationAsComingFromReturnValueOfMethod(XMethod methodInvoked) {
FieldDescriptor fieldDescriptor = methodInvoked.getAccessMethodForField();
if (fieldDescriptor != null) {
XField f = XFactory.getExactXField(fieldDescriptor);
return markInformationAsComingFromFieldValue(f);
}
int flag = RETURN_VAL;
if ("readLine".equals(methodInvoked.getName()) && "()Ljava/lang/String;".equals(methodInvoked.getSignature())) {
flag = READLINE_VAL;
}
if (getBaseKind() == NO_KABOOM_NN) {
return new IsNullValue(kind | flag, locationOfKaBoom);
}
return instanceByFlagsList[(getFlags() | flag) >> FLAG_SHIFT][getBaseKind()];
}
/**
* Convert to a value known because it was returned from a method in a
* method property database.
*
* @param field
* TODO
*/
public IsNullValue markInformationAsComingFromFieldValue(XField field) {
if (getBaseKind() == NO_KABOOM_NN) {
return new IsNullValue(kind | FIELD_VAL, locationOfKaBoom);
}
return instanceByFlagsList[(getFlags() | FIELD_VAL) >> FLAG_SHIFT][getBaseKind()];
}
/**
* Get the instance representing values that are definitely null.
*/
public static IsNullValue nullValue() {
return instanceByFlagsList[0][NULL];
}
/**
* Get the instance representing a value known to be null because it was
* compared against null value, or because we saw that it was assigned the
* null constant.
*/
public static IsNullValue checkedNullValue() {
return instanceByFlagsList[0][CHECKED_NULL];
}
/**
* Get the instance representing values that are definitely not null.
*/
public static IsNullValue nonNullValue() {
return instanceByFlagsList[0][NN];
}
/**
* Get the instance representing a value known to be non-null because it was
* compared against null value, or because we saw the object creation.
*/
public static IsNullValue checkedNonNullValue() {
return instanceByFlagsList[0][CHECKED_NN];
}
/**
* Get the instance representing a value known to be non-null because a NPE
* would have occurred if it were null.
*/
public static IsNullValue noKaboomNonNullValue(@Nonnull Location ins) {
if (ins == null) {
throw new NullPointerException("ins cannot be null");
}
return new IsNullValue(NO_KABOOM_NN, ins);
}
/**
* Get the instance representing values that are definitely null on some
* simple (no branches) incoming path.
*/
public static IsNullValue nullOnSimplePathValue() {
return instanceByFlagsList[0][NSP];
}
/**
* Get instance representing a parameter marked as MightBeNull
*/
public static IsNullValue parameterMarkedAsMightBeNull(XMethodParameter mp) {
return instanceByFlagsList[PARAM >> FLAG_SHIFT][NSP];
}
/**
* Get instance representing a parameter marked as Nonnull
*/
public static IsNullValue parameterMarkedAsNonnull(XMethodParameter mp) {
return instanceByFlagsList[PARAM >> FLAG_SHIFT][NN];
}
/**
* Get non-reporting non-null value. This is what we use for unknown values.
*/
public static IsNullValue nonReportingNotNullValue() {
return instanceByFlagsList[0][NN_UNKNOWN];
}
/**
* Get null on complex path value. This is like null on simple path value,
* but there are at least two branches between the explicit null value and
* the current location. If the conditions are correlated, then the path on
* which the value is null may be infeasible.
*/
public static IsNullValue nullOnComplexPathValue() {
return instanceByFlagsList[0][NCP2];
}
/**
* Like "null on complex path" except that there are at least three
* branches between the explicit null value and the current location.
*/
public static IsNullValue nullOnComplexPathValue3() {
return instanceByFlagsList[0][NCP3];
}
/**
* Get null value resulting from comparison to explicit null.
*/
public static IsNullValue pathSensitiveNullValue() {
return instanceByFlagsList[0][CHECKED_NULL];
}
/**
* Get non-null value resulting from comparison to explicit null.
*/
public static IsNullValue pathSensitiveNonNullValue() {
return instanceByFlagsList[0][CHECKED_NN];
}
/**
* Merge two values.
*/
public static IsNullValue merge(IsNullValue a, IsNullValue b) {
if (a == b) {
return a;
}
if (a.equals(b)) {
return a;
}
int aKind = a.kind & 0xff;
int bKind = b.kind & 0xff;
int aFlags = a.getFlags();
int bFlags = b.getFlags();
int combinedFlags = aFlags & bFlags;
if (!(a.isNullOnSomePath() || a.isDefinitelyNull()) && b.isException()) {
combinedFlags |= EXCEPTION;
} else if (!(b.isNullOnSomePath() || b.isDefinitelyNull()) && a.isException()) {
combinedFlags |= EXCEPTION;
}
// Left hand value should be >=, since it is used
// as the first dimension of the matrix to index.
if (aKind < bKind) {
int tmp = aKind;
aKind = bKind;
bKind = tmp;
}
assert aKind >= bKind;
int result = mergeMatrix[aKind][bKind];
IsNullValue resultValue = (result == NO_KABOOM_NN) ? noKaboomNonNullValue(a.locationOfKaBoom)
: instanceByFlagsList[combinedFlags >> FLAG_SHIFT][result];
return resultValue;
}
/**
* Is this value definitely null?
*/
public boolean isDefinitelyNull() {
int baseKind = getBaseKind();
return baseKind == NULL || baseKind == CHECKED_NULL;
}
/**
* Is this value null on some path?
*/
public boolean isNullOnSomePath() {
int baseKind = getBaseKind();
if (NCP_EXTRA_BRANCH) {
// Note: NCP_EXTRA_BRANCH is an experimental feature
// to see how many false warnings we get when we allow
// two branches between an explicit null and a
// a dereference.
return baseKind == NSP || baseKind == NCP2;
} else {
return baseKind == NSP;
}
}
/**
* Is this value null on a complicated path?
*/
public boolean isNullOnComplicatedPath() {
int baseKind = getBaseKind();
return baseKind == NN_UNKNOWN || baseKind == NCP2 || baseKind == NCP3;
}
/**
* Is this value null on a complicated path?
*/
public boolean isNullOnComplicatedPath23() {
int baseKind = getBaseKind();
return baseKind == NCP2 || baseKind == NCP3;
}
/**
* Is this value null on a complicated path?
*/
public boolean isNullOnComplicatedPath2() {
int baseKind = getBaseKind();
return baseKind == NCP2;
}
/**
* Return true if this value is either definitely null, or might be null on
* a simple path.
*
* @return true if this value is either definitely null, or might be null on
* a simple path, false otherwise
*/
public boolean mightBeNull() {
return isDefinitelyNull() || isNullOnSomePath();
}
/**
* Is this value definitely not null?
*/
public boolean isDefinitelyNotNull() {
int baseKind = getBaseKind();
return baseKind == NN || baseKind == CHECKED_NN || baseKind == NO_KABOOM_NN;
}
@Override
public String toString() {
String pfx = "";
if (DEBUG_EXCEPTION) {
int flags = getFlags();
if (flags == 0) {
pfx = "_";
} else {
if ((flags & EXCEPTION) != 0) {
pfx += "e";
}
if ((flags & PARAM) != 0) {
pfx += "p";
}
if ((flags & RETURN_VAL) != 0) {
pfx += "r";
}
if ((flags & FIELD_VAL) != 0) {
pfx += "f";
}
}
}
if (DEBUG_KABOOM && locationOfKaBoom == null) {
pfx += "[?]";
}
switch (getBaseKind()) {
case NULL:
return pfx + "n" + ",";
case CHECKED_NULL:
return pfx + "w" + ",";
case NN:
return pfx + "N" + ",";
case CHECKED_NN:
return pfx + "W" + ",";
case NO_KABOOM_NN:
return pfx + "K" + ",";
case NSP:
return pfx + "s" + ",";
case NN_UNKNOWN:
return pfx + "-" + ",";
case NCP2:
return pfx + "/" + ",";
default:
throw new IllegalStateException("unknown kind of IsNullValue: " + kind);
}
}
public Location getLocationOfKaBoom() {
return locationOfKaBoom;
}
/**
* Control split: move given value down in the lattice if it is a
* conditionally-null value.
*
* @return another value (equal or further down in the lattice)
*/
public IsNullValue downgradeOnControlSplit() {
IsNullValue value = this;
if (NCP_EXTRA_BRANCH) {
// Experimental: track two distinct kinds of "null on complex path"
// values.
if (value.isNullOnSomePath()) {
value = nullOnComplexPathValue();
} else if (value.equals(nullOnComplexPathValue())) {
value = nullOnComplexPathValue3();
}
} else {
// Downgrade "null on simple path" values to
// "null on complex path".
if (value.isNullOnSomePath()) {
value = nullOnComplexPathValue();
}
}
return value;
}
}