From 8eb3633fb3a8746a1a32d76c9e62275220490e4b Mon Sep 17 00:00:00 2001 From: Ilya Klyuchnikov Date: Wed, 20 Aug 2014 15:09:21 +0400 Subject: [PATCH] inference of @Nullable annotations for parameters --- .../BytecodeAnalysisConverter.java | 29 +--- .../bytecodeAnalysis/ClassDataIndexer.java | 34 ++++- .../bytecodeAnalysis/Combined.java | 101 +++++++++++-- .../bytecodeAnalysis/HData.java | 4 + .../bytecodeAnalysis/Parameters.java | 15 +- .../ProjectBytecodeAnalysis.java | 141 +++++++++++------- .../asm/LeakingParameters.java | 56 +++++-- 7 files changed, 269 insertions(+), 111 deletions(-) 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 c1c9a91b25d0..84a5851e847d 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 @@ -329,14 +329,14 @@ public class BytecodeAnalysisConverter { * Given `solution` of all dependencies of a method with the `methodKey`, converts this solution into annotations. * * @param solution solution of equations - * @param annotations annotations to which corresponding solutions should be added + * @param methodAnnotations annotations to which corresponding solutions should be added * @param methodKey a primary key of a method being analyzed * @param arity arity of this method (hint for constructing @Contract annotations) */ - public static void addMethodAnnotations(@NotNull HashMap solution, @NotNull Annotations annotations, @NotNull HKey methodKey, int arity) { + public static void addMethodAnnotations(@NotNull HashMap solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) { List clauses = new ArrayList(); - HashSet notNulls = annotations.notNulls; - HashMap contracts = annotations.contracts; + HashSet notNulls = methodAnnotations.notNulls; + HashMap contracts = methodAnnotations.contracts; for (Map.Entry entry : solution.entrySet()) { HKey key = entry.getKey().mkStable(); Value value = entry.getValue(); @@ -364,27 +364,6 @@ public class BytecodeAnalysisConverter { } } - /** - * Converts solutions for equations over parameters into annotations. - * - * @param solution - * @param annotations - */ - public static void addParameterAnnotations(@NotNull HashMap solution, @NotNull Annotations annotations) { - HashSet notNulls = annotations.notNulls; - for (Map.Entry entry : solution.entrySet()) { - HKey key = entry.getKey().mkStable(); - Value value = entry.getValue(); - if (value == Value.Top || value == Value.Bot) { - continue; - } - Direction direction = extractDirection(key.dirKey); - if (value == Value.NotNull && (direction instanceof In || direction instanceof Out)) { - notNulls.add(key); - } - } - } - private static String contractValueString(@NotNull Value v) { switch (v) { case False: return "false"; 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 9af4ec3ac534..316307896467 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 @@ -42,6 +42,7 @@ public class ClassDataIndexer implements DataIndexer FINAL_TOP = new Final(Value.Top); public static final Final FINAL_BOT = new Final(Value.Bot); public static final Final FINAL_NOT_NULL = new Final(Value.NotNull); + public static final Final FINAL_NULL = new Final(Value.Null); private static final List> EMPTY_EQUATIONS = Collections.EMPTY_LIST; @NotNull @@ -129,22 +130,21 @@ public class ClassDataIndexer implements DataIndexer 0) { final DFSTree dfs = DFSTree.build(graph.transitions, graph.edgeCount); - boolean complex = !dfs.back.isEmpty(); - if (!complex) { + boolean branching = !dfs.back.isEmpty(); + if (!branching) { for (int[] transition : graph.transitions) { if (transition != null && transition.length > 1) { - complex = true; + branching = true; break; } } } - if (complex) { + if (branching) { RichControlFlow richControlFlow = new RichControlFlow(graph, dfs); if (richControlFlow.reducible()) { return Pair.create(primaryKey, @@ -180,7 +180,7 @@ public class ClassDataIndexer implements DataIndexer> result = new ArrayList>(argumentTypes.length * 3 + 1); + List> result = new ArrayList>(argumentTypes.length * 4 + 1); boolean maybeLeakingParameter = isInterestingResult; for (Type argType : argumentTypes) { if (ASMUtils.isReferenceType(argType) || (isReferenceResult && ASMUtils.isBooleanType(argType))) { @@ -193,6 +193,8 @@ public class ClassDataIndexer implements DataIndexer origins = new NullableLazyValue() { @Override @@ -231,8 +233,11 @@ public class ClassDataIndexer implements DataIndexer notNullParamEquation = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable).analyze(); + NonNullInAnalysis notNullInAnalysis = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable); + Equation notNullParamEquation = notNullInAnalysis.analyze(); + possibleNPE = notNullInAnalysis.possibleNPE; notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL); result.add(notNullParamEquation); } @@ -240,6 +245,17 @@ public class ClassDataIndexer implements DataIndexer(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP)); } + if (leakingNullableParameters[i]) { + if (notNullParam || possibleNPE) { + result.add(new Equation(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP)); + } + else { + result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE), stable).analyze()); + } + } + else { + result.add(new Equation(new Key(method, new In(i, In.NULLABLE), stable), FINAL_NULL)); + } } if (isReferenceArg && isInterestingResult) { if (leakingParameters[i]) { @@ -296,7 +312,7 @@ public class ClassDataIndexer implements DataIndexer> result = new ArrayList>(argumentTypes.length * 3 + 1); + List> result = new ArrayList>(argumentTypes.length * 4 + 1); CombinedSingleAnalysis analyzer = new CombinedSingleAnalysis(method, graph); analyzer.analyze(); if (isReferenceResult) { @@ -307,6 +323,7 @@ public class ClassDataIndexer implements DataIndexer(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP)); + result.add(new Equation(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP)); } if (isReferenceArg && isInterestingResult) { result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP)); 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 d20bbcbd3cac..645c00260af1 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 @@ -28,12 +28,48 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter; import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; import org.jetbrains.org.objectweb.asm.tree.analysis.Frame; +import java.util.Collections; import java.util.List; import java.util.Set; import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*; import static org.jetbrains.org.objectweb.asm.Opcodes.*; +final class ParamKey { + final Method method; + final int i; + final boolean stable; + + + ParamKey(Method method, int i, boolean stable) { + this.method = method; + this.i = i; + this.stable = stable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ParamKey paramKey = (ParamKey)o; + + if (i != paramKey.i) return false; + if (stable != paramKey.stable) return false; + if (!method.equals(paramKey.method)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = method.hashCode(); + result = 31 * result + i; + result = 31 * result + (stable ? 1 : 0); + return result; + } +} + final class CombinedCall extends BasicValue { final Method method; final boolean stableCall; @@ -112,12 +148,38 @@ final class CombinedSingleAnalysis { result = new Final(Value.NotNull); } else { - Set calls = interpreter.callDerefs[i]; + Set calls = interpreter.callDerefs[i]; if (calls == null || calls.isEmpty()) { result = new Final(Value.Top); } else { - result = new Pending(new SingletonSet>(new Product(Value.Top, calls))); + Set keys = new HashSet(); + for (ParamKey pk: calls) { + keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL), pk.stable)); + } + result = new Pending(new SingletonSet>(new Product(Value.Top, keys))); + } + } + return new Equation(key, result); + } + + final Equation nullableParamEquation(int i, boolean stable) { + final Key key = new Key(method, new In(i, In.NULLABLE), stable); + final Result result; + if (interpreter.dereferenced[i] || interpreter.notNullable[i] || returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) { + result = new Final(Value.Top); + } + else { + Set calls = interpreter.callDerefs[i]; + if (calls == null || calls.isEmpty()) { + result = new Final(Value.Null); + } + else { + Set> sum = new HashSet>(); + for (ParamKey pk: calls) { + sum.add(new Product(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE), pk.stable)))); + } + result = new Pending(sum); } } return new Equation(key, result); @@ -228,10 +290,12 @@ final class CombinedSingleAnalysis { final class CombinedInterpreter extends BasicInterpreter { final boolean[] dereferenced; - final Set[] callDerefs; + final boolean[] notNullable; + final Set[] callDerefs; CombinedInterpreter(int arity) { dereferenced = new boolean[arity]; + notNullable = new boolean[arity]; callDerefs = new Set[arity]; } @@ -303,10 +367,17 @@ final class CombinedInterpreter extends BasicInterpreter { case BALOAD: case CALOAD: case SALOAD: + if (value1 instanceof NParamValue) { + dereferenced[((NParamValue)value1).n] = true; + } + break; case PUTFIELD: if (value1 instanceof NParamValue) { dereferenced[((NParamValue)value1).n] = true; } + if (value2 instanceof NParamValue) { + notNullable[((NParamValue)value2).n] = true; + } break; default: } @@ -321,13 +392,21 @@ final class CombinedInterpreter extends BasicInterpreter { case LASTORE: case FASTORE: case DASTORE: - case AASTORE: case BASTORE: case CASTORE: case SASTORE: if (value1 instanceof NParamValue) { dereferenced[((NParamValue)value1).n] = true; } + break; + case AASTORE: + if (value1 instanceof NParamValue) { + dereferenced[((NParamValue)value1).n] = true; + } + if (value3 instanceof NParamValue) { + notNullable[((NParamValue)value3).n] = true; + } + break; default: } return super.ternaryOperation(insn, value1, value2, value3); @@ -347,7 +426,6 @@ final class CombinedInterpreter extends BasicInterpreter { } } - switch (opCode) { case INVOKESTATIC: case INVOKESPECIAL: @@ -360,14 +438,17 @@ final class CombinedInterpreter extends BasicInterpreter { for (int i = shift; i < values.size(); i++) { if (values.get(i) instanceof NParamValue) { - if (opCode != INVOKEINTERFACE) { - int n = ((NParamValue)values.get(i)).n; - Set npKeys = callDerefs[n]; + int n = ((NParamValue)values.get(i)).n; + if (opCode == INVOKEINTERFACE) { + notNullable[n] = true; + } + else { + Set npKeys = callDerefs[n]; if (npKeys == null) { - npKeys = new HashSet(); + npKeys = new HashSet(); callDerefs[n] = npKeys; } - npKeys.add(new Key(method, new In(i - shift, In.NOT_NULL), stable)); + npKeys.add(new ParamKey(method, i - shift, stable)); } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java index 047613847186..7c938347ccb9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java @@ -63,6 +63,10 @@ final class HKey { return stable ? this : new HKey(key, dirKey, true); } + HKey mkUnstable() { + return stable ? new HKey(key, dirKey, false) : this; + } + public HKey mkBase() { return dirKey == 0 ? this : new HKey(key, 0, stable); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java index 946070668753..a7c25782d48b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java @@ -116,7 +116,7 @@ abstract class PResults { if (NPE == r2) return NPE; ConditionalNPE cnpe1 = (ConditionalNPE) r1; ConditionalNPE cnpe2 = (ConditionalNPE) r2; - return new ConditionalNPE(meet(cnpe1.sop, cnpe2.sop)); + return new ConditionalNPE(join(cnpe1.sop, cnpe2.sop)); } static PResult join(PResult r1, PResult r2) throws AnalyzerException { @@ -184,6 +184,9 @@ class NonNullInAnalysis extends Analysis { private final NotNullInterpreter interpreter = new NotNullInterpreter(); private PResult[] results; + // Flag saying that at some branch NPE was found. Used later as an evidence that this param is *NOT* @Nullable (optimization). + boolean possibleNPE; + protected NonNullInAnalysis(RichControlFlow richControlFlow, Direction direction, boolean stable) { super(richControlFlow, direction, stable); results = ourResults.get(); @@ -301,6 +304,7 @@ class NonNullInAnalysis extends Analysis { if (subResult == NPE) { results[stateIndex] = NPE; + possibleNPE = true; addComputed(insnIndex, state); return; } @@ -326,6 +330,7 @@ class NonNullInAnalysis extends Analysis { if (opcode == ATHROW) { if (taken) { results[stateIndex] = NPE; + possibleNPE = true; } else { results[stateIndex] = Identity; } @@ -481,13 +486,9 @@ class NullableInAnalysis extends Analysis { } } } - if (baseState != null) { - addComputed(insnIndex, state); - } else { - // the main call + if (baseState == null) { processState(state); } - } } if (earlyResult != null) { @@ -505,6 +506,8 @@ class NullableInAnalysis extends Analysis { Frame frame = conf.frame; AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex); List nextHistory = dfsTree.loopEnters[insnIndex] ? append(history, conf) : history; + + addComputed(insnIndex, state); execute(frame, insnNode); if (subResult == NPE || top) { 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 03e2d3afff91..aa44951961ad 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 @@ -37,10 +37,7 @@ import org.jetbrains.annotations.Nullable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +import java.util.*; /** * @author lambdamix @@ -64,7 +61,7 @@ public class ProjectBytecodeAnalysis { if (!(listOwner instanceof PsiCompiledElement)) { return null; } - if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { + if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(AnnotationUtil.NULLABLE) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { PsiAnnotation[] annotations = findInferredAnnotations(listOwner); for (PsiAnnotation annotation : annotations) { if (annotationFQN.equals(annotation.getQualifiedName())) { @@ -96,34 +93,45 @@ public class ProjectBytecodeAnalysis { private PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) { try { MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); - HKey ownerKey = getKey(listOwner, md); - if (ownerKey == null) { + HKey primaryKey = getKey(listOwner, md); + if (primaryKey == null) { return PsiAnnotation.EMPTY_ARRAY; } - ArrayList allKeys = contractKeys(listOwner, ownerKey); - Annotations annotations = loadAnnotations(listOwner, ownerKey, allKeys); - boolean notNull = annotations.notNulls.contains(ownerKey); - String contractValue = annotations.contracts.get(ownerKey); - - if (notNull && contractValue != null) { - return new PsiAnnotation[]{ - getNotNullAnnotation(), - createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")") - }; - } - else if (notNull) { - return new PsiAnnotation[]{ - getNotNullAnnotation() - }; - } - else if (contractValue != null) { - return new PsiAnnotation[]{ - createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")") - }; - } - else { - return PsiAnnotation.EMPTY_ARRAY; + if (listOwner instanceof PsiMethod) { + ArrayList allKeys = contractKeys((PsiMethod)listOwner, primaryKey); + MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys); + boolean notNull = methodAnnotations.notNulls.contains(primaryKey); + String contractValue = methodAnnotations.contracts.get(primaryKey); + if (notNull && contractValue != null) { + return new PsiAnnotation[]{ + getNotNullAnnotation(), + createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")") + }; + } + else if (notNull) { + return new PsiAnnotation[]{ + getNotNullAnnotation() + }; + } + else if (contractValue != null) { + return new PsiAnnotation[]{ + createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")") + }; + } + } else if (listOwner instanceof PsiParameter) { + ParameterAnnotations parameterAnnotations = loadParameterAnnotations(primaryKey); + if (parameterAnnotations.notNull) { + return new PsiAnnotation[]{ + getNotNullAnnotation() + }; + } + else if (parameterAnnotations.nullable) { + return new PsiAnnotation[]{ + getNullableAnnotation() + }; + } } + return PsiAnnotation.EMPTY_ARRAY; } catch (EquationsLimitException e) { String externalName = PsiFormatUtil.getExternalName(listOwner, false, Integer.MAX_VALUE); @@ -146,6 +154,16 @@ public class ProjectBytecodeAnalysis { }); } + private PsiAnnotation getNullableAnnotation() { + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, new CachedValueProvider() { + @Nullable + @Override + public Result compute() { + return Result.create(createAnnotationFromText("@" + AnnotationUtil.NULLABLE), ModificationTracker.NEVER_CHANGED); + } + }); + } + public PsiAnnotation createContractAnnotation(String contractValue) { return createAnnotationFromText("@org.jetbrains.annotations.Contract(" + contractValue + ")"); } @@ -169,36 +187,43 @@ public class ProjectBytecodeAnalysis { return null; } - public static ArrayList contractKeys(@NotNull PsiModifierListOwner owner, HKey primaryKey) { - if (owner instanceof PsiMethod) { - ArrayList result = BytecodeAnalysisConverter.mkInOutKeys((PsiMethod)owner, primaryKey); - result.add(primaryKey); - return result; - } - ArrayList result = new ArrayList(1); + public static ArrayList contractKeys(@NotNull PsiMethod owner, HKey primaryKey) { + ArrayList result = BytecodeAnalysisConverter.mkInOutKeys(owner, primaryKey); result.add(primaryKey); return result; } - private Annotations loadAnnotations(@NotNull PsiModifierListOwner owner, @NotNull HKey key, ArrayList allKeys) + private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey) throws EquationsLimitException { - Annotations result = new Annotations(); - if (owner instanceof PsiParameter) { - final Solver solver = new Solver(new ELattice(Value.NotNull, Value.Top)); - collectEquations(allKeys, solver); - HashMap solutions = solver.solve(); - BytecodeAnalysisConverter.addParameterAnnotations(solutions, result); - } else if (owner instanceof PsiMethod) { - final Solver solver = new Solver(new ELattice(Value.Bot, Value.Top)); - collectEquations(allKeys, solver); - HashMap solutions = solver.solve(); - int arity = ((PsiMethod)owner).getParameterList().getParameters().length; - BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity); - } + + final Solver notNullSolver = new Solver(new ELattice(Value.NotNull, 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 HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true); + collectEquations(Collections.singletonList(nullableKey), nullableSolver); + HashMap nullableSolutions = nullableSolver.solve(); + boolean nullable = + (Value.Null == nullableSolutions.get(nullableKey)) || (Value.Null == nullableSolutions.get(nullableKey.mkUnstable())); + return new ParameterAnnotations(notNull, nullable); + } + + 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(); + int arity = owner.getParameterList().getParameters().length; + BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity); return result; } - private void collectEquations(ArrayList keys, Solver solver) throws EquationsLimitException { + private void collectEquations(List keys, Solver solver) throws EquationsLimitException { GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject); HashSet queued = new HashSet(); Stack queue = new Stack(); @@ -258,11 +283,21 @@ public class ProjectBytecodeAnalysis { } } -class Annotations { +class MethodAnnotations { // @NotNull keys final HashSet notNulls = new HashSet(); // @Contracts final HashMap contracts = new HashMap(); } +class ParameterAnnotations { + final boolean notNull; + final boolean nullable; + + ParameterAnnotations(boolean notNull, boolean nullable) { + this.notNull = notNull; + this.nullable = nullable; + } +} + class EquationsLimitException extends Exception {} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java index 9fa5425e6999..1a7ab15722f9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java @@ -31,10 +31,12 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*; public class LeakingParameters { public final Frame[] frames; public final boolean[] parameters; + public final boolean[] nullableParameters; - public LeakingParameters(Frame[] frames, boolean[] parameters) { + public LeakingParameters(Frame[] frames, boolean[] parameters, boolean[] nullableParameters) { this.frames = frames; this.parameters = parameters; + this.nullableParameters = nullableParameters; } public static LeakingParameters build(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { @@ -57,7 +59,12 @@ public class LeakingParameters { } } } - return new LeakingParameters((Frame[])(Frame[])frames, collector.leaking); + boolean[] notNullParameters = collector.leaking; + boolean[] nullableParameters = collector.nullableLeaking; + for (int i = 0; i < nullableParameters.length; i++) { + nullableParameters[i] |= notNullParameters[i]; + } + return new LeakingParameters((Frame[])(Frame[])frames, notNullParameters, nullableParameters); } public static LeakingParameters buildFast(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { @@ -66,11 +73,14 @@ public class LeakingParameters { new Analyzer(parametersUsage).analyze(className, methodNode) : new LiteAnalyzer(parametersUsage).analyze(className, methodNode); int leakingMask = parametersUsage.leaking; - boolean[] result = new boolean[parametersUsage.arity]; - for (int i = 0; i < result.length; i++) { - result[i] = (leakingMask & (1 << i)) != 0; + int nullableLeakingMask = parametersUsage.nullableLeaking; + boolean[] notNullParameters = new boolean[parametersUsage.arity]; + boolean[] nullableParameters = new boolean[parametersUsage.arity]; + for (int i = 0; i < notNullParameters.length; i++) { + notNullParameters[i] = (leakingMask & (1 << i)) != 0; + nullableParameters[i] = ((leakingMask | nullableLeakingMask) & (1 << i)) != 0; } - return new LeakingParameters((Frame[])frames, result); + return new LeakingParameters((Frame[])frames, notNullParameters, nullableParameters); } } @@ -288,6 +298,7 @@ class IParametersUsage extends Interpreter { static final IParamsValue val1 = new IParamsValue(0, 1); static final IParamsValue val2 = new IParamsValue(0, 2); int leaking = 0; + int nullableLeaking = 0; int called = -1; final int rangeStart; final int rangeEnd; @@ -415,8 +426,12 @@ class IParametersUsage extends Interpreter { case BALOAD: case CALOAD: case SALOAD: + leaking |= value1.params; + size = 1; + break; case PUTFIELD: leaking |= value1.params; + nullableLeaking |= value2.params; size = 1; break; default: @@ -432,12 +447,15 @@ class IParametersUsage extends Interpreter { case LASTORE: case FASTORE: case DASTORE: - case AASTORE: case BASTORE: case CASTORE: case SASTORE: leaking |= value1.params; break; + case AASTORE: + leaking |= value1.params; + nullableLeaking |= value3.params; + break; default: } return val1; @@ -479,9 +497,11 @@ class IParametersUsage extends Interpreter { class LeakingParametersCollector extends ParametersUsage { final boolean[] leaking; + final boolean[] nullableLeaking; LeakingParametersCollector(MethodNode methodNode) { super(methodNode); leaking = new boolean[arity]; + nullableLeaking = new boolean[arity]; } @Override @@ -518,12 +538,21 @@ class LeakingParametersCollector extends ParametersUsage { case BALOAD: case CALOAD: case SALOAD: - case PUTFIELD: boolean[] params = value1.params; for (int i = 0; i < arity; i++) { leaking[i] |= params[i]; } break; + case PUTFIELD: + params = value1.params; + for (int i = 0; i < arity; i++) { + leaking[i] |= params[i]; + } + params = value2.params; + for (int i = 0; i < arity; i++) { + nullableLeaking[i] |= params[i]; + } + break; default: } return super.binaryOperation(insn, value1, value2); @@ -536,7 +565,6 @@ class LeakingParametersCollector extends ParametersUsage { case LASTORE: case FASTORE: case DASTORE: - case AASTORE: case BASTORE: case CASTORE: case SASTORE: @@ -545,6 +573,16 @@ class LeakingParametersCollector extends ParametersUsage { leaking[i] |= params[i]; } break; + case AASTORE: + params = value1.params; + for (int i = 0; i < arity; i++) { + leaking[i] |= params[i]; + } + params = value3.params; + for (int i = 0; i < arity; i++) { + nullableLeaking[i] |= params[i]; + } + break; default: } return super.ternaryOperation(insn, value1, value2, value3);