diff options
Diffstat (limited to 'java/java-analysis-impl/src')
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; + } } |