summaryrefslogtreecommitdiff
path: root/java/java-analysis-impl/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/java-analysis-impl/src')
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java10
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java2
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/RedundantLambdaCodeBlockInspection.java31
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java8
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java27
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java2
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java129
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java335
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java7
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java200
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java423
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java3
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java84
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java6
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/AnalyzerExt.java446
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java46
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/InterpreterExt.java32
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java9
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzerExt.java222
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java6
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/Subroutine.java74
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java41
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java4
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java4
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java4
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/equalsAndHashcode/EqualsAndHashcodeBase.java (renamed from java/java-analysis-impl/src/com/intellij/codeInspection/equalsAndHashcode/EqualsAndHashcode.java)17
26 files changed, 1781 insertions, 391 deletions
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java
index 813ff25caee2..c9230625eecd 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java
@@ -20,6 +20,7 @@ import com.intellij.codeInspection.dataFlow.ContractInference;
import com.intellij.codeInspection.dataFlow.MethodContract;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.util.PsiUtil;
@@ -32,9 +33,17 @@ import java.util.List;
import static com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT;
public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
+
+ @NotNull
+ private static PsiModifierListOwner preferCompiledElement(@NotNull PsiModifierListOwner element) {
+ PsiElement original = element.getOriginalElement();
+ return original instanceof PsiModifierListOwner ? (PsiModifierListOwner)original : element;
+ }
+
@Nullable
@Override
public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) {
+ listOwner = preferCompiledElement(listOwner);
PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(listOwner.getProject()).findInferredAnnotation(listOwner, annotationFQN);
if (fromBytecode != null) {
return fromBytecode;
@@ -57,6 +66,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
@NotNull
@Override
public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) {
+ listOwner = preferCompiledElement(listOwner);
List<PsiAnnotation> result = ContainerUtil.newArrayList();
PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(listOwner.getProject()).findInferredAnnotations(listOwner);
for (PsiAnnotation annotation : fromBytecode) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java
index 118c98efc9d2..83af5606d8c8 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java
@@ -1026,7 +1026,7 @@ public class HighlightMethodUtil {
description = JavaErrorMessages.message("extension.method.should.have.a.body");
additionalFixes.add(QUICK_FIX_FACTORY.createAddMethodBodyFix(method));
}
- else if (isInterface && isStatic) {
+ else if (isInterface && isStatic && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
description = "Static methods in interfaces should have a body";
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantLambdaCodeBlockInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantLambdaCodeBlockInspection.java
index 0dfa5df979bc..d65abf63dda3 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantLambdaCodeBlockInspection.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantLambdaCodeBlockInspection.java
@@ -19,12 +19,23 @@ import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
+import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
+import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
+import com.intellij.psi.infos.CandidateInfo;
+import com.intellij.psi.infos.MethodCandidateInfo;
+import com.intellij.psi.scope.conflictResolvers.JavaMethodsConflictResolver;
import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* User: anna
*/
@@ -67,6 +78,26 @@ public class RedundantLambdaCodeBlockInspection extends BaseJavaBatchLocalInspec
if (body instanceof PsiCodeBlock) {
PsiExpression psiExpression = getExpression((PsiCodeBlock)body);
if (psiExpression != null) {
+ if (!expression.isVoidCompatible() && LambdaUtil.isExpressionStatementExpression(psiExpression)) {
+ final PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
+ if (parent instanceof PsiExpressionList) {
+ final PsiElement gParent = parent.getParent();
+ if (gParent instanceof PsiCallExpression) {
+ final CandidateInfo[] candidates = PsiResolveHelper.SERVICE.getInstance(gParent.getProject())
+ .getReferencedMethodCandidates((PsiCallExpression)gParent, false);
+ if (candidates.length > 1) {
+ final List<CandidateInfo> info = new ArrayList<CandidateInfo>(Arrays.asList(candidates));
+ final LanguageLevel level = PsiUtil.getLanguageLevel(parent);
+ final JavaMethodsConflictResolver conflictResolver = new JavaMethodsConflictResolver((PsiExpressionList)parent, level);
+ final int applicability = conflictResolver.checkApplicability(info);
+ conflictResolver.checkSpecifics(info, applicability, level);
+ if (info.size() > 1) {
+ return;
+ }
+ }
+ }
+ }
+ }
final PsiElement errorElement;
final PsiElement parent = psiExpression.getParent();
if (parent instanceof PsiReturnStatement) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java
index 01993d5e9dd0..61d018f2f5ae 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java
@@ -28,6 +28,8 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
import java.util.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
+
class AbstractValues {
static final class ParamValue extends BasicValue {
ParamValue(Type tp) {
@@ -274,10 +276,8 @@ abstract class Analysis<Res> {
}
for (int i = 0; i < args.length; i++) {
BasicValue value;
- if (direction instanceof InOut && ((InOut)direction).paramIndex == i) {
- value = new AbstractValues.ParamValue(args[i]);
- }
- else if (direction instanceof In && ((In)direction).paramIndex == i) {
+ if (direction instanceof InOut && ((InOut)direction).paramIndex == i ||
+ direction instanceof In && ((In)direction).paramIndex == i) {
value = new AbstractValues.ParamValue(args[i]);
}
else {
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 84a5851e847d..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
@@ -28,6 +28,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.*;
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
/**
* @author lambdamix
@@ -272,25 +273,35 @@ public class BytecodeAnalysisConverter {
return null;
}
- private static int mkDirectionKey(Direction dir) {
- if (dir instanceof Out) {
+
+ 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();
}
}
@NotNull
private static Direction extractDirection(int directionKey) {
if (directionKey == 0) {
- return new Out();
+ return Out;
+ }
+ else if (directionKey == 1) {
+ return NullableOut;
}
else {
+ directionKey--;
int paramId = directionKey / 8;
int subDirection = directionKey % 8;
if (subDirection <= 2) {
@@ -344,7 +355,7 @@ public class BytecodeAnalysisConverter {
continue;
}
Direction direction = extractDirection(key.dirKey);
- if (value == Value.NotNull && direction instanceof Out && methodKey.equals(key)) {
+ if (value == Value.NotNull && direction == Out && methodKey.equals(key)) {
notNulls.add(key);
}
else if (direction instanceof InOut) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java
index a7070b882431..4311dd39c2c0 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java
@@ -40,7 +40,7 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
private static final ClassDataIndexer INDEXER = new ClassDataIndexer();
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
- private static final int ourInternalVersion = 1;
+ private static final int ourInternalVersion = 2;
private static boolean ourEnabled = SystemProperties.getBooleanProperty("idea.enable.bytecode.contract.inference", true);
@NotNull
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 316307896467..f06ad3fa8987 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
@@ -18,8 +18,6 @@ package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.codeInspection.bytecodeAnalysis.asm.*;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
-import com.intellij.openapi.util.NotNullLazyValue;
-import com.intellij.openapi.util.NullableLazyValue;
import com.intellij.openapi.util.Pair;
import com.intellij.util.indexing.DataIndexer;
import com.intellij.util.indexing.FileContent;
@@ -31,6 +29,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import java.security.MessageDigest;
import java.util.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
/**
@@ -125,7 +124,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
final Method method = new Method(className, methodNode.name, methodNode.desc);
final boolean stable = stableClass || (methodNode.access & STABLE_FLAGS) != 0 || "<init>".equals(methodNode.name);
- Key primaryKey = new Key(method, new Out(), stable);
+ Key primaryKey = new Key(method, Out, stable);
if (argumentTypes.length == 0 && !isInterestingResult) {
return Pair.create(primaryKey, EMPTY_EQUATIONS);
}
@@ -172,15 +171,15 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
}
private List<Equation<Key, Value>> processBranchingMethod(final Method method,
- final MethodNode methodNode,
- final RichControlFlow richControlFlow,
- Type[] argumentTypes,
- boolean isReferenceResult,
- boolean isInterestingResult,
- final boolean stable,
- boolean jsr) throws AnalyzerException {
-
- List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 1);
+ final MethodNode methodNode,
+ final RichControlFlow richControlFlow,
+ Type[] argumentTypes,
+ boolean isReferenceResult,
+ boolean isInterestingResult,
+ final boolean stable,
+ boolean jsr) throws AnalyzerException {
+
+ 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))) {
@@ -191,41 +190,25 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
final LeakingParameters leakingParametersAndFrames =
maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes, jsr) : null;
+
boolean[] leakingParameters =
leakingParametersAndFrames != null ? leakingParametersAndFrames.parameters : null;
boolean[] leakingNullableParameters =
leakingParametersAndFrames != null ? leakingParametersAndFrames.nullableParameters : null;
- final NullableLazyValue<boolean[]> origins = new NullableLazyValue<boolean[]>() {
- @Override
- protected boolean[] compute() {
- try {
- return OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow);
- }
- catch (AnalyzerException e) {
- LOG.debug("when processing " + method + " in " + presentableUrl, e);
- return null;
- }
- }
- };
+ final boolean[] origins =
+ isInterestingResult ?
+ OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow) :
+ null;
- NotNullLazyValue<Equation<Key, Value>> outEquation = new NotNullLazyValue<Equation<Key, Value>>() {
- @NotNull
- @Override
- protected Equation<Key, Value> compute() {
- if (origins.getValue() != null) {
- try {
- return new InOutAnalysis(richControlFlow, new Out(), origins.getValue(), stable).analyze();
- }
- catch (AnalyzerException ignored) {
- }
- }
- return new Equation<Key, Value>(new Key(method, new Out(), stable), FINAL_TOP);
- }
- };
+ Equation<Key, Value> outEquation =
+ isInterestingResult ?
+ new InOutAnalysis(richControlFlow, Out, origins, stable).analyze() :
+ null;
if (isReferenceResult) {
- result.add(outEquation.getValue());
+ result.add(outEquation);
+ result.add(new Equation<Key, Value>(new Key(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
}
for (int i = 0; i < argumentTypes.length; i++) {
@@ -259,47 +242,31 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
}
if (isReferenceArg && isInterestingResult) {
if (leakingParameters[i]) {
- if (origins.getValue() != null) {
- // result origins analysis was ok
- if (!notNullParam) {
- // may be null on some branch, running "null->..." analysis
- result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins.getValue(), stable).analyze());
- }
- else {
- // @NotNull, so "null->fail"
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
- }
- result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins.getValue(), stable).analyze());
+ if (!notNullParam) {
+ // may be null on some branch, running "null->..." analysis
+ result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable).analyze());
}
else {
- // result origins analysis failed, approximating to Top
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
+ // @NotNull, so "null->fail"
+ result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
}
+ result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable).analyze());
}
else {
// parameter is not leaking, so a contract is the same as for the whole method
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), outEquation.getValue().rhs));
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.getValue().rhs));
+ result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), outEquation.rhs));
+ result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.rhs));
}
}
if (ASMUtils.isBooleanType(argumentTypes[i]) && isInterestingResult) {
if (leakingParameters[i]) {
- if (origins.getValue() != null) {
- // result origins analysis was ok
- result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.False), origins.getValue(), stable).analyze());
- result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.True), origins.getValue(), stable).analyze());
- }
- else {
- // result origins analysis failed, approximating to Top
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), FINAL_TOP));
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), FINAL_TOP));
- }
+ result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.False), origins, stable).analyze());
+ result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.True), origins, stable).analyze());
}
else {
// parameter is not leaking, so a contract is the same as for the whole method
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), outEquation.getValue().rhs));
- result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), outEquation.getValue().rhs));
+ result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), outEquation.rhs));
+ result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), outEquation.rhs));
}
}
}
@@ -307,16 +274,17 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
}
private List<Equation<Key, Value>> processNonBranchingMethod(Method method,
- Type[] argumentTypes,
- ControlFlowGraph graph,
- boolean isReferenceResult,
- boolean isBooleanResult,
- boolean stable) throws AnalyzerException {
- List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 1);
- CombinedSingleAnalysis analyzer = new CombinedSingleAnalysis(method, graph);
+ Type[] argumentTypes,
+ ControlFlowGraph graph,
+ boolean isReferenceResult,
+ boolean isBooleanResult,
+ boolean stable) throws AnalyzerException {
+ 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];
@@ -338,13 +306,14 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
}
private List<Equation<Key, Value>> topEquations(Method method,
- Type[] argumentTypes,
- boolean isReferenceResult,
- boolean isInterestingResult,
- boolean stable) {
- List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
+ Type[] argumentTypes,
+ boolean isReferenceResult,
+ boolean isInterestingResult,
+ boolean stable) {
+ 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, new Out(), stable), FINAL_TOP));
+ 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];
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 645c00260af1..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
@@ -34,64 +34,116 @@ import java.util.Set;
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.CombinedData.*;
-final class ParamKey {
- final Method method;
- final int i;
- final boolean stable;
+// additional data structures for combined analysis
+interface CombinedData {
+ 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;
- }
+ 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;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
- ParamKey paramKey = (ParamKey)o;
+ ParamKey paramKey = (ParamKey)o;
- if (i != paramKey.i) return false;
- if (stable != paramKey.stable) return false;
- if (!method.equals(paramKey.method)) return false;
+ if (i != paramKey.i) return false;
+ if (stable != paramKey.stable) return false;
+ if (!method.equals(paramKey.method)) return false;
- return true;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = method.hashCode();
+ result = 31 * result + i;
+ result = 31 * result + (stable ? 1 : 0);
+ return result;
+ }
}
- @Override
- public int hashCode() {
- int result = method.hashCode();
- result = 31 * result + i;
- result = 31 * result + (stable ? 1 : 0);
- return result;
+ // value knowing at which instruction it was created
+ interface Trackable {
+ int getOriginInsnIndex();
}
-}
-final class CombinedCall extends BasicValue {
- final Method method;
- final boolean stableCall;
- final List<? extends BasicValue> args;
+ final class TrackableCallValue extends BasicValue implements Trackable {
+ 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, List<? extends BasicValue> args, boolean stableCall, boolean thisCall) {
+ super(tp);
+ this.originInsnIndex = originInsnIndex;
+ this.method = method;
+ this.args = args;
+ this.stableCall = stableCall;
+ this.thisCall = thisCall;
+ }
- CombinedCall(Type tp, Method method, boolean stableCall, List<? extends BasicValue> args) {
- super(tp);
- this.method = method;
- this.stableCall = stableCall;
- this.args = args;
+ @Override
+ public int getOriginInsnIndex() {
+ return originInsnIndex;
+ }
}
-}
-final class NParamValue extends BasicValue {
- final int n;
- public NParamValue(Type type, int n) {
- super(type);
- this.n = n;
+ final class NthParamValue extends BasicValue {
+ final int n;
+
+ public NthParamValue(Type type, int n) {
+ super(type);
+ this.n = n;
+ }
+ }
+
+ final class TrackableNullValue extends BasicValue implements Trackable {
+ static final Type NullType = Type.getObjectType("null");
+ private final int originInsnIndex;
+ public TrackableNullValue(int originInsnIndex) {
+ super(NullType);
+ this.originInsnIndex = originInsnIndex;
+ }
+
+ @Override
+ public int getOriginInsnIndex() {
+ return originInsnIndex;
+ }
}
+
+ final class TrackableValue extends BasicValue implements Trackable {
+ private final int originInsnIndex;
+
+ public TrackableValue(int originInsnIndex, Type type) {
+ super(type);
+ this.originInsnIndex = originInsnIndex;
+ }
+
+ @Override
+ public int getOriginInsnIndex() {
+ return originInsnIndex;
+ }
+ }
+
+ BasicValue ThisValue = new BasicValue(Type.getObjectType("java/lang/Object"));
}
-final class CombinedSingleAnalysis {
+// specialized class for analyzing methods without branching in single pass
+final class CombinedAnalysis {
+
private final ControlFlowGraph controlFlow;
private final Method method;
private final CombinedInterpreter interpreter;
@@ -99,11 +151,11 @@ final class CombinedSingleAnalysis {
private boolean exception;
private final MethodNode methodNode;
- CombinedSingleAnalysis(Method method, ControlFlowGraph controlFlow) {
+ CombinedAnalysis(Method method, ControlFlowGraph controlFlow) {
this.method = method;
this.controlFlow = controlFlow;
methodNode = controlFlow.methodNode;
- interpreter = new CombinedInterpreter(Type.getArgumentTypes(methodNode.desc).length);
+ interpreter = new CombinedInterpreter(methodNode.instructions, Type.getArgumentTypes(methodNode.desc).length);
}
final void analyze() throws AnalyzerException {
@@ -144,11 +196,11 @@ final class CombinedSingleAnalysis {
final Equation<Key, Value> notNullParamEquation(int i, boolean stable) {
final Key key = new Key(method, new In(i, In.NOT_NULL), stable);
final Result<Key, Value> result;
- if (interpreter.dereferenced[i]) {
+ if (interpreter.dereferencedParams[i]) {
result = new Final<Key, Value>(Value.NotNull);
}
else {
- Set<ParamKey> calls = interpreter.callDerefs[i];
+ Set<ParamKey> calls = interpreter.parameterFlow[i];
if (calls == null || calls.isEmpty()) {
result = new Final<Key, Value>(Value.Top);
}
@@ -166,11 +218,11 @@ final class CombinedSingleAnalysis {
final Equation<Key, Value> nullableParamEquation(int i, boolean stable) {
final Key key = new Key(method, new In(i, In.NULLABLE), stable);
final Result<Key, Value> result;
- if (interpreter.dereferenced[i] || interpreter.notNullable[i] || returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) {
+ if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
result = new Final<Key, Value>(Value.Top);
}
else {
- Set<ParamKey> calls = interpreter.callDerefs[i];
+ Set<ParamKey> calls = interpreter.parameterFlow[i];
if (calls == null || calls.isEmpty()) {
result = new Final<Key, Value>(Value.Null);
}
@@ -188,7 +240,7 @@ final class CombinedSingleAnalysis {
final Equation<Key, Value> contractEquation(int i, Value inValue, boolean stable) {
final Key key = new Key(method, new InOut(i, inValue), stable);
final Result<Key, Value> result;
- if (exception || (inValue == Value.Null && interpreter.dereferenced[i])) {
+ if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) {
result = new Final<Key, Value>(Value.Bot);
}
else if (FalseValue == returnValue) {
@@ -197,29 +249,29 @@ final class CombinedSingleAnalysis {
else if (TrueValue == returnValue) {
result = new Final<Key, Value>(Value.True);
}
- else if (NullValue == returnValue) {
+ else if (returnValue instanceof TrackableNullValue) {
result = new Final<Key, Value>(Value.Null);
}
- else if (returnValue instanceof NotNullValue) {
+ else if (returnValue instanceof NotNullValue || ThisValue == returnValue) {
result = new Final<Key, Value>(Value.NotNull);
}
- else if (returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) {
+ else if (returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
result = new Final<Key, Value>(inValue);
}
- else if (returnValue instanceof CombinedCall) {
- CombinedCall call = (CombinedCall)returnValue;
+ else if (returnValue instanceof TrackableCallValue) {
+ TrackableCallValue call = (TrackableCallValue)returnValue;
HashSet<Key> keys = new HashSet<Key>();
for (int argI = 0; argI < call.args.size(); argI++) {
BasicValue arg = call.args.get(argI);
- if (arg instanceof NParamValue) {
- NParamValue npv = (NParamValue)arg;
+ if (arg instanceof NthParamValue) {
+ NthParamValue npv = (NthParamValue)arg;
if (npv.n == i) {
keys.add(new Key(call.method, new InOut(argI, inValue), call.stableCall));
}
}
}
if (ASMUtils.isReferenceType(call.getType())) {
- keys.add(new Key(call.method, new Out(), call.stableCall));
+ keys.add(new Key(call.method, Out, call.stableCall));
}
if (keys.isEmpty()) {
result = new Final<Key, Value>(Value.Top);
@@ -234,7 +286,7 @@ final class CombinedSingleAnalysis {
}
final Equation<Key, Value> outContractEquation(boolean stable) {
- final Key key = new Key(method, new Out(), stable);
+ final Key key = new Key(method, Out, stable);
final Result<Key, Value> result;
if (exception) {
result = new Final<Key, Value>(Value.Bot);
@@ -245,15 +297,15 @@ final class CombinedSingleAnalysis {
else if (TrueValue == returnValue) {
result = new Final<Key, Value>(Value.True);
}
- else if (NullValue == returnValue) {
+ else if (returnValue instanceof TrackableNullValue) {
result = new Final<Key, Value>(Value.Null);
}
- else if (returnValue instanceof NotNullValue) {
+ else if (returnValue instanceof NotNullValue || returnValue == ThisValue) {
result = new Final<Key, Value>(Value.NotNull);
}
- else if (returnValue instanceof CombinedCall) {
- CombinedCall call = (CombinedCall)returnValue;
- Key callKey = new Key(call.method, new Out(), call.stableCall);
+ else if (returnValue instanceof TrackableCallValue) {
+ TrackableCallValue call = (TrackableCallValue)returnValue;
+ Key callKey = new Key(call.method, Out, call.stableCall);
Set<Key> keys = new SingletonSet<Key>(callKey);
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
}
@@ -263,6 +315,28 @@ final class CombinedSingleAnalysis {
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);
@@ -272,10 +346,10 @@ final class CombinedSingleAnalysis {
Type[] args = Type.getArgumentTypes(methodNode.desc);
int local = 0;
if ((methodNode.access & Opcodes.ACC_STATIC) == 0) {
- frame.setLocal(local++, new AbstractValues.NotNullValue(Type.getObjectType(controlFlow.className)));
+ frame.setLocal(local++, ThisValue);
}
for (int i = 0; i < args.length; i++) {
- BasicValue value = new NParamValue(args[i], i);
+ BasicValue value = new NthParamValue(args[i], i);
frame.setLocal(local++, value);
if (args[i].getSize() == 2) {
frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE);
@@ -289,25 +363,47 @@ final class CombinedSingleAnalysis {
}
final class CombinedInterpreter extends BasicInterpreter {
- final boolean[] dereferenced;
- final boolean[] notNullable;
- final Set<ParamKey>[] callDerefs;
-
- CombinedInterpreter(int arity) {
- dereferenced = new boolean[arity];
- notNullable = new boolean[arity];
- callDerefs = new Set[arity];
+ // Parameters dereferenced during execution of a method, tracked by parameter's indices.
+ // Dereferenced parameters are @NotNull.
+ final boolean[] dereferencedParams;
+ // Parameters, that are written to something or passed to an interface methods.
+ // This parameters cannot be @Nullable.
+ final boolean[] notNullableParams;
+ // parameterFlow(i) for i-th parameter stores a set parameter positions it is passed to
+ // parameter is @NotNull if any of its usages are @NotNull
+ final Set<ParamKey>[] parameterFlow;
+
+ // Trackable values that were dereferenced during execution of a method
+ // Values are are identified by `origin` index
+ final boolean[] dereferencedValues;
+ private final InsnList insns;
+
+ CombinedInterpreter(InsnList insns, int arity) {
+ dereferencedParams = new boolean[arity];
+ notNullableParams = new boolean[arity];
+ parameterFlow = new Set[arity];
+ this.insns = insns;
+ dereferencedValues = new boolean[insns.size()];
+ }
+
+ private int insnIndex(AbstractInsnNode insn) {
+ return insns.indexOf(insn);
+ }
+
+ private static BasicValue track(int origin, BasicValue basicValue) {
+ return basicValue == null ? null : new TrackableValue(origin, basicValue.getType());
}
@Override
public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
+ int origin = insnIndex(insn);
switch (insn.getOpcode()) {
case ICONST_0:
return FalseValue;
case ICONST_1:
return TrueValue;
case ACONST_NULL:
- return NullValue;
+ return new TrackableNullValue(origin);
case LDC:
Object cst = ((LdcInsnNode)insn).cst;
if (cst instanceof Type) {
@@ -330,22 +426,26 @@ final class CombinedInterpreter extends BasicInterpreter {
return new NotNullValue(Type.getObjectType(((TypeInsnNode)insn).desc));
default:
}
- return super.newOperation(insn);
+ return track(origin, super.newOperation(insn));
}
@Override
public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
+ int origin = insnIndex(insn);
switch (insn.getOpcode()) {
case GETFIELD:
case ARRAYLENGTH:
case MONITORENTER:
- if (value instanceof NParamValue) {
- dereferenced[((NParamValue)value).n] = true;
+ if (value instanceof NthParamValue) {
+ dereferencedParams[((NthParamValue)value).n] = true;
}
- return super.unaryOperation(insn, value);
+ if (value instanceof Trackable) {
+ dereferencedValues[((Trackable)value).getOriginInsnIndex()] = true;
+ }
+ return track(origin, super.unaryOperation(insn, value));
case CHECKCAST:
- if (value instanceof NParamValue) {
- return new NParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NParamValue)value).n);
+ if (value instanceof NthParamValue) {
+ return new NthParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NthParamValue)value).n);
}
break;
case NEWARRAY:
@@ -353,12 +453,23 @@ final class CombinedInterpreter extends BasicInterpreter {
return new NotNullValue(super.unaryOperation(insn, value).getType());
default:
}
- return super.unaryOperation(insn, value);
+ return track(origin, super.unaryOperation(insn, value));
}
@Override
public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException {
switch (insn.getOpcode()) {
+ case PUTFIELD:
+ if (value1 instanceof NthParamValue) {
+ dereferencedParams[((NthParamValue)value1).n] = true;
+ }
+ if (value1 instanceof Trackable) {
+ dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
+ }
+ if (value2 instanceof NthParamValue) {
+ notNullableParams[((NthParamValue)value2).n] = true;
+ }
+ break;
case IALOAD:
case LALOAD:
case FALOAD:
@@ -367,21 +478,16 @@ 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 (value1 instanceof NthParamValue) {
+ dereferencedParams[((NthParamValue)value1).n] = true;
}
- if (value2 instanceof NParamValue) {
- notNullable[((NParamValue)value2).n] = true;
+ if (value1 instanceof Trackable) {
+ dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
}
break;
default:
}
- return super.binaryOperation(insn, value1, value2);
+ return track(insnIndex(insn), super.binaryOperation(insn, value1, value2));
}
@Override
@@ -395,35 +501,46 @@ final class CombinedInterpreter extends BasicInterpreter {
case BASTORE:
case CASTORE:
case SASTORE:
- if (value1 instanceof NParamValue) {
- dereferenced[((NParamValue)value1).n] = true;
+ if (value1 instanceof NthParamValue) {
+ dereferencedParams[((NthParamValue)value1).n] = true;
+ }
+ if (value1 instanceof Trackable) {
+ dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
}
break;
case AASTORE:
- if (value1 instanceof NParamValue) {
- dereferenced[((NParamValue)value1).n] = true;
+ if (value1 instanceof NthParamValue) {
+ dereferencedParams[((NthParamValue)value1).n] = true;
+ }
+ if (value1 instanceof Trackable) {
+ dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
}
- if (value3 instanceof NParamValue) {
- notNullable[((NParamValue)value3).n] = true;
+ if (value3 instanceof NthParamValue) {
+ notNullableParams[((NthParamValue)value3).n] = true;
}
break;
default:
}
- return super.ternaryOperation(insn, value1, value2, value3);
+ return null;
}
@Override
public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> values) throws AnalyzerException {
int opCode = insn.getOpcode();
int shift = opCode == INVOKESTATIC ? 0 : 1;
-
+ int origin = insnIndex(insn);
switch (opCode) {
case INVOKESPECIAL:
case INVOKEINTERFACE:
case INVOKEVIRTUAL:
- if (values.get(0) instanceof NParamValue) {
- dereferenced[((NParamValue)values.get(0)).n] = true;
+ BasicValue receiver = values.get(0);
+ if (receiver instanceof NthParamValue) {
+ dereferencedParams[((NthParamValue)receiver).n] = true;
+ }
+ if (receiver instanceof Trackable) {
+ dereferencedValues[((Trackable)receiver).getOriginInsnIndex()] = true;
}
+ default:
}
switch (opCode) {
@@ -437,29 +554,31 @@ final class CombinedInterpreter extends BasicInterpreter {
Type retType = Type.getReturnType(mNode.desc);
for (int i = shift; i < values.size(); i++) {
- if (values.get(i) instanceof NParamValue) {
- int n = ((NParamValue)values.get(i)).n;
+ if (values.get(i) instanceof NthParamValue) {
+ int n = ((NthParamValue)values.get(i)).n;
if (opCode == INVOKEINTERFACE) {
- notNullable[n] = true;
+ notNullableParams[n] = true;
}
else {
- Set<ParamKey> npKeys = callDerefs[n];
+ Set<ParamKey> npKeys = parameterFlow[n];
if (npKeys == null) {
npKeys = new HashSet<ParamKey>();
- callDerefs[n] = npKeys;
+ parameterFlow[n] = npKeys;
}
npKeys.add(new ParamKey(method, i - shift, stable));
}
}
}
+ BasicValue receiver = null;
if (shift == 1) {
- values.remove(0);
+ receiver = values.remove(0);
}
- return new CombinedCall(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:
}
- return super.naryOperation(insn, values);
+ return track(origin, super.naryOperation(insn, values));
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java
index c382148abb05..73972871ee7b 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java
@@ -34,6 +34,7 @@ import java.util.Set;
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
class InOutAnalysis extends Analysis<Result<Key, Value>> {
@@ -396,7 +397,7 @@ class InOutInterpreter extends BasicInterpreter {
}
default:
}
- return super.ternaryOperation(insn, value1, value2, value3);
+ return null;
}
@Override
@@ -436,7 +437,7 @@ class InOutInterpreter extends BasicInterpreter {
}
}
if (isRefRetType) {
- keys.add(new Key(method, new Out(), stable));
+ keys.add(new Key(method, Out, stable));
}
if (!keys.isEmpty()) {
return new CallResultValue(retType, keys);
@@ -444,7 +445,7 @@ class InOutInterpreter extends BasicInterpreter {
}
else if (isRefRetType) {
HashSet<Key> keys = new HashSet<Key>();
- keys.add(new Key(method, new Out(), stable));
+ keys.add(new Key(method, Out, stable));
return new CallResultValue(retType, keys);
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java
index 55a842af684c..b76e31197482 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java
@@ -52,103 +52,109 @@ enum Value {
Bot, NotNull, Null, True, False, Top
}
-interface Direction {}
-
-final class In implements Direction {
- static final int NOT_NULL = 0;
- static final int NULLABLE = 1;
- final int paramIndex;
- final int nullityMask;
-
- In(int paramIndex, int nullityMask) {
- this.paramIndex = paramIndex;
- this.nullityMask = nullityMask;
- }
-
- @Override
- public String toString() {
- return "In " + paramIndex;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- In in = (In) o;
- if (paramIndex != in.paramIndex) return false;
- if (nullityMask != in.nullityMask) return false;
- return true;
- }
-
- @Override
- public int hashCode() {
- return 31*paramIndex + nullityMask;
- }
-
- public int paramId() {
- return paramIndex;
- }
-
-}
-
-final class InOut implements Direction {
- final int paramIndex;
- final Value inValue;
-
- InOut(int paramIndex, Value inValue) {
- this.paramIndex = paramIndex;
- this.inValue = inValue;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- InOut inOut = (InOut) o;
-
- if (paramIndex != inOut.paramIndex) return false;
- if (inValue != inOut.inValue) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = paramIndex;
- result = 31 * result + inValue.ordinal();
- return result;
- }
-
- @Override
- public String toString() {
- return "InOut " + paramIndex + " " + inValue.toString();
- }
-
- public int paramId() {
- return paramIndex;
- }
-
- public int valueId() {
- return inValue.ordinal();
- }
-}
-
-final class Out implements Direction {
- @Override
- public String toString() {
- return "Out";
- }
-
- @Override
- public int hashCode() {
- return 1;
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof Out;
- }
+interface Direction {
+ final class In implements Direction {
+ static final int NOT_NULL = 0;
+ static final int NULLABLE = 1;
+ final int paramIndex;
+ final int nullityMask;
+
+ In(int paramIndex, int nullityMask) {
+ this.paramIndex = paramIndex;
+ this.nullityMask = nullityMask;
+ }
+
+ @Override
+ public String toString() {
+ return "In " + paramIndex;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ In in = (In)o;
+ if (paramIndex != in.paramIndex) return false;
+ if (nullityMask != in.nullityMask) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * paramIndex + nullityMask;
+ }
+
+ public int paramId() {
+ return paramIndex;
+ }
+ }
+
+ final class InOut implements Direction {
+ final int paramIndex;
+ final Value inValue;
+
+ InOut(int paramIndex, Value inValue) {
+ this.paramIndex = paramIndex;
+ this.inValue = inValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ InOut inOut = (InOut)o;
+
+ if (paramIndex != inOut.paramIndex) return false;
+ if (inValue != inOut.inValue) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = paramIndex;
+ result = 31 * result + inValue.ordinal();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "InOut " + paramIndex + " " + inValue.toString();
+ }
+
+ public int paramId() {
+ return paramIndex;
+ }
+
+ public int valueId() {
+ return inValue.ordinal();
+ }
+ }
+
+ Direction Out = new Direction() {
+ @Override
+ public String toString() {
+ return "Out";
+ }
+
+ @Override
+ public int hashCode() {
+ return -1;
+ }
+ };
+
+ Direction NullableOut = new Direction() {
+ @Override
+ public String toString() {
+ return "NullableOut";
+ }
+
+ @Override
+ public int hashCode() {
+ return -2;
+ }
+ };
}
final class Key {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java
new file mode 100644
index 000000000000..cf2e8d7a1da4
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.bytecodeAnalysis;
+
+import com.intellij.codeInspection.bytecodeAnalysis.asm.AnalyzerExt;
+import com.intellij.codeInspection.bytecodeAnalysis.asm.InterpreterExt;
+import com.intellij.codeInspection.bytecodeAnalysis.asm.LiteAnalyzerExt;
+import org.jetbrains.org.objectweb.asm.Opcodes;
+import org.jetbrains.org.objectweb.asm.Type;
+import org.jetbrains.org.objectweb.asm.tree.*;
+import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
+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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.intellij.codeInspection.bytecodeAnalysis.NullableMethodAnalysisData.*;
+
+interface NullableMethodAnalysisData {
+ Type NullType = Type.getObjectType("null");
+ Type ThisType = Type.getObjectType("this");
+ Type CallType = Type.getObjectType("/Call");
+
+ final class LabeledNull extends BasicValue {
+ final int origins;
+
+ public LabeledNull(int origins) {
+ super(NullType);
+ this.origins = origins;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LabeledNull that = (LabeledNull)o;
+ return origins == that.origins;
+ }
+
+ @Override
+ public int hashCode() {
+ return origins;
+ }
+ }
+
+ final class Calls extends BasicValue {
+ final int mergedLabels;
+
+ public Calls(int mergedLabels) {
+ super(CallType);
+ this.mergedLabels = mergedLabels;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ Calls calls = (Calls)o;
+ return mergedLabels == calls.mergedLabels;
+ }
+
+ @Override
+ public int hashCode() {
+ return mergedLabels;
+ }
+ }
+
+ final class Constraint {
+ final static Constraint EMPTY = new Constraint(0, 0);
+
+ final int calls;
+ final int nulls;
+
+ public Constraint(int calls, int nulls) {
+ this.calls = calls;
+ this.nulls = nulls;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Constraint that = (Constraint)o;
+
+ if (calls != that.calls) return false;
+ if (nulls != that.nulls) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = calls;
+ result = 31 * result + nulls;
+ return result;
+ }
+ }
+
+ BasicValue ThisValue = new BasicValue(ThisType);
+}
+
+class NullableMethodAnalysis {
+
+ static Result<Key, Value> FinalNull = new Final<Key, Value>(Value.Null);
+ static Result<Key, Value> FinalBot = new Final<Key, Value>(Value.Bot);
+ static BasicValue lNull = new LabeledNull(0);
+
+ static Result<Key, Value> analyze(MethodNode methodNode, boolean[] origins, boolean jsr) throws AnalyzerException {
+ InsnList insns = methodNode.instructions;
+ Constraint[] data = new Constraint[insns.size()];
+ int[] originsMapping = mapOrigins(origins);
+
+ NullableMethodInterpreter interpreter = new NullableMethodInterpreter(insns, origins, originsMapping);
+ Frame<BasicValue>[] frames =
+ jsr ?
+ new AnalyzerExt<BasicValue, Constraint, NullableMethodInterpreter>(interpreter, data, Constraint.EMPTY).analyze("this", methodNode) :
+ new LiteAnalyzerExt<BasicValue, Constraint, NullableMethodInterpreter>(interpreter, data, Constraint.EMPTY).analyze("this", methodNode);
+
+ BasicValue result = BasicValue.REFERENCE_VALUE;
+ for (int i = 0; i < frames.length; i++) {
+ Frame<BasicValue> frame = frames[i];
+ if (frame != null && insns.get(i).getOpcode() == Opcodes.ARETURN) {
+ BasicValue stackTop = frame.pop();
+ result = combine(result, stackTop, data[i]);
+ }
+ }
+ if (result instanceof LabeledNull) {
+ return FinalNull;
+ }
+ if (result instanceof Calls) {
+ Calls calls = ((Calls)result);
+ int mergedMappedLabels = calls.mergedLabels;
+ if (mergedMappedLabels != 0) {
+ Set<Product<Key, Value>> sum = new HashSet<Product<Key, Value>>();
+ Key[] createdKeys = interpreter.keys;
+ for (int origin = 0; origin < originsMapping.length; origin++) {
+ int mappedOrigin = originsMapping[origin];
+ Key createdKey = createdKeys[origin];
+ if (createdKey != null && (mergedMappedLabels & (1 << mappedOrigin)) != 0) {
+ sum.add(new Product<Key, Value>(Value.Null, Collections.singleton(createdKey)));
+ }
+ }
+ if (!sum.isEmpty()) {
+ return new Pending<Key, Value>(sum);
+ }
+ }
+ }
+ return FinalBot;
+ }
+
+ private static int[] mapOrigins(boolean[] origins) {
+ int[] originsMapping = new int[origins.length];
+ int mapped = 0;
+ for (int i = 0; i < origins.length; i++) {
+ originsMapping[i] = origins[i] ? mapped++ : -1;
+ }
+ return originsMapping;
+ }
+
+ static BasicValue combine(BasicValue v1, BasicValue v2, Constraint constraint) {
+ if (v1 instanceof LabeledNull) {
+ return lNull;
+ }
+ else if (v2 instanceof LabeledNull) {
+ int v2Origins = ((LabeledNull)v2).origins;
+ int constraintOrigins = constraint.nulls;
+ int intersect = v2Origins & constraintOrigins;
+ return intersect == v2Origins ? v1 : lNull;
+ }
+ else if (v1 instanceof Calls) {
+ if (v2 instanceof Calls) {
+ Calls calls1 = (Calls)v1;
+ Calls calls2 = (Calls)v2;
+ int labels2 = calls2.mergedLabels;
+ int aliveLabels2 = labels2 - (labels2 & constraint.calls);
+ return new Calls(calls1.mergedLabels | aliveLabels2);
+ } else {
+ return v1;
+ }
+ }
+ else if (v2 instanceof Calls) {
+ Calls calls2 = (Calls)v2;
+ int labels2 = calls2.mergedLabels;
+ int aliveLabels2 = labels2 - (labels2 & constraint.calls);
+ return new Calls(aliveLabels2);
+ }
+ return BasicValue.REFERENCE_VALUE;
+ }
+}
+
+class NullableMethodInterpreter extends BasicInterpreter implements InterpreterExt<Constraint> {
+ final InsnList insns;
+ final boolean[] origins;
+ private final int[] originsMapping;
+ final Key[] keys;
+
+ Constraint constraint = null;
+ int delta = 0;
+ int nullsDelta = 0;
+ int notNullInsn = -1;
+ int notNullCall = 0;
+ int notNullNull = 0;
+
+ NullableMethodInterpreter(InsnList insns, boolean[] origins, int[] originsMapping) {
+ this.insns = insns;
+ this.origins = origins;
+ this.originsMapping = originsMapping;
+ keys = new Key[originsMapping.length];
+ }
+
+ @Override
+ public BasicValue newValue(Type type) {
+ return ThisType.equals(type) ? ThisValue : super.newValue(type);
+ }
+
+ @Override
+ public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
+ if (insn.getOpcode() == Opcodes.ACONST_NULL) {
+ int insnIndex = insns.indexOf(insn);
+ if (origins[insnIndex]) {
+ return new LabeledNull(1 << originsMapping[insnIndex]);
+ }
+ }
+ return super.newOperation(insn);
+ }
+
+ @Override
+ public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
+ switch (insn.getOpcode()) {
+ case GETFIELD:
+ case ARRAYLENGTH:
+ case MONITORENTER:
+ if (value instanceof Calls) {
+ delta = ((Calls)value).mergedLabels;
+ }
+ break;
+ case IFNULL:
+ if (value instanceof Calls) {
+ notNullInsn = insns.indexOf(insn) + 1;
+ notNullCall = ((Calls)value).mergedLabels;
+ }
+ else if (value instanceof LabeledNull) {
+ notNullInsn = insns.indexOf(insn) + 1;
+ notNullNull = ((LabeledNull)value).origins;
+ }
+ break;
+ case IFNONNULL:
+ if (value instanceof Calls) {
+ notNullInsn = insns.indexOf(((JumpInsnNode)insn).label);
+ notNullCall = ((Calls)value).mergedLabels;
+ }
+ else if (value instanceof LabeledNull) {
+ notNullInsn = insns.indexOf(((JumpInsnNode)insn).label);
+ notNullNull = ((LabeledNull)value).origins;
+ }
+ break;
+ default:
+
+ }
+ return super.unaryOperation(insn, value);
+ }
+
+ @Override
+ public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException {
+ switch (insn.getOpcode()) {
+ case PUTFIELD:
+ case IALOAD:
+ case LALOAD:
+ case FALOAD:
+ case DALOAD:
+ case AALOAD:
+ case BALOAD:
+ case CALOAD:
+ case SALOAD:
+ if (value1 instanceof Calls) {
+ delta = ((Calls)value1).mergedLabels;
+ }
+ if (value1 instanceof LabeledNull){
+ nullsDelta = ((LabeledNull)value1).origins;
+ }
+ break;
+ default:
+ }
+ return super.binaryOperation(insn, value1, value2);
+ }
+
+ @Override
+ public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3)
+ throws AnalyzerException {
+ if (value1 instanceof Calls) {
+ delta = ((Calls)value1).mergedLabels;
+ }
+ if (value1 instanceof LabeledNull){
+ nullsDelta = ((LabeledNull)value1).origins;
+ }
+ return null;
+ }
+
+ @Override
+ public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> values) throws AnalyzerException {
+ int opCode = insn.getOpcode();
+ switch (opCode) {
+ case INVOKESPECIAL:
+ case INVOKEINTERFACE:
+ case INVOKEVIRTUAL:
+ BasicValue receiver = values.get(0);
+ if (receiver instanceof Calls) {
+ delta = ((Calls)receiver).mergedLabels;
+ }
+ if (receiver instanceof LabeledNull){
+ nullsDelta = ((LabeledNull)receiver).origins;
+ }
+ break;
+ default:
+ }
+
+ switch (opCode) {
+ case INVOKESTATIC:
+ case INVOKESPECIAL:
+ case INVOKEVIRTUAL:
+ int insnIndex = insns.indexOf(insn);
+ if (origins[insnIndex]) {
+ boolean stable = (opCode == INVOKESTATIC) ||
+ (opCode == INVOKESPECIAL) ||
+ (values.get(0) == ThisValue);
+ MethodInsnNode mNode = ((MethodInsnNode)insn);
+ Method method = new Method(mNode.owner, mNode.name, mNode.desc);
+ int label = 1 << originsMapping[insnIndex];
+ if (keys[insnIndex] == null) {
+ keys[insnIndex] = new Key(method, Direction.NullableOut, stable);
+ }
+ return new Calls(label);
+ }
+ break;
+ default:
+ }
+ return super.naryOperation(insn, values);
+ }
+
+ @Override
+ public BasicValue merge(BasicValue v1, BasicValue v2) {
+ if (v1 instanceof LabeledNull) {
+ if (v2 instanceof LabeledNull) {
+ return new LabeledNull(((LabeledNull)v1).origins | ((LabeledNull)v2).origins);
+ }
+ else {
+ return v1;
+ }
+ }
+ else if (v2 instanceof LabeledNull) {
+ return v2;
+ }
+ else if (v1 instanceof Calls) {
+ if (v2 instanceof Calls) {
+ Calls calls1 = (Calls)v1;
+ Calls calls2 = (Calls)v2;
+ return new Calls(calls1.mergedLabels | calls2.mergedLabels);
+ }
+ else {
+ return v1;
+ }
+ }
+ else if (v2 instanceof Calls) {
+ return v2;
+ }
+ return super.merge(v1, v2);
+ }
+
+ // ---------- InterpreterExt<Constraint> --------------
+
+ @Override
+ public void init(Constraint previous) {
+ constraint = previous;
+ delta = 0;
+ nullsDelta = 0;
+
+ notNullInsn = -1;
+ notNullCall = 0;
+ notNullNull = 0;
+ }
+
+ @Override
+ public Constraint getAfterData(int insn) {
+ Constraint afterData = mkAfterData();
+ if (notNullInsn == insn) {
+ return new Constraint(afterData.calls | notNullCall, afterData.nulls | notNullNull);
+ }
+ return afterData;
+ }
+
+ private Constraint mkAfterData() {
+ if (delta == 0 && nullsDelta == 0 && notNullInsn == -1) {
+ return constraint;
+ }
+ return new Constraint(constraint.calls | delta, constraint.nulls | nullsDelta);
+ }
+
+ @Override
+ public Constraint merge(Constraint data1, Constraint data2) {
+ if (data1.equals(data2)) {
+ return data1;
+ } else {
+ return new Constraint(data1.calls | data2.calls, data1.nulls | data2.nulls);
+ }
+ }
+}
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 a7c25782d48b..6102c44792bf 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
@@ -37,6 +37,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.Instan
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.ParamValue;
import static com.intellij.codeInspection.bytecodeAnalysis.PResults.*;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
abstract class PResults {
// SoP = sum of products
@@ -706,7 +707,7 @@ abstract class NullityInterpreter extends BasicInterpreter {
break;
default:
}
- return super.ternaryOperation(insn, value1, value2, value3);
+ return null;
}
@Override
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 aa44951961ad..610a6fecdaf5 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
@@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ModificationTracker;
+import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
@@ -39,14 +40,18 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
+import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
+
/**
* @author lambdamix
*/
public class ProjectBytecodeAnalysis {
public static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.bytecodeAnalysis");
public static final Key<Boolean> INFERRED_ANNOTATION = Key.create("INFERRED_ANNOTATION");
+ public static final String NULLABLE_METHOD_TRANSITIVITY = "java.annotations.inference.nullable.method.transitivity";
public static final int EQUATIONS_LIMIT = 1000;
private final Project myProject;
+ private final boolean nullableMethodTransitivity;
public static ProjectBytecodeAnalysis getInstance(@NotNull Project project) {
return ServiceManager.getService(project, ProjectBytecodeAnalysis.class);
@@ -54,6 +59,7 @@ public class ProjectBytecodeAnalysis {
public ProjectBytecodeAnalysis(Project project) {
myProject = project;
+ nullableMethodTransitivity = Registry.is(NULLABLE_METHOD_TRANSITIVITY);
}
@Nullable
@@ -101,6 +107,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[]{
@@ -108,11 +115,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 + ")")
@@ -172,7 +190,7 @@ public class ProjectBytecodeAnalysis {
public static HKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
LOG.assertTrue(owner instanceof PsiCompiledElement, owner);
if (owner instanceof PsiMethod) {
- return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, new Out(), md);
+ return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, Out, md);
}
if (owner instanceof PsiParameter) {
PsiElement parent = owner.getParent();
@@ -196,16 +214,18 @@ public class ProjectBytecodeAnalysis {
private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey)
throws EquationsLimitException {
- final Solver notNullSolver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top));
- collectEquations(Collections.singletonList(notNullKey), notNullSolver);
+ Map<Bytes, List<HEquations>> equationsCache = new HashMap<Bytes, List<HEquations>>();
+
+ final Solver notNullSolver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top), Value.Top);
+ collectEquations(Collections.singletonList(notNullKey), notNullSolver, equationsCache);
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);
+ collectEquations(Collections.singletonList(nullableKey), nullableSolver, equationsCache);
HashMap<HKey, Value> nullableSolutions = nullableSolver.solve();
boolean nullable =
(Value.Null == nullableSolutions.get(nullableKey)) || (Value.Null == nullableSolutions.get(nullableKey.mkUnstable()));
@@ -215,15 +235,32 @@ 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();
+ Map<Bytes, List<HEquations>> equationsCache = new HashMap<Bytes, List<HEquations>>();
+
+ final Solver outSolver = new Solver(new ELattice<Value>(Value.Bot, Value.Top), Value.Top);
+ collectEquations(allKeys, outSolver, equationsCache);
+ 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));
+ if (nullableMethodTransitivity) {
+ collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver, equationsCache);
+ }
+ else {
+ collectSingleEquation(nullableKey, nullableMethodSolver, equationsCache);
+ }
+
+ HashMap<HKey, Value> nullableSolutions = nullableMethodSolver.solve();
+ if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.negate()) == Value.Null) {
+ result.nullables.add(key);
+ }
return result;
}
- private void collectEquations(List<HKey> keys, Solver solver) throws EquationsLimitException {
+ private void collectEquations(List<HKey> keys, Solver solver, @NotNull Map<Bytes, List<HEquations>> cache) throws EquationsLimitException {
+
GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject);
HashSet<HKey> queued = new HashSet<HKey>();
Stack<HKey> queue = new Stack<HKey>();
@@ -233,7 +270,6 @@ public class ProjectBytecodeAnalysis {
queued.add(key);
}
- HashMap<Bytes, List<HEquations>> cache = new HashMap<Bytes, List<HEquations>>();
FileBasedIndex index = FileBasedIndex.getInstance();
while (!queue.empty()) {
@@ -275,6 +311,32 @@ public class ProjectBytecodeAnalysis {
}
}
+ private void collectSingleEquation(HKey hKey, Solver solver, @NotNull Map<Bytes, List<HEquations>> cache) throws EquationsLimitException {
+ GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject);
+
+ FileBasedIndex index = FileBasedIndex.getInstance();
+
+ ProgressManager.checkCanceled();
+ Bytes bytes = new Bytes(hKey.key);
+
+ List<HEquations> hEquationss = cache.get(bytes);
+ if (hEquationss == null) {
+ hEquationss = index.getValues(BytecodeAnalysisIndex.NAME, bytes, librariesScope);
+ cache.put(bytes, hEquationss);
+ }
+
+ for (HEquations hEquations : hEquationss) {
+ boolean stable = hEquations.stable;
+ for (DirectionResultPair pair : hEquations.results) {
+ int dirKey = pair.directionKey;
+ if (dirKey == hKey.dirKey) {
+ HResult result = pair.hResult;
+ solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable), result));
+ }
+ }
+ }
+ }
+
@NotNull
private PsiAnnotation createAnnotationFromText(@NotNull final String text) throws IncorrectOperationException {
PsiAnnotation annotation = JavaPsiFacade.getElementFactory(myProject).createAnnotationFromText(text, null);
@@ -286,6 +348,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>();
}
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<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];
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/AnalyzerExt.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/AnalyzerExt.java
new file mode 100644
index 000000000000..0fae8d9d4c6c
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/AnalyzerExt.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.bytecodeAnalysis.asm;
+
+import org.jetbrains.org.objectweb.asm.Opcodes;
+import org.jetbrains.org.objectweb.asm.Type;
+import org.jetbrains.org.objectweb.asm.tree.*;
+import org.jetbrains.org.objectweb.asm.tree.analysis.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Extended version of {@link org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer}.
+ * It handles frames <b>and</b> additional data.
+ *
+ * @author lambdamix
+ */
+public class AnalyzerExt<V extends Value, Data, MyInterpreter extends Interpreter<V> & InterpreterExt<Data>> implements Opcodes {
+
+ private final MyInterpreter interpreter;
+
+ private int n;
+
+ private InsnList insns;
+
+ private List<TryCatchBlockNode>[] handlers;
+
+ private Frame<V>[] frames;
+
+ private Subroutine[] subroutines;
+
+ private boolean[] queued;
+
+ private int[] queue;
+
+ private int top;
+
+ public Data[] getData() {
+ return data;
+ }
+
+ private Data[] data;
+
+ public AnalyzerExt(final MyInterpreter interpreter, Data[] data, Data startData) {
+ this.interpreter = interpreter;
+ this.data = data;
+ if (data.length > 0) {
+ data[0] = startData;
+ }
+ }
+
+ public Frame<V>[] analyze(final String owner, final MethodNode m) throws AnalyzerException {
+ if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) {
+ frames = (Frame<V>[]) new Frame<?>[0];
+ return frames;
+ }
+ final V refV = (V) BasicValue.REFERENCE_VALUE;
+
+ n = m.instructions.size();
+ insns = m.instructions;
+ handlers = (List<TryCatchBlockNode>[]) new List<?>[n];
+ frames = (Frame<V>[]) new Frame<?>[n];
+ subroutines = new Subroutine[n];
+ queued = new boolean[n];
+ queue = new int[n];
+ top = 0;
+
+ // computes exception handlers for each instruction
+ for (int i = 0; i < m.tryCatchBlocks.size(); ++i) {
+ TryCatchBlockNode tcb = m.tryCatchBlocks.get(i);
+ int begin = insns.indexOf(tcb.start);
+ int end = insns.indexOf(tcb.end);
+ for (int j = begin; j < end; ++j) {
+ List<TryCatchBlockNode> insnHandlers = handlers[j];
+ if (insnHandlers == null) {
+ insnHandlers = new ArrayList<TryCatchBlockNode>();
+ handlers[j] = insnHandlers;
+ }
+ insnHandlers.add(tcb);
+ }
+ }
+
+ // computes the subroutine for each instruction:
+ Subroutine main = new Subroutine(null, m.maxLocals, null);
+ List<AbstractInsnNode> subroutineCalls = new ArrayList<AbstractInsnNode>();
+ Map<LabelNode, Subroutine> subroutineHeads = new HashMap<LabelNode, Subroutine>();
+ findSubroutine(0, main, subroutineCalls);
+ while (!subroutineCalls.isEmpty()) {
+ JumpInsnNode jsr = (JumpInsnNode) subroutineCalls.remove(0);
+ Subroutine sub = subroutineHeads.get(jsr.label);
+ if (sub == null) {
+ sub = new Subroutine(jsr.label, m.maxLocals, jsr);
+ subroutineHeads.put(jsr.label, sub);
+ findSubroutine(insns.indexOf(jsr.label), sub, subroutineCalls);
+ } else {
+ sub.callers.add(jsr);
+ }
+ }
+ for (int i = 0; i < n; ++i) {
+ if (subroutines[i] != null && subroutines[i].start == null) {
+ subroutines[i] = null;
+ }
+ }
+
+ // initializes the data structures for the control flow analysis
+ Frame<V> current = newFrame(m.maxLocals, m.maxStack);
+ Frame<V> handler = newFrame(m.maxLocals, m.maxStack);
+ current.setReturn(interpreter.newValue(Type.getReturnType(m.desc)));
+ Type[] args = Type.getArgumentTypes(m.desc);
+ int local = 0;
+ if ((m.access & ACC_STATIC) == 0) {
+ Type ctype = Type.getObjectType(owner);
+ current.setLocal(local++, interpreter.newValue(ctype));
+ }
+ for (int i = 0; i < args.length; ++i) {
+ current.setLocal(local++, interpreter.newValue(args[i]));
+ if (args[i].getSize() == 2) {
+ current.setLocal(local++, interpreter.newValue(null));
+ }
+ }
+ while (local < m.maxLocals) {
+ current.setLocal(local++, interpreter.newValue(null));
+ }
+
+ interpreter.init(data[0]);
+ merge(0, current, null);
+
+ init(owner, m);
+
+ // control flow analysis
+ while (top > 0) {
+ int insn = queue[--top];
+ Frame<V> f = frames[insn];
+ Subroutine subroutine = subroutines[insn];
+ queued[insn] = false;
+
+ AbstractInsnNode insnNode = null;
+ try {
+ insnNode = m.instructions.get(insn);
+ int insnOpcode = insnNode.getOpcode();
+ int insnType = insnNode.getType();
+
+ if (insnType == AbstractInsnNode.LABEL
+ || insnType == AbstractInsnNode.LINE
+ || insnType == AbstractInsnNode.FRAME) {
+ interpreter.init(data[insn]);
+ merge(insn + 1, f, subroutine);
+ newControlFlowEdge(insn, insn + 1);
+ } else {
+ // delta
+ interpreter.init(data[insn]);
+ current.init(f).execute(insnNode, interpreter);
+ subroutine = subroutine == null ? null : subroutine.copy();
+
+ if (insnNode instanceof JumpInsnNode) {
+ JumpInsnNode j = (JumpInsnNode) insnNode;
+ if (insnOpcode != GOTO && insnOpcode != JSR) {
+ merge(insn + 1, current, subroutine);
+ newControlFlowEdge(insn, insn + 1);
+ }
+ int jump = insns.indexOf(j.label);
+ if (insnOpcode == JSR) {
+ merge(jump, current, new Subroutine(j.label,
+ m.maxLocals, j));
+ } else {
+ merge(jump, current, subroutine);
+ }
+ newControlFlowEdge(insn, jump);
+ } else if (insnNode instanceof LookupSwitchInsnNode) {
+ LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode;
+ int jump = insns.indexOf(lsi.dflt);
+ merge(jump, current, subroutine);
+ newControlFlowEdge(insn, jump);
+ for (int j = 0; j < lsi.labels.size(); ++j) {
+ LabelNode label = lsi.labels.get(j);
+ jump = insns.indexOf(label);
+ merge(jump, current, subroutine);
+ newControlFlowEdge(insn, jump);
+ }
+ } else if (insnNode instanceof TableSwitchInsnNode) {
+ TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode;
+ int jump = insns.indexOf(tsi.dflt);
+ merge(jump, current, subroutine);
+ newControlFlowEdge(insn, jump);
+ for (int j = 0; j < tsi.labels.size(); ++j) {
+ LabelNode label = tsi.labels.get(j);
+ jump = insns.indexOf(label);
+ merge(jump, current, subroutine);
+ newControlFlowEdge(insn, jump);
+ }
+ } else if (insnOpcode == RET) {
+ if (subroutine == null) {
+ throw new AnalyzerException(insnNode,
+ "RET instruction outside of a sub routine");
+ }
+ for (int i = 0; i < subroutine.callers.size(); ++i) {
+ JumpInsnNode caller = subroutine.callers.get(i);
+ int call = insns.indexOf(caller);
+ if (frames[call] != null) {
+ merge(call + 1, frames[call], current,
+ subroutines[call], subroutine.access);
+ newControlFlowEdge(insn, call + 1);
+ }
+ }
+ } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
+ if (subroutine != null) {
+ if (insnNode instanceof VarInsnNode) {
+ int var = ((VarInsnNode) insnNode).var;
+ subroutine.access[var] = true;
+ if (insnOpcode == LLOAD || insnOpcode == DLOAD
+ || insnOpcode == LSTORE
+ || insnOpcode == DSTORE) {
+ subroutine.access[var + 1] = true;
+ }
+ } else if (insnNode instanceof IincInsnNode) {
+ int var = ((IincInsnNode) insnNode).var;
+ subroutine.access[var] = true;
+ }
+ }
+ merge(insn + 1, current, subroutine);
+ newControlFlowEdge(insn, insn + 1);
+ }
+ }
+
+ List<TryCatchBlockNode> insnHandlers = handlers[insn];
+ if (insnHandlers != null) {
+ for (int i = 0; i < insnHandlers.size(); ++i) {
+ TryCatchBlockNode tcb = insnHandlers.get(i);
+ int jump = insns.indexOf(tcb.handler);
+ if (newControlFlowExceptionEdge(insn, tcb)) {
+ handler.init(f);
+ handler.clearStack();
+ handler.push(refV);
+ merge(jump, handler, subroutine);
+ }
+ }
+ }
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(e.node, "Error at instruction "
+ + insn + ": " + e.getMessage(), e);
+ } catch (Exception e) {
+ throw new AnalyzerException(insnNode, "Error at instruction "
+ + insn + ": " + e.getMessage(), e);
+ }
+ }
+
+ return frames;
+ }
+
+ private void findSubroutine(int insn, final Subroutine sub,
+ final List<AbstractInsnNode> calls) throws AnalyzerException {
+ while (true) {
+ if (insn < 0 || insn >= n) {
+ throw new AnalyzerException(null,
+ "Execution can fall off end of the code");
+ }
+ if (subroutines[insn] != null) {
+ return;
+ }
+ subroutines[insn] = sub.copy();
+ AbstractInsnNode node = insns.get(insn);
+
+ // calls findSubroutine recursively on normal successors
+ if (node instanceof JumpInsnNode) {
+ if (node.getOpcode() == JSR) {
+ // do not follow a JSR, it leads to another subroutine!
+ calls.add(node);
+ } else {
+ JumpInsnNode jnode = (JumpInsnNode) node;
+ findSubroutine(insns.indexOf(jnode.label), sub, calls);
+ }
+ } else if (node instanceof TableSwitchInsnNode) {
+ TableSwitchInsnNode tsnode = (TableSwitchInsnNode) node;
+ findSubroutine(insns.indexOf(tsnode.dflt), sub, calls);
+ for (int i = tsnode.labels.size() - 1; i >= 0; --i) {
+ LabelNode l = tsnode.labels.get(i);
+ findSubroutine(insns.indexOf(l), sub, calls);
+ }
+ } else if (node instanceof LookupSwitchInsnNode) {
+ LookupSwitchInsnNode lsnode = (LookupSwitchInsnNode) node;
+ findSubroutine(insns.indexOf(lsnode.dflt), sub, calls);
+ for (int i = lsnode.labels.size() - 1; i >= 0; --i) {
+ LabelNode l = lsnode.labels.get(i);
+ findSubroutine(insns.indexOf(l), sub, calls);
+ }
+ }
+
+ // calls findSubroutine recursively on exception handler successors
+ List<TryCatchBlockNode> insnHandlers = handlers[insn];
+ if (insnHandlers != null) {
+ for (int i = 0; i < insnHandlers.size(); ++i) {
+ TryCatchBlockNode tcb = insnHandlers.get(i);
+ findSubroutine(insns.indexOf(tcb.handler), sub, calls);
+ }
+ }
+
+ // if insn does not falls through to the next instruction, return.
+ switch (node.getOpcode()) {
+ case GOTO:
+ case RET:
+ case TABLESWITCH:
+ case LOOKUPSWITCH:
+ case IRETURN:
+ case LRETURN:
+ case FRETURN:
+ case DRETURN:
+ case ARETURN:
+ case RETURN:
+ case ATHROW:
+ return;
+ }
+ insn++;
+ }
+ }
+
+ public Frame<V>[] getFrames() {
+ return frames;
+ }
+
+ public List<TryCatchBlockNode> getHandlers(final int insn) {
+ return handlers[insn];
+ }
+
+ protected void init(String owner, MethodNode m) throws AnalyzerException {
+ }
+
+ protected Frame<V> newFrame(final int nLocals, final int nStack) {
+ return new Frame<V>(nLocals, nStack);
+ }
+
+ protected Frame<V> newFrame(final Frame<? extends V> src) {
+ return new Frame<V>(src);
+ }
+
+ protected void newControlFlowEdge(final int insn, final int successor) {
+ }
+
+ protected boolean newControlFlowExceptionEdge(final int insn,
+ final int successor) {
+ return true;
+ }
+
+ protected boolean newControlFlowExceptionEdge(final int insn,
+ final TryCatchBlockNode tcb) {
+ return newControlFlowExceptionEdge(insn, insns.indexOf(tcb.handler));
+ }
+
+ // -------------------------------------------------------------------------
+
+ private void merge(final int insn, final Frame<V> frame,
+ final Subroutine subroutine) throws AnalyzerException {
+ Frame<V> oldFrame = frames[insn];
+ Subroutine oldSubroutine = subroutines[insn];
+ boolean changes;
+
+ if (oldFrame == null) {
+ frames[insn] = newFrame(frame);
+ changes = true;
+ } else {
+ changes = oldFrame.merge(frame, interpreter);
+ }
+
+ if (oldSubroutine == null) {
+ if (subroutine != null) {
+ subroutines[insn] = subroutine.copy();
+ changes = true;
+ }
+ } else {
+ if (subroutine != null) {
+ changes |= oldSubroutine.merge(subroutine);
+ }
+ }
+ if (changes && !queued[insn]) {
+ queued[insn] = true;
+ queue[top++] = insn;
+ }
+
+ // delta
+ mergeData(insn, interpreter);
+ }
+
+ private void merge(final int insn, final Frame<V> beforeJSR,
+ final Frame<V> afterRET, final Subroutine subroutineBeforeJSR,
+ final boolean[] access) throws AnalyzerException {
+ Frame<V> oldFrame = frames[insn];
+ Subroutine oldSubroutine = subroutines[insn];
+ boolean changes;
+
+ afterRET.merge(beforeJSR, access);
+
+ if (oldFrame == null) {
+ frames[insn] = newFrame(afterRET);
+ changes = true;
+ } else {
+ changes = oldFrame.merge(afterRET, interpreter);
+ }
+
+ if (oldSubroutine != null && subroutineBeforeJSR != null) {
+ changes |= oldSubroutine.merge(subroutineBeforeJSR);
+ }
+ if (changes && !queued[insn]) {
+ queued[insn] = true;
+ queue[top++] = insn;
+ }
+
+ // delta
+ mergeData(insn, interpreter);
+ }
+
+ private void mergeData(int insn, MyInterpreter interpreter) {
+ boolean changes = false;
+
+ Data oldData = data[insn];
+ Data newData = interpreter.getAfterData(insn);
+
+ if (oldData == null) {
+ data[insn] = newData;
+ changes = true;
+ } else if (newData != null) {
+ Data mergedData = interpreter.merge(oldData, newData);
+ data[insn] = mergedData;
+ changes = !oldData.equals(mergedData);
+ }
+
+ if (changes && !queued[insn]) {
+ queued[insn] = true;
+ queue[top++] = insn;
+ }
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java
index 5804723e9ec1..ae1c7b819cb8 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java
@@ -31,52 +31,6 @@ import java.util.Map;
* So, the main point here is handling of subroutines (jsr) and try-catch-finally blocks.
*/
public class FramelessAnalyzer implements Opcodes {
- static class Subroutine {
-
- LabelNode start;
- boolean[] access;
- List<JumpInsnNode> callers;
-
- private Subroutine() {
- }
-
- Subroutine(@Nullable final LabelNode start, final int maxLocals,
- @Nullable final JumpInsnNode caller) {
- this.start = start;
- this.access = new boolean[maxLocals];
- this.callers = new ArrayList<JumpInsnNode>();
- callers.add(caller);
- }
-
- public Subroutine copy() {
- Subroutine result = new Subroutine();
- result.start = start;
- result.access = new boolean[access.length];
- System.arraycopy(access, 0, result.access, 0, access.length);
- result.callers = new ArrayList<JumpInsnNode>(callers);
- return result;
- }
-
- public boolean merge(final Subroutine subroutine) throws AnalyzerException {
- boolean changes = false;
- for (int i = 0; i < access.length; ++i) {
- if (subroutine.access[i] && !access[i]) {
- access[i] = true;
- changes = true;
- }
- }
- if (subroutine.start == start) {
- for (int i = 0; i < subroutine.callers.size(); ++i) {
- JumpInsnNode caller = subroutine.callers.get(i);
- if (!callers.contains(caller)) {
- callers.add(caller);
- changes = true;
- }
- }
- }
- return changes;
- }
- }
private int n;
private InsnList insns;
private List<TryCatchBlockNode>[] handlers;
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/InterpreterExt.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/InterpreterExt.java
new file mode 100644
index 000000000000..5b284a074882
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/InterpreterExt.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.bytecodeAnalysis.asm;
+
+/**
+ * @author lambdamix
+ */
+public interface InterpreterExt<Data> { // self: Interpreter[_] =>
+
+ // init interpreter state by passing entry data
+ void init(Data previous);
+
+ // exit data after execution for edge to insn
+ // there are may be different outcomes for different edges if an instruction was branching one
+ Data getAfterData(int insn);
+
+ // merge two states
+ Data merge(Data data1, Data data2);
+}
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 1a7ab15722f9..97b129b9c774 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
@@ -262,7 +262,7 @@ class ParametersUsage extends Interpreter<ParamsValue> {
@Override
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
- return val1;
+ return null;
}
@Override
@@ -458,7 +458,7 @@ class IParametersUsage extends Interpreter<IParamsValue> {
break;
default:
}
- return val1;
+ return null;
}
@Override
@@ -560,6 +560,7 @@ class LeakingParametersCollector extends ParametersUsage {
@Override
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
+ boolean[] params;
switch (insn.getOpcode()) {
case IASTORE:
case LASTORE:
@@ -568,7 +569,7 @@ class LeakingParametersCollector extends ParametersUsage {
case BASTORE:
case CASTORE:
case SASTORE:
- boolean[] params = value1.params;
+ params = value1.params;
for (int i = 0; i < arity; i++) {
leaking[i] |= params[i];
}
@@ -585,7 +586,7 @@ class LeakingParametersCollector extends ParametersUsage {
break;
default:
}
- return super.ternaryOperation(insn, value1, value2, value3);
+ return null;
}
@Override
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzerExt.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzerExt.java
new file mode 100644
index 000000000000..06dd39ffab96
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzerExt.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.bytecodeAnalysis.asm;
+
+import org.jetbrains.org.objectweb.asm.Opcodes;
+import org.jetbrains.org.objectweb.asm.Type;
+import org.jetbrains.org.objectweb.asm.tree.*;
+import org.jetbrains.org.objectweb.asm.tree.analysis.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Extended version of {@link com.intellij.codeInspection.bytecodeAnalysis.asm.LiteAnalyzer}.
+ * It handles frames <b>and</b> additional data.
+ *
+ * @author lambdamix
+ */
+public class LiteAnalyzerExt<V extends Value, Data, MyInterpreter extends Interpreter<V> & InterpreterExt<Data>> implements Opcodes {
+
+ private final MyInterpreter interpreter;
+ private Frame<V>[] frames;
+ private boolean[] queued;
+ private int[] queue;
+ private int top;
+
+ public Data[] getData() {
+ return data;
+ }
+
+ private Data[] data;
+
+ public LiteAnalyzerExt(final MyInterpreter interpreter, Data[] data, Data startData) {
+ this.interpreter = interpreter;
+ this.data = data;
+ if (data.length > 0) {
+ data[0] = startData;
+ }
+ }
+
+ public Frame<V>[] analyze(final String owner, final MethodNode m) throws AnalyzerException {
+ if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) {
+ frames = (Frame<V>[]) new Frame<?>[0];
+ return frames;
+ }
+
+ final V refV = (V) BasicValue.REFERENCE_VALUE;
+
+ int n = m.instructions.size();
+ InsnList insns = m.instructions;
+ List<TryCatchBlockNode>[] handlers = (List<TryCatchBlockNode>[]) new List<?>[n];
+ frames = (Frame<V>[]) new Frame<?>[n];
+ queued = new boolean[n];
+ queue = new int[n];
+ top = 0;
+
+ // computes exception handlers for each instruction
+ for (int i = 0; i < m.tryCatchBlocks.size(); ++i) {
+ TryCatchBlockNode tcb = m.tryCatchBlocks.get(i);
+ int begin = insns.indexOf(tcb.start);
+ int end = insns.indexOf(tcb.end);
+ for (int j = begin; j < end; ++j) {
+ List<TryCatchBlockNode> insnHandlers = handlers[j];
+ if (insnHandlers == null) {
+ insnHandlers = new ArrayList<TryCatchBlockNode>();
+ handlers[j] = insnHandlers;
+ }
+ insnHandlers.add(tcb);
+ }
+ }
+
+ // initializes the data structures for the control flow analysis
+ Frame<V> current = newFrame(m.maxLocals, m.maxStack);
+ Frame<V> handler = newFrame(m.maxLocals, m.maxStack);
+ current.setReturn(interpreter.newValue(Type.getReturnType(m.desc)));
+ Type[] args = Type.getArgumentTypes(m.desc);
+ int local = 0;
+ if ((m.access & ACC_STATIC) == 0) {
+ Type ctype = Type.getObjectType(owner);
+ current.setLocal(local++, interpreter.newValue(ctype));
+ }
+ for (int i = 0; i < args.length; ++i) {
+ current.setLocal(local++, interpreter.newValue(args[i]));
+ if (args[i].getSize() == 2) {
+ current.setLocal(local++, interpreter.newValue(null));
+ }
+ }
+ while (local < m.maxLocals) {
+ current.setLocal(local++, interpreter.newValue(null));
+ }
+
+ interpreter.init(data[0]);
+ merge(0, current);
+
+ // control flow analysis
+ while (top > 0) {
+ int insn = queue[--top];
+ Frame<V> f = frames[insn];
+ queued[insn] = false;
+
+ AbstractInsnNode insnNode = null;
+ try {
+ insnNode = m.instructions.get(insn);
+ int insnOpcode = insnNode.getOpcode();
+ int insnType = insnNode.getType();
+
+ if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
+ interpreter.init(data[insn]);
+ merge(insn + 1, f);
+ } else {
+ // delta
+ interpreter.init(data[insn]);
+ current.init(f).execute(insnNode, interpreter);
+
+ if (insnNode instanceof JumpInsnNode) {
+ JumpInsnNode j = (JumpInsnNode) insnNode;
+ if (insnOpcode != GOTO && insnOpcode != JSR) {
+ merge(insn + 1, current);
+ }
+ int jump = insns.indexOf(j.label);
+ merge(jump, current);
+ } else if (insnNode instanceof LookupSwitchInsnNode) {
+ LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode;
+ int jump = insns.indexOf(lsi.dflt);
+ merge(jump, current);
+ for (int j = 0; j < lsi.labels.size(); ++j) {
+ LabelNode label = lsi.labels.get(j);
+ jump = insns.indexOf(label);
+ merge(jump, current);
+ }
+ } else if (insnNode instanceof TableSwitchInsnNode) {
+ TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode;
+ int jump = insns.indexOf(tsi.dflt);
+ merge(jump, current);
+ for (int j = 0; j < tsi.labels.size(); ++j) {
+ LabelNode label = tsi.labels.get(j);
+ jump = insns.indexOf(label);
+ merge(jump, current);
+ }
+ } else if (insnOpcode != ATHROW
+ && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
+ merge(insn + 1, current);
+ }
+ }
+
+ List<TryCatchBlockNode> insnHandlers = handlers[insn];
+ if (insnHandlers != null) {
+ for (int i = 0; i < insnHandlers.size(); ++i) {
+ TryCatchBlockNode tcb = insnHandlers.get(i);
+ int jump = insns.indexOf(tcb.handler);
+ handler.init(f);
+ handler.clearStack();
+ handler.push(refV);
+ merge(jump, handler);
+ }
+ }
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(e.node, "Error at instruction " + insn + ": " + e.getMessage(), e);
+ } catch (Exception e) {
+ throw new AnalyzerException(insnNode, "Error at instruction " + insn + ": " + e.getMessage(), e);
+ }
+ }
+
+ return frames;
+ }
+
+ public Frame<V>[] getFrames() {
+ return frames;
+ }
+
+ protected Frame<V> newFrame(final int nLocals, final int nStack) {
+ return new Frame<V>(nLocals, nStack);
+ }
+
+ protected Frame<V> newFrame(final Frame<? extends V> src) {
+ return new Frame<V>(src);
+ }
+
+ // -------------------------------------------------------------------------
+
+ private void merge(final int insn, final Frame<V> frame) throws AnalyzerException {
+ Frame<V> oldFrame = frames[insn];
+ boolean changes;
+
+ if (oldFrame == null) {
+ frames[insn] = newFrame(frame);
+ changes = true;
+ } else {
+ changes = oldFrame.merge(frame, interpreter);
+ }
+
+ Data oldData = data[insn];
+ Data newData = interpreter.getAfterData(insn);
+
+ if (oldData == null) {
+ data[insn] = newData;
+ changes = true;
+ } else if (newData != null) {
+ Data mergedData = interpreter.merge(oldData, newData);
+ data[insn] = mergedData;
+ changes |= !oldData.equals(mergedData);
+ }
+
+ if (changes && !queued[insn]) {
+ queued[insn] = true;
+ queue[top++] = insn;
+ }
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java
index 0a9721018862..3cfb1ae51feb 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java
@@ -29,11 +29,11 @@ import java.util.List;
public class LiteFramelessAnalyzer extends FramelessAnalyzer {
@Override
- protected void findSubroutine(int insn, FramelessAnalyzer.Subroutine sub, List<AbstractInsnNode> calls) throws AnalyzerException {
+ protected void findSubroutine(int insn, Subroutine sub, List<AbstractInsnNode> calls) throws AnalyzerException {
}
@Override
- protected void merge(final int insn, final FramelessAnalyzer.Subroutine subroutine) throws AnalyzerException {
+ protected void merge(final int insn, final Subroutine subroutine) throws AnalyzerException {
if (!wasQueued[insn]) {
wasQueued[insn] = true;
if (!queued[insn]) {
@@ -44,7 +44,7 @@ public class LiteFramelessAnalyzer extends FramelessAnalyzer {
}
@Override
- protected void merge(final int insn, final FramelessAnalyzer.Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException {
+ protected void merge(final int insn, final Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException {
if (!wasQueued[insn]) {
wasQueued[insn] = true;
if (!queued[insn]) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/Subroutine.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/Subroutine.java
new file mode 100644
index 000000000000..a484b1b1fdb1
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/Subroutine.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.bytecodeAnalysis.asm;
+
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode;
+import org.jetbrains.org.objectweb.asm.tree.LabelNode;
+import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author lambdamix
+ */
+public class Subroutine {
+
+ LabelNode start;
+ boolean[] access;
+ List<JumpInsnNode> callers;
+
+ private Subroutine() {
+ }
+
+ Subroutine(@Nullable final LabelNode start, final int maxLocals,
+ @Nullable final JumpInsnNode caller) {
+ this.start = start;
+ this.access = new boolean[maxLocals];
+ this.callers = new ArrayList<JumpInsnNode>();
+ callers.add(caller);
+ }
+
+ public Subroutine copy() {
+ Subroutine result = new Subroutine();
+ result.start = start;
+ result.access = new boolean[access.length];
+ System.arraycopy(access, 0, result.access, 0, access.length);
+ result.callers = new ArrayList<JumpInsnNode>(callers);
+ return result;
+ }
+
+ public boolean merge(final Subroutine subroutine) throws AnalyzerException {
+ boolean changes = false;
+ for (int i = 0; i < access.length; ++i) {
+ if (subroutine.access[i] && !access[i]) {
+ access[i] = true;
+ changes = true;
+ }
+ }
+ if (subroutine.start == start) {
+ for (int i = 0; i < subroutine.callers.size(); ++i) {
+ JumpInsnNode caller = subroutine.callers.get(i);
+ if (!callers.contains(caller)) {
+ callers.add(caller);
+ changes = true;
+ }
+ }
+ }
+ return changes;
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java
index 7037fac8cfea..f919ac5f8796 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java
@@ -31,6 +31,7 @@ import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -59,12 +60,27 @@ public class ContractInference {
class ContractInferenceInterpreter {
private final PsiMethod myMethod;
+ private final ValueConstraint[] myEmptyConstraints;
public ContractInferenceInterpreter(PsiMethod method) {
myMethod = method;
+ myEmptyConstraints = MethodContract.createConstraintArray(myMethod.getParameterList().getParametersCount());
}
List<MethodContract> inferContracts() {
+ final boolean notNull = NullableNotNullManager.isNotNull(myMethod);
+ return ContainerUtil.filter(doInferContracts(), new Condition<MethodContract>() {
+ @Override
+ public boolean value(MethodContract contract) {
+ if (notNull && contract.returnValue == NOT_NULL_VALUE && Arrays.equals(contract.arguments, myEmptyConstraints)) {
+ return false;
+ }
+ return true;
+ }
+ });
+ }
+
+ private List<MethodContract> doInferContracts() {
PsiCodeBlock body = myMethod.getBody();
PsiStatement[] statements = body == null ? PsiStatement.EMPTY_ARRAY : body.getStatements();
if (statements.length == 0) return Collections.emptyList();
@@ -101,8 +117,7 @@ class ContractInferenceInterpreter {
}
}
- ValueConstraint[] emptyState = MethodContract.createConstraintArray(myMethod.getParameterList().getParametersCount());
- return visitStatements(Collections.singletonList(emptyState), statements);
+ return visitStatements(Collections.singletonList(myEmptyConstraints), statements);
}
@Nullable
@@ -130,12 +145,12 @@ class ContractInferenceInterpreter {
return RecursionManager.doPreventingRecursion(myMethod, true, new Computable<List<MethodContract>>() {
@Override
public List<MethodContract> compute() {
- List<MethodContract> delegateContracts = ControlFlowAnalyzer.getMethodContracts(targetMethod);
- return ContainerUtil.mapNotNull(delegateContracts, new NullableFunction<MethodContract, MethodContract>() {
+ final boolean notNull = NullableNotNullManager.isNotNull(targetMethod);
+ List<MethodContract> fromDelegate = ContainerUtil.mapNotNull(ControlFlowAnalyzer.getMethodContracts(targetMethod), new NullableFunction<MethodContract, MethodContract>() {
@Nullable
@Override
public MethodContract fun(MethodContract delegateContract) {
- ValueConstraint[] answer = MethodContract.createConstraintArray(myMethod.getParameterList().getParametersCount());
+ ValueConstraint[] answer = myEmptyConstraints;
for (int i = 0; i < delegateContract.arguments.length; i++) {
if (i >= arguments.length) return null;
@@ -155,9 +170,17 @@ class ContractInferenceInterpreter {
}
}
}
- return answer == null ? null : new MethodContract(answer, negated ? negateConstraint(delegateContract.returnValue) : delegateContract.returnValue);
+ ValueConstraint returnValue = negated ? negateConstraint(delegateContract.returnValue) : delegateContract.returnValue;
+ if (notNull && returnValue != THROW_EXCEPTION) {
+ returnValue = NOT_NULL_VALUE;
+ }
+ return answer == null ? null : new MethodContract(answer, returnValue);
}
});
+ if (notNull) {
+ return ContainerUtil.concat(fromDelegate, Arrays.asList(new MethodContract(myEmptyConstraints, NOT_NULL_VALUE)));
+ }
+ return fromDelegate;
}
});
}
@@ -370,12 +393,6 @@ class ContractInferenceInterpreter {
else if (statement instanceof PsiDoWhileStatement) {
result.addAll(visitStatements(states, ((PsiDoWhileStatement)statement).getBody()));
}
- else if (statement instanceof PsiTryStatement) {
- PsiCodeBlock block = ((PsiTryStatement)statement).getTryBlock();
- if (block != null) {
- result.addAll(visitStatements(states, block.getStatements()));
- }
- }
break; // visit only the first statement unless it's 'if' whose 'then' always returns and the next statement is effectively 'else'
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
index 7539d7195701..92777dd659e0 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
@@ -1434,6 +1434,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
final PsiAnnotation contractAnno = findContractAnnotation(method);
final int paramCount = method.getParameterList().getParametersCount();
if (contractAnno != null) {
+ if (AnnotationUtil.isInferredAnnotation(contractAnno) && PsiUtil.canBeOverriden(method)) {
+ return Collections.emptyList();
+ }
+
return CachedValuesManager.getCachedValue(contractAnno, new CachedValueProvider<List<MethodContract>>() {
@Nullable
@Override
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java
index 36449128b494..96102b53f5d4 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java
@@ -70,7 +70,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValueFactory factory = runner.getFactory();
if (dfaSource instanceof DfaVariableValue && factory.getVarFactory().getAllQualifiedBy(var).contains(dfaSource)) {
- dfaSource = factory.createTypeValue(((DfaVariableValue)dfaSource).getVariableType(), ((DfaVariableValue)dfaSource).getInherentNullability());
+ Nullness nullability = memState.isNotNull(dfaSource) ? Nullness.NOT_NULL
+ : ((DfaVariableValue)dfaSource).getInherentNullability();
+ dfaSource = factory.createTypeValue(((DfaVariableValue)dfaSource).getVariableType(), nullability);
}
if (var.getInherentNullability() == Nullness.NOT_NULL) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java
index 7ebce779c3a8..9b32f8a4c000 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java
@@ -19,6 +19,7 @@ import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
@@ -50,8 +51,7 @@ public class DfaExpressionFactory {
}
catch (Exception e) {
LOG.error(e);
- //noinspection unchecked
- return Condition.FALSE;
+ return Conditions.alwaysFalse();
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/equalsAndHashcode/EqualsAndHashcode.java b/java/java-analysis-impl/src/com/intellij/codeInspection/equalsAndHashcode/EqualsAndHashcodeBase.java
index 4c41d90a9afa..6a5f0627d5e1 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/equalsAndHashcode/EqualsAndHashcode.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/equalsAndHashcode/EqualsAndHashcodeBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,7 @@
*/
package com.intellij.codeInspection.equalsAndHashcode;
-import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
-import com.intellij.codeInspection.InspectionsBundle;
-import com.intellij.codeInspection.LocalQuickFix;
-import com.intellij.codeInspection.ProblemsHolder;
+import com.intellij.codeInspection.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
@@ -36,10 +33,10 @@ import org.jetbrains.annotations.Nullable;
/**
* @author max
*/
-public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool {
+public class EqualsAndHashcodeBase extends BaseJavaBatchLocalInspectionTool {
@Override
@NotNull
- public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
+ public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
final Project project = holder.getProject();
Pair<PsiMethod, PsiMethod> pair = CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider<Pair<PsiMethod, PsiMethod>>() {
@Override
@@ -92,7 +89,7 @@ public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool {
hasEquals[0]
? InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor", "<code>equals()</code>", "<code>hashCode()</code>")
: InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor","<code>hashCode()</code>", "<code>equals()</code>"),
- (LocalQuickFix[])null);
+ buildFixes(isOnTheFly, hasEquals[0]));
}
}
};
@@ -130,4 +127,8 @@ public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool {
public String getShortName() {
return "EqualsAndHashcode";
}
+
+ protected LocalQuickFix[] buildFixes(boolean isOnTheFly, boolean hasEquals) {
+ return LocalQuickFix.EMPTY_ARRAY;
+ }
}