inference of @Nullable results for simple methods

This commit is contained in:
Ilya Klyuchnikov
2014-09-01 17:51:24 +04:00
parent b7b5dce917
commit ec15cf3777
5 changed files with 87 additions and 22 deletions
@@ -274,16 +274,21 @@ public class BytecodeAnalysisConverter {
}
private static int mkDirectionKey(Direction dir) {
static int mkDirectionKey(Direction dir) {
if (dir == Out) {
return 0;
} else if (dir instanceof In) {
}
else if (dir == NullableOut) {
return 1;
}
else if (dir instanceof In) {
In in = (In)dir;
// nullity mask is 0/1
return 8 * in.paramId() + 1 + in.nullityMask;
} else {
return 2 + 8 * in.paramId() + in.nullityMask;
}
else {
InOut inOut = (InOut)dir;
return 8 * inOut.paramId() + 3 + inOut.valueId();
return 4 + 8 * inOut.paramId() + inOut.valueId();
}
}
@@ -292,7 +297,11 @@ public class BytecodeAnalysisConverter {
if (directionKey == 0) {
return Out;
}
else if (directionKey == 1) {
return NullableOut;
}
else {
directionKey--;
int paramId = directionKey / 8;
int subDirection = directionKey % 8;
if (subDirection <= 2) {
@@ -181,7 +181,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
final boolean stable,
boolean jsr) throws AnalyzerException {
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 1);
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
boolean maybeLeakingParameter = isInterestingResult;
for (Type argType : argumentTypes) {
if (ASMUtils.isReferenceType(argType) || (isReferenceResult && ASMUtils.isBooleanType(argType))) {
@@ -313,11 +313,12 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
boolean isReferenceResult,
boolean isBooleanResult,
boolean stable) throws AnalyzerException {
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 1);
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
CombinedAnalysis analyzer = new CombinedAnalysis(method, graph);
analyzer.analyze();
if (isReferenceResult) {
result.add(analyzer.outContractEquation(stable));
result.add(analyzer.nullableResultEquation(stable));
}
for (int i = 0; i < argumentTypes.length; i++) {
Type argType = argumentTypes[i];
@@ -343,9 +344,10 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
boolean isReferenceResult,
boolean isInterestingResult,
boolean stable) {
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
if (isReferenceResult) {
result.add(new Equation<Key, Value>(new Key(method, Out, stable), FINAL_TOP));
result.add(new Equation<Key, Value>(new Key(method, NullableOut, stable), FINAL_BOT));
}
for (int i = 0; i < argumentTypes.length; i++) {
Type argType = argumentTypes[i];
@@ -80,17 +80,19 @@ interface CombinedData {
}
final class TrackableCallValue extends BasicValue implements Trackable {
final Method method;
final boolean stableCall;
final List<? extends BasicValue> args;
private final int originInsnIndex;
final Method method;
final List<? extends BasicValue> args;
final boolean stableCall;
final boolean thisCall;
TrackableCallValue(int originInsnIndex, Type tp, Method method, boolean stableCall, List<? extends BasicValue> args) {
TrackableCallValue(int originInsnIndex, Type tp, Method method, List<? extends BasicValue> args, boolean stableCall, boolean thisCall) {
super(tp);
this.originInsnIndex = originInsnIndex;
this.method = method;
this.stableCall = stableCall;
this.args = args;
this.stableCall = stableCall;
this.thisCall = thisCall;
}
@Override
@@ -313,6 +315,28 @@ final class CombinedAnalysis {
return new Equation<Key, Value>(key, result);
}
final Equation<Key, Value> nullableResultEquation(boolean stable) {
final Key key = new Key(method, NullableOut, stable);
final Result<Key, Value> result;
if (exception ||
returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) {
result = new Final<Key, Value>(Value.Bot);
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall);
Set<Key> keys = new SingletonSet<Key>(callKey);
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Null, keys)));
}
else if (returnValue instanceof TrackableNullValue) {
result = new Final<Key, Value>(Value.Null);
}
else {
result = new Final<Key, Value>(Value.Bot);
}
return new Equation<Key, Value>(key, result);
}
final Frame<BasicValue> createStartFrame() {
Frame<BasicValue> frame = new Frame<BasicValue>(methodNode.maxLocals, methodNode.maxStack);
Type returnType = Type.getReturnType(methodNode.desc);
@@ -545,10 +569,12 @@ final class CombinedInterpreter extends BasicInterpreter {
}
}
}
BasicValue receiver = null;
if (shift == 1) {
values.remove(0);
receiver = values.remove(0);
}
return new TrackableCallValue(origin, retType, method, stable, values);
boolean thisCall = (opCode == INVOKEINTERFACE || opCode == INVOKEVIRTUAL) && receiver == ThisValue;
return new TrackableCallValue(origin, retType, method, values, stable, thisCall);
case MULTIANEWARRAY:
return new NotNullValue(super.naryOperation(insn, values).getType());
default:
@@ -103,6 +103,7 @@ public class ProjectBytecodeAnalysis {
ArrayList<HKey> allKeys = contractKeys((PsiMethod)listOwner, primaryKey);
MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys);
boolean notNull = methodAnnotations.notNulls.contains(primaryKey);
boolean nullable = methodAnnotations.nullables.contains(primaryKey);
String contractValue = methodAnnotations.contracts.get(primaryKey);
if (notNull && contractValue != null) {
return new PsiAnnotation[]{
@@ -110,11 +111,22 @@ public class ProjectBytecodeAnalysis {
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
};
}
if (nullable && contractValue != null) {
return new PsiAnnotation[]{
getNullableAnnotation(),
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
};
}
else if (notNull) {
return new PsiAnnotation[]{
getNotNullAnnotation()
};
}
else if (nullable) {
return new PsiAnnotation[]{
getNullableAnnotation()
};
}
else if (contractValue != null) {
return new PsiAnnotation[]{
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
@@ -198,14 +210,14 @@ public class ProjectBytecodeAnalysis {
private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey)
throws EquationsLimitException {
final Solver notNullSolver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top));
final Solver notNullSolver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top), Value.Top);
collectEquations(Collections.singletonList(notNullKey), notNullSolver);
HashMap<HKey, Value> notNullSolutions = notNullSolver.solve();
boolean notNull =
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
final Solver nullableSolver = new Solver(new ELattice<Value>(Value.Null, Value.Top));
final Solver nullableSolver = new Solver(new ELattice<Value>(Value.Null, Value.Top), Value.Top);
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true);
collectEquations(Collections.singletonList(nullableKey), nullableSolver);
HashMap<HKey, Value> nullableSolutions = nullableSolver.solve();
@@ -217,11 +229,23 @@ public class ProjectBytecodeAnalysis {
private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull HKey key, ArrayList<HKey> allKeys)
throws EquationsLimitException {
MethodAnnotations result = new MethodAnnotations();
final Solver solver = new Solver(new ELattice<Value>(Value.Bot, Value.Top));
collectEquations(allKeys, solver);
HashMap<HKey, Value> solutions = solver.solve();
final Solver outSolver = new Solver(new ELattice<Value>(Value.Bot, Value.Top), Value.Top);
collectEquations(allKeys, outSolver);
HashMap<HKey, Value> solutions = outSolver.solve();
int arity = owner.getParameterList().getParameters().length;
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
final Solver nullableMethodSolver = new Solver(new ELattice<Value>(Value.Bot, Value.Null), Value.Bot);
HKey nullableKey = key.updateDirection(BytecodeAnalysisConverter.mkDirectionKey(NullableOut));
collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver);
HashMap<HKey, Value> nullableSolutions = nullableMethodSolver.solve();
if (nullableSolutions.get(nullableKey) == Value.Null) {
result.nullables.add(key);
}
return result;
}
@@ -288,6 +312,8 @@ public class ProjectBytecodeAnalysis {
class MethodAnnotations {
// @NotNull keys
final HashSet<HKey> notNulls = new HashSet<HKey>();
// @Nullable keys
final HashSet<HKey> nullables = new HashSet<HKey>();
// @Contracts
final HashMap<HKey, String> contracts = new HashMap<HKey, String>();
}
@@ -265,9 +265,11 @@ final class Solver {
private final HResultUtil resultUtil;
private final HashMap<CoreHKey, HEquation> equations = new HashMap<CoreHKey, HEquation>();
private final Value unstableValue;
Solver(ELattice<Value> lattice) {
Solver(ELattice<Value> lattice, Value unstableValue) {
this.lattice = lattice;
this.unstableValue = unstableValue;
resultUtil = new HResultUtil(lattice);
}
@@ -324,7 +326,7 @@ final class Solver {
Value value = solved.get(id);
HKey[] pIds = id.stable ? new HKey[]{id, id.negate()} : new HKey[]{id.negate(), id};
Value[] pVals = id.stable ? new Value[]{value, value} : new Value[]{value, lattice.top};
Value[] pVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue};
for (int i = 0; i < pIds.length; i++) {
HKey pId = pIds[i];