diff options
Diffstat (limited to 'java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java')
-rw-r--r-- | java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java | 260 |
1 files changed, 114 insertions, 146 deletions
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 86b9dd101fd9..23d9d5a9e1c5 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 @@ -15,34 +15,31 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; -import com.intellij.ProjectTopics; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ContentIterator; -import com.intellij.openapi.roots.ModuleRootAdapter; -import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.ModificationTracker; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiFormatUtil; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.LongStack; import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.TIntHashSet; -import gnu.trove.TIntObjectHashMap; +import gnu.trove.TLongArrayList; +import gnu.trove.TLongHashSet; +import gnu.trove.TLongObjectHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.Collection; +import java.util.List; /** * @author lambdamix @@ -50,93 +47,15 @@ import java.util.Collection; 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 int EQUATIONS_LIMIT = 1000; private final Project myProject; - private volatile Annotations myAnnotations = null; - public static ProjectBytecodeAnalysis getInstance(@NotNull Project project) { return ServiceManager.getService(project, ProjectBytecodeAnalysis.class); } public ProjectBytecodeAnalysis(Project project) { myProject = project; - final MessageBusConnection connection = myProject.getMessageBus().connect(); - connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { - @Override - public void rootsChanged(ModuleRootEvent event) { - unloadAnnotations(); - } - }); - } - - private void loadAnnotations() { - Annotations annotations = new Annotations(); - loadParameterAnnotations(annotations); - loadContractAnnotations(annotations); - myAnnotations = annotations; - LOG.debug("NotNull annotations: " + myAnnotations.notNulls.size()); - LOG.debug("Contract annotations: " + myAnnotations.contracts.size()); - } - - private void unloadAnnotations() { - myAnnotations = null; - LOG.debug("unloaded"); - } - - private void loadParameterAnnotations(Annotations annotations) { - LOG.debug("initializing parameter annotations"); - final IntIdSolver solver = new IntIdSolver(new ELattice<Value>(Value.NotNull, Value.Top)); - - processValues(true, new FileBasedIndex.ValueProcessor<Collection<IntIdEquation>>() { - @Override - public boolean process(VirtualFile file, Collection<IntIdEquation> value) { - for (IntIdEquation intIdEquation : value) { - solver.addEquation(intIdEquation); - } - return true; - } - }); - - LOG.debug("parameter equations are constructed"); - LOG.debug("equations: " + solver.getSize()); - TIntObjectHashMap<Value> solutions = solver.solve(); - LOG.debug("parameter equations are solved"); - BytecodeAnalysisConverter.getInstance().addAnnotations(solutions, annotations); - } - - private void processValues(final boolean parameters, final FileBasedIndex.ValueProcessor<Collection<IntIdEquation>> processor) { - final GlobalSearchScope libScope = ProjectScope.getLibrariesScope(myProject); - final FileBasedIndex index = FileBasedIndex.getInstance(); - index.iterateIndexableFiles(new ContentIterator() { - @Override - public boolean processFile(VirtualFile fileOrDir) { - ProgressManager.checkCanceled(); - if (!fileOrDir.isDirectory() && libScope.contains(fileOrDir)) { - index.processValues(BytecodeAnalysisIndex.NAME, BytecodeAnalysisIndex.indexKey(fileOrDir, parameters), - fileOrDir, processor, GlobalSearchScope.fileScope(myProject, fileOrDir)); - } - return false; - } - }, myProject, null); - } - - private void loadContractAnnotations(Annotations annotations) { - LOG.debug("initializing contract annotations"); - final IntIdSolver solver = new IntIdSolver(new ELattice<Value>(Value.Bot, Value.Top)); - processValues(false, new FileBasedIndex.ValueProcessor<Collection<IntIdEquation>>() { - @Override - public boolean process(VirtualFile file, Collection<IntIdEquation> value) { - for (IntIdEquation intIdEquation : value) { - solver.addEquation(intIdEquation); - } - return true; - } - }); - LOG.debug("contract equations are constructed"); - LOG.debug("equations: " + solver.getSize()); - TIntObjectHashMap<Value> solutions = solver.solve(); - LOG.debug("contract equations are solved"); - BytecodeAnalysisConverter.getInstance().addAnnotations(solutions, annotations); } @Nullable @@ -144,11 +63,14 @@ public class ProjectBytecodeAnalysis { if (!(listOwner instanceof PsiCompiledElement)) { return null; } - if (annotationFQN.equals("org.jetbrains.annotations.NotNull")) { - return findNotNullAnnotation(listOwner); - } - else if (annotationFQN.equals("org.jetbrains.annotations.Contract")) { - return findContractAnnotation(listOwner); + if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { + PsiAnnotation[] annotations = findInferredAnnotations(listOwner); + for (PsiAnnotation annotation : annotations) { + if (annotationFQN.equals(annotation.getQualifiedName())) { + return annotation; + } + } + return null; } else { return null; @@ -156,26 +78,30 @@ public class ProjectBytecodeAnalysis { } @NotNull - public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) { + public PsiAnnotation[] findInferredAnnotations(@NotNull final PsiModifierListOwner listOwner) { if (!(listOwner instanceof PsiCompiledElement)) { return PsiAnnotation.EMPTY_ARRAY; } - return collectInferredAnnotations(listOwner); + return CachedValuesManager.getCachedValue(listOwner, new CachedValueProvider<PsiAnnotation[]>() { + @Nullable + @Override + public Result<PsiAnnotation[]> compute() { + return Result.create(collectInferredAnnotations(listOwner), listOwner); + } + }); } - // TODO the best way to synchronize? @NotNull - private synchronized PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) { - if (myAnnotations == null) { - loadAnnotations(); - } + private PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) { try { - int key = getKey(listOwner); - if (key == -1) { + long ownerKey = getKey(listOwner); + if (ownerKey == -1) { return PsiAnnotation.EMPTY_ARRAY; } - boolean notNull = myAnnotations.notNulls.contains(key); - String contractValue = myAnnotations.contracts.get(key); + TLongArrayList allKeys = contractKeys(listOwner, ownerKey); + Annotations annotations = loadAnnotations(listOwner, ownerKey, allKeys); + boolean notNull = annotations.notNulls.contains(ownerKey); + String contractValue = annotations.contracts.get(ownerKey); if (notNull && contractValue != null) { return new PsiAnnotation[]{ @@ -201,6 +127,11 @@ public class ProjectBytecodeAnalysis { LOG.debug(e); return PsiAnnotation.EMPTY_ARRAY; } + catch (EquationsLimitException e) { + String externalName = PsiFormatUtil.getExternalName(listOwner, false, Integer.MAX_VALUE); + LOG.info("Too many equations for " + externalName); + return PsiAnnotation.EMPTY_ARRAY; + } } private PsiAnnotation getNotNullAnnotation() { @@ -213,54 +144,15 @@ public class ProjectBytecodeAnalysis { }); } - @Nullable - private synchronized PsiAnnotation findNotNullAnnotation(PsiModifierListOwner listOwner) { - if (myAnnotations == null) { - loadAnnotations(); - } - try { - int key = getKey(listOwner); - if (key == -1) { - return null; - } - return myAnnotations.notNulls.contains(key) ? getNotNullAnnotation() : null; - } - catch (IOException e) { - LOG.debug(e); - return null; - } - } - - @Nullable - private synchronized PsiAnnotation findContractAnnotation(PsiModifierListOwner listOwner) { - if (myAnnotations == null) { - loadAnnotations(); - } - try { - int key = getKey(listOwner); - if (key == -1) { - return null; - } - String contractValue = myAnnotations.contracts.get(key); - return contractValue != null ? createContractAnnotation(contractValue) : null; - } - catch (IOException e) { - LOG.debug(e); - return null; - } - } - public PsiAnnotation createContractAnnotation(String contractValue) { return createAnnotationFromText("@org.jetbrains.annotations.Contract(" + contractValue + ")"); } - public static int getKey(@NotNull PsiModifierListOwner owner) throws IOException { + public static long getKey(@NotNull PsiModifierListOwner owner) throws IOException { LOG.assertTrue(owner instanceof PsiCompiledElement, owner); - if (owner instanceof PsiMethod) { return BytecodeAnalysisConverter.getInstance().mkPsiKey((PsiMethod)owner, new Out()); } - if (owner instanceof PsiParameter) { PsiElement parent = owner.getParent(); if (parent instanceof PsiParameterList) { @@ -275,6 +167,80 @@ public class ProjectBytecodeAnalysis { return -1; } + public static TLongArrayList contractKeys(@NotNull PsiModifierListOwner owner, long primaryKey) throws IOException { + if (owner instanceof PsiMethod) { + TLongArrayList result = BytecodeAnalysisConverter.getInstance().mkInOutKeys((PsiMethod)owner, primaryKey); + result.add(primaryKey); + return result; + } + TLongArrayList result = new TLongArrayList(1); + result.add(primaryKey); + return result; + } + + private Annotations loadAnnotations(@NotNull PsiModifierListOwner owner, long key, TLongArrayList allKeys) + throws IOException, EquationsLimitException { + Annotations result = new Annotations(); + if (owner instanceof PsiParameter) { + final Solver solver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top)); + collectEquations(allKeys, solver); + TLongObjectHashMap<Value> solutions = solver.solve(); + BytecodeAnalysisConverter.getInstance().addParameterAnnotations(solutions, result); + } else if (owner instanceof PsiMethod) { + final Solver solver = new Solver(new ELattice<Value>(Value.Bot, Value.Top)); + collectEquations(allKeys, solver); + TLongObjectHashMap<Value> solutions = solver.solve(); + BytecodeAnalysisConverter.getInstance().addMethodAnnotations(solutions, result, key, + ((PsiMethod)owner).getParameterList().getParameters().length); + } + return result; + } + + private void collectEquations(TLongArrayList keys, Solver solver) throws EquationsLimitException { + GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject); + TLongHashSet queued = new TLongHashSet(); + LongStack queue = new LongStack(); + + for (int i = 0; i < keys.size(); i++) { + long key = keys.get(i); + queue.push(key); + queued.add(key); + // stable/unstable + queue.push(-key); + queued.add(-key); + } + + FileBasedIndex index = FileBasedIndex.getInstance(); + while (!queue.empty()) { + if (queued.size() > EQUATIONS_LIMIT) { + throw new EquationsLimitException(); + } + ProgressManager.checkCanceled(); + List<IdEquation> equations = index.getValues(BytecodeAnalysisIndex.NAME, queue.pop(), librariesScope); + for (IdEquation equation : equations) { + IdResult rhs = equation.rhs; + solver.addEquation(equation); + if (rhs instanceof IdPending) { + IdPending intIdPending = (IdPending)rhs; + for (IntIdComponent component : intIdPending.delta) { + for (long depKey : component.ids) { + if (!queued.contains(depKey)) { + queue.push(depKey); + queued.add(depKey); + } + // stable/unstable + long swapped = -depKey; + if (!queued.contains(swapped)) { + queue.push(swapped); + queued.add(swapped); + } + } + } + } + } + } + } + @NotNull private PsiAnnotation createAnnotationFromText(@NotNull final String text) throws IncorrectOperationException { PsiAnnotation annotation = JavaPsiFacade.getElementFactory(myProject).createAnnotationFromText(text, null); @@ -285,7 +251,9 @@ public class ProjectBytecodeAnalysis { class Annotations { // @NotNull keys - final TIntHashSet notNulls = new TIntHashSet(); + final TLongHashSet notNulls = new TLongHashSet(); // @Contracts - final TIntObjectHashMap<String> contracts = new TIntObjectHashMap<String>(); + final TLongObjectHashMap<String> contracts = new TLongObjectHashMap<String>(); } + +class EquationsLimitException extends Exception {} |