diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java index 068ee5c06acf..91f1b685faa8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java index a3c813e9d07c..cffaf4da8d19 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java @@ -181,7 +181,7 @@ public class ClassDataIndexer implements DataIndexer> result = new ArrayList>(argumentTypes.length * 4 + 1); + List> result = new ArrayList>(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> result = new ArrayList>(argumentTypes.length * 4 + 1); + List> result = new ArrayList>(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> result = new ArrayList>(argumentTypes.length * 3 + 1); + List> result = new ArrayList>(argumentTypes.length * 4 + 2); if (isReferenceResult) { result.add(new Equation(new Key(method, Out, stable), FINAL_TOP)); + result.add(new Equation(new Key(method, NullableOut, stable), FINAL_BOT)); } for (int i = 0; i < argumentTypes.length; i++) { Type argType = argumentTypes[i]; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java index c02d12831c16..dd10492785e7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java @@ -80,17 +80,19 @@ interface CombinedData { } final class TrackableCallValue extends BasicValue implements Trackable { - final Method method; - final boolean stableCall; - final List args; private final int originInsnIndex; + final Method method; + final List args; + final boolean stableCall; + final boolean thisCall; - TrackableCallValue(int originInsnIndex, Type tp, Method method, boolean stableCall, List args) { + TrackableCallValue(int originInsnIndex, Type tp, Method method, List 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, result); } + final Equation nullableResultEquation(boolean stable) { + final Key key = new Key(method, NullableOut, stable); + final Result result; + if (exception || + returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) { + result = new Final(Value.Bot); + } + else if (returnValue instanceof TrackableCallValue) { + TrackableCallValue call = (TrackableCallValue)returnValue; + Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall); + Set keys = new SingletonSet(callKey); + result = new Pending(new SingletonSet>(new Product(Value.Null, keys))); + } + else if (returnValue instanceof TrackableNullValue) { + result = new Final(Value.Null); + } + else { + result = new Final(Value.Bot); + } + return new Equation(key, result); + } + final Frame createStartFrame() { Frame frame = new Frame(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: diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java index cafc57d0b373..0b6f17a5495b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java @@ -103,6 +103,7 @@ public class ProjectBytecodeAnalysis { ArrayList 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.NotNull, Value.Top)); + final Solver notNullSolver = new Solver(new ELattice(Value.NotNull, Value.Top), Value.Top); collectEquations(Collections.singletonList(notNullKey), notNullSolver); HashMap notNullSolutions = notNullSolver.solve(); boolean notNull = (Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable())); - final Solver nullableSolver = new Solver(new ELattice(Value.Null, Value.Top)); + final Solver nullableSolver = new Solver(new ELattice(Value.Null, Value.Top), Value.Top); final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true); collectEquations(Collections.singletonList(nullableKey), nullableSolver); HashMap nullableSolutions = nullableSolver.solve(); @@ -217,11 +229,23 @@ public class ProjectBytecodeAnalysis { private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull HKey key, ArrayList allKeys) throws EquationsLimitException { MethodAnnotations result = new MethodAnnotations(); - final Solver solver = new Solver(new ELattice(Value.Bot, Value.Top)); - collectEquations(allKeys, solver); - HashMap solutions = solver.solve(); + + final Solver outSolver = new Solver(new ELattice(Value.Bot, Value.Top), Value.Top); + collectEquations(allKeys, outSolver); + HashMap solutions = outSolver.solve(); int arity = owner.getParameterList().getParameters().length; BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity); + + + final Solver nullableMethodSolver = new Solver(new ELattice(Value.Bot, Value.Null), Value.Bot); + HKey nullableKey = key.updateDirection(BytecodeAnalysisConverter.mkDirectionKey(NullableOut)); + collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver); + + HashMap 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 notNulls = new HashSet(); + // @Nullable keys + final HashSet nullables = new HashSet(); // @Contracts final HashMap contracts = new HashMap(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java index 21e749e12b29..1bc2c3a020e9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java @@ -265,9 +265,11 @@ final class Solver { private final HResultUtil resultUtil; private final HashMap equations = new HashMap(); + private final Value unstableValue; - Solver(ELattice lattice) { + Solver(ELattice 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];