From 016bb9866b1c4a45624d5f7a5a5f13cc07556c49 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 30 May 2017 11:27:53 +0700 Subject: [PATCH 01/19] BytecodeAnalysisIntegrationTest: fast XML export --- .../ExternalAnnotationsManagerImpl.java | 11 +- .../BytecodeAnalysisIntegrationTest.java | 210 +++++++++++------- 2 files changed, 139 insertions(+), 82 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java index 366c0c27bf72..c6c6cecf67c7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java @@ -679,7 +679,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM @NonNls @NotNull - private static String createAnnotationTag(@NotNull String annotationFQName, @Nullable PsiNameValuePair[] values) { + public static String createAnnotationTag(@NotNull String annotationFQName, @Nullable PsiNameValuePair[] values) { @NonNls String text; if (values != null && values.length != 0) { text = " \n"; @@ -696,6 +696,11 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM @Nullable private XmlFile createAnnotationsXml(@NotNull VirtualFile root, @NonNls @NotNull String packageName) { + return createAnnotationsXml(root, packageName, myPsiManager); + } + + @Nullable + public static XmlFile createAnnotationsXml(@NotNull VirtualFile root, @NonNls @NotNull String packageName, PsiManager manager) { final String[] dirs = packageName.split("[\\.]"); for (String dir : dirs) { if (dir.isEmpty()) break; @@ -710,7 +715,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM } root = subdir; } - final PsiDirectory directory = myPsiManager.findDirectory(root); + final PsiDirectory directory = manager.findDirectory(root); if (directory == null) return null; final PsiFile psiFile = directory.findFile(ANNOTATIONS_XML); @@ -719,7 +724,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM } try { - final PsiFileFactory factory = PsiFileFactory.getInstance(myPsiManager.getProject()); + final PsiFileFactory factory = PsiFileFactory.getInstance(manager.getProject()); return (XmlFile)directory.add(factory.createFileFromText(ANNOTATIONS_XML, XmlFileType.INSTANCE, "")); } catch (IncorrectOperationException e) { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisIntegrationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisIntegrationTest.java index d5dd19737564..2b53915fe04f 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisIntegrationTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisIntegrationTest.java @@ -17,10 +17,15 @@ package com.intellij.java.codeInspection.bytecodeAnalysis; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.ExternalAnnotationsManager; +import com.intellij.codeInsight.ExternalAnnotationsManagerImpl; import com.intellij.codeInsight.daemon.GutterMark; import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter; import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis; +import com.intellij.openapi.application.Result; import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkModificator; import com.intellij.openapi.roots.AnnotationOrderRootType; @@ -38,19 +43,21 @@ import com.intellij.openapi.vfs.VirtualFileVisitor; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiFormatUtil; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; import com.intellij.testFramework.PsiTestUtil; import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; import com.intellij.util.ArrayUtil; import com.intellij.util.AsynchConsumer; import com.intellij.util.containers.ContainerUtil; +import one.util.streamex.EntryStream; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.java.decompiler.IdeaDecompiler; import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; /** * @author lambdamix @@ -61,6 +68,7 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC private MessageDigest myMessageDigest; private final List myDiffs = new ArrayList<>(); private boolean myNullableMethodRegistryValue; + private VirtualFile myAnnotationsDir; @Override protected void setUp() throws Exception { @@ -88,8 +96,8 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC private void setUpExternalUpAnnotations() { String annotationsPath = PathManagerEx.getTestDataPath() + "/codeInspection/bytecodeAnalysis/annotations"; - VirtualFile annotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath); - assertNotNull(annotationsDir); + myAnnotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath); + assertNotNull(myAnnotationsDir); ModuleRootModificationUtil.updateModel(myModule, new AsynchConsumer() { @Override @@ -101,7 +109,7 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC Library[] libs = libraryTable.getLibraries(); for (Library library : libs) { Library.ModifiableModel libraryModel = library.getModifiableModel(); - libraryModel.addRoot(annotationsDir, AnnotationOrderRootType.getInstance()); + libraryModel.addRoot(myAnnotationsDir, AnnotationOrderRootType.getInstance()); libraryModel.commit(); } Sdk sdk = modifiableRootModel.getSdk(); @@ -114,15 +122,16 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC throw new RuntimeException(e); } SdkModificator sdkModificator = clone.getSdkModificator(); - sdkModificator.addRoot(annotationsDir, AnnotationOrderRootType.getInstance()); + sdkModificator.addRoot(myAnnotationsDir, AnnotationOrderRootType.getInstance()); sdkModificator.commitChanges(); modifiableRootModel.setSdk(clone); } } }); - VfsUtilCore.visitChildrenRecursively(annotationsDir, new VirtualFileVisitor() { }); - annotationsDir.refresh(false, true); + VfsUtilCore.visitChildrenRecursively(myAnnotationsDir, new VirtualFileVisitor() { + }); + myAnnotationsDir.refresh(false, true); } private void openDecompiledClass(String name) { @@ -198,80 +207,11 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC assert rootPackage != null; GlobalSearchScope scope = GlobalSearchScope.allScope(getProject()); - JavaRecursiveElementVisitor visitor = new JavaRecursiveElementVisitor() { - @Override - public void visitPackage(PsiPackage aPackage) { - // annotations are in class paths, but we are not interested in inferred annotations for them - if ("org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) { - return; - } - for (PsiPackage subPackage : aPackage.getSubPackages(scope)) { - visitPackage(subPackage); - } - for (PsiClass aClass : aPackage.getClasses(scope)) { - processClass(aClass); - for (PsiClass innerClass : aClass.getInnerClasses()) { - processClass(innerClass); - } - } - } - - private void processClass(PsiClass aClass) { - for (PsiMethod method : aClass.getMethods()) { - exportMethodAnnotations(method); - } - for (PsiClass innerClass : aClass.getInnerClasses()) { - processClass(innerClass); - } - } - }; + JavaRecursiveElementVisitor visitor = new AnnotationExporter(scope); rootPackage.accept(visitor); } - private void exportMethodAnnotations(PsiMethod method) { - // @Contract - PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT); - if (inferredContractAnnotation != null) { - PsiNameValuePair[] attributes = inferredContractAnnotation.getParameterList().getAttributes(); - ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT, method.getContainingFile(), attributes); - } - - { - // @NotNull method - PsiAnnotation inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL); - if (inferredNotNullMethodAnnotation != null) { - ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, AnnotationUtil.NOT_NULL, method.getContainingFile(), null); - } - } - - { - // @Nullable method - PsiAnnotation inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE); - if (inferredNullableMethodAnnotation != null) { - ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, AnnotationUtil.NULLABLE, method.getContainingFile(), null); - } - } - - for (PsiParameter parameter : method.getParameterList().getParameters()) { - { - // @NotNull parameter - PsiAnnotation inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL); - if (inferredNotNull != null) { - ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(parameter, AnnotationUtil.NOT_NULL, method.getContainingFile(), null); - } - } - - { - // @Nullable parameter - PsiAnnotation inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE); - if (inferredNullable != null) { - ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(parameter, AnnotationUtil.NULLABLE, method.getContainingFile(), null); - } - } - } - } - private void checkMethodAnnotations(PsiMethod method) { if (ProjectBytecodeAnalysis.getKey(method, myMessageDigest) == null) { return; @@ -344,4 +284,116 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC private PsiAnnotation findExternalAnnotation(PsiModifierListOwner owner, String fqn) { return ExternalAnnotationsManager.getInstance(myModule.getProject()).findExternalAnnotation(owner, fqn); } + + private class AnnotationExporter extends JavaRecursiveElementVisitor { + private final GlobalSearchScope myScope; + + public AnnotationExporter(GlobalSearchScope scope) {myScope = scope;} + + @Override + public void visitPackage(PsiPackage aPackage) { + // annotations are in class paths, but we are not interested in inferred annotations for them + if ("org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) { + return; + } + for (PsiPackage subPackage : aPackage.getSubPackages(myScope)) { + visitPackage(subPackage); + } + Map> packageAnnotations = new TreeMap<>(); + for (PsiClass aClass : aPackage.getClasses(myScope)) { + processClass(aClass, packageAnnotations); + } + saveXmlForPackage(myAnnotationsDir, aPackage, convertToXml(packageAnnotations)); + } + + private void saveXmlForPackage(VirtualFile root, PsiPackage aPackage, XmlTag newContent) { + XmlTag[] tags = newContent.getSubTags(); + if (tags.length == 0) return; + new WriteCommandAction(getProject()) { + @Override + protected void run(@NotNull Result result) throws Throwable { + XmlFile xml = ExternalAnnotationsManagerImpl.createAnnotationsXml(root, aPackage.getQualifiedName(), aPackage.getManager()); + if (xml == null) { + throw new IllegalStateException("Unable to get XML for package " + aPackage.getQualifiedName() + "; root = " + root); + } + XmlTag rootTag = xml.getRootTag(); + if (rootTag == null) { + throw new IllegalStateException("No root tag in " + xml); + } + XmlTag[] existingItems = rootTag.getSubTags(); + if (existingItems.length > 0) { + rootTag.deleteChildRange(ArrayUtil.getFirstElement(existingItems), ArrayUtil.getLastElement(existingItems)); + } + rootTag.collapseIfEmpty(); + for (XmlTag item : tags) { + rootTag.addSubTag(item, false); + } + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(xml.getProject()); + Document doc = documentManager.getDocument(xml); + documentManager.doPostponedOperationsAndUnblockDocument(doc); + FileDocumentManager.getInstance().saveDocument(doc); + } + }.execute(); + } + + @NotNull + private XmlTag convertToXml(Map> annotations) { + String xmlContent = EntryStream.of(annotations) + .mapValues(map -> EntryStream.of(map).mapKeyValue(ExternalAnnotationsManagerImpl::createAnnotationTag).joining()) + .mapKeyValue((externalName, content) -> "\n" + content + "") + .joining("", "\n", "\n"); + XmlElementFactory factory = XmlElementFactory.getInstance(getProject()); + return factory.createTagFromText(xmlContent); + } + + private void processClass(PsiClass aClass, Map> packageAnnotations) { + for (PsiMethod method : aClass.getMethods()) { + annotateMethod(method, packageAnnotations); + } + for (PsiClass innerClass : aClass.getInnerClasses()) { + processClass(innerClass, packageAnnotations); + } + } + + private void annotateMethod(PsiMethod method, Map> packageAnnotations) { + // @Contract + PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT); + if (inferredContractAnnotation != null) { + PsiNameValuePair[] attributes = inferredContractAnnotation.getParameterList().getAttributes(); + annotate(packageAnnotations, method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT, attributes); + } + + // @NotNull method + PsiAnnotation inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL); + if (inferredNotNullMethodAnnotation != null) { + annotate(packageAnnotations, method, AnnotationUtil.NOT_NULL, PsiNameValuePair.EMPTY_ARRAY); + } + // @Nullable method + PsiAnnotation inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE); + if (inferredNullableMethodAnnotation != null) { + annotate(packageAnnotations, method, AnnotationUtil.NULLABLE, PsiNameValuePair.EMPTY_ARRAY); + } + + for (PsiParameter parameter : method.getParameterList().getParameters()) { + // @NotNull parameter + PsiAnnotation inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL); + if (inferredNotNull != null) { + annotate(packageAnnotations, parameter, AnnotationUtil.NOT_NULL, PsiNameValuePair.EMPTY_ARRAY); + } + // @Nullable parameter + PsiAnnotation inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE); + if (inferredNullable != null) { + annotate(packageAnnotations, parameter, AnnotationUtil.NULLABLE, PsiNameValuePair.EMPTY_ARRAY); + } + } + } + + private void annotate(Map> packageAnnotations, + PsiModifierListOwner owner, + String annotationFQN, + PsiNameValuePair[] attributes) { + packageAnnotations.computeIfAbsent(PsiFormatUtil.getExternalName(owner, false, Integer.MAX_VALUE), + k -> new TreeMap<>()).put(annotationFQN, attributes); + } + } } From 97eaee737b5a32efab219913eaaed9f249958a14 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 30 May 2017 13:16:31 +0700 Subject: [PATCH 02/19] BytecodeAnalysis refactoring: HKey and Key merged to EKey; hashing is encapsulated inside HMethod/Method pair --- .../bytecodeAnalysis/Analysis.java | 12 +- .../BytecodeAnalysisConverter.java | 110 ++------ .../BytecodeAnalysisIndex.java | 119 ++++---- .../bytecodeAnalysis/ClassDataIndexer.java | 80 +++--- .../bytecodeAnalysis/Combined.java | 68 ++--- .../bytecodeAnalysis/Contracts.java | 28 +- .../{HData.java => Data.java} | 182 +++++------- .../bytecodeAnalysis/Direction.java | 20 +- .../codeInspection/bytecodeAnalysis/EKey.java | 107 +++++++ .../codeInspection/bytecodeAnalysis/HKey.java | 99 ------- .../{Key.java => HMethod.java} | 53 ++-- .../bytecodeAnalysis/KeyedMethodVisitor.java | 4 +- .../bytecodeAnalysis/Method.java | 25 +- .../bytecodeAnalysis/MethodDescriptor.java | 36 +++ .../NullableMethodAnalysis.java | 14 +- .../bytecodeAnalysis/Parameters.java | 44 +-- .../ProjectBytecodeAnalysis.java | 114 ++++---- .../bytecodeAnalysis/PurityAnalysis.java | 191 ++++--------- .../bytecodeAnalysis/Solver.java | 264 +++++------------- .../annotations/java/io/annotations.xml | 1 - .../java/lang/invoke/annotations.xml | 5 - .../annotations/java/net/annotations.xml | 25 -- .../annotations/java/util/annotations.xml | 10 - .../org/apache/commons/lang/annotations.xml | 20 -- .../apache/commons/lang/math/annotations.xml | 12 +- .../app/event/implement/annotations.xml | 5 - .../apache/velocity/app/tools/annotations.xml | 5 - .../apache/velocity/convert/annotations.xml | 5 - .../velocity/runtime/parser/annotations.xml | 5 - .../org/apache/velocity/util/annotations.xml | 5 - .../BytecodeAnalysisTest.java | 4 +- 31 files changed, 662 insertions(+), 1010 deletions(-) rename java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/{HData.java => Data.java} (51%) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/EKey.java delete mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HKey.java rename java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/{Key.java => HMethod.java} (50%) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/MethodDescriptor.java 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 6d3a78653ab0..60a30072c1cb 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 @@ -69,8 +69,8 @@ class AbstractValues { } } static final class CallResultValue extends BasicValue { - final Set inters; - CallResultValue(Type tp, Set inters) { + final Set inters; + CallResultValue(Type tp, Set inters) { super(tp); this.inters = inters; } @@ -161,8 +161,8 @@ class AbstractValues { static boolean equiv(BasicValue curr, BasicValue prev) { if (curr.getClass() == prev.getClass()) { if (curr instanceof CallResultValue && prev instanceof CallResultValue) { - Set keys1 = ((CallResultValue)prev).inters; - Set keys2 = ((CallResultValue)curr).inters; + Set keys1 = ((CallResultValue)prev).inters; + Set keys2 = ((CallResultValue)curr).inters; return keys1.equals(keys2); } else return true; @@ -220,7 +220,7 @@ abstract class Analysis { final DFSTree dfsTree; final protected List[] computed; - final Key aKey; + final EKey aKey; Res earlyResult; @@ -231,7 +231,7 @@ abstract class Analysis { methodNode = controlFlow.methodNode; method = new Method(controlFlow.className, methodNode.name, methodNode.desc); dfsTree = richControlFlow.dfsTree; - aKey = new Key(method, direction, stable); + aKey = new EKey(method, direction, stable); computed = (List[]) new List[controlFlow.transitions.length]; } 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 d6cedf2d82c2..3c1b97b43b50 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 @@ -17,7 +17,6 @@ package com.intellij.codeInspection.bytecodeAnalysis; import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint; import com.intellij.codeInspection.dataFlow.StandardMethodContract; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.ThreadLocalCachedValue; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.psi.*; @@ -66,80 +65,11 @@ public class BytecodeAnalysisConverter { } /** - * Converts an equation over asm keys into equation over small hash keys. - */ - @NotNull - static DirectionResultPair convert(@NotNull Equation equation, @NotNull MessageDigest md) { - ProgressManager.checkCanceled(); - - Result rhs = equation.rhs; - HResult hResult; - if (rhs instanceof Final) { - hResult = new HFinal(((Final)rhs).value); - } - else if (rhs instanceof Pending) { - Pending pending = (Pending)rhs; - Set sumOrigin = pending.sum; - HComponent[] components = new HComponent[sumOrigin.size()]; - int componentI = 0; - for (Product prod : sumOrigin) { - HKey[] intProd = new HKey[prod.ids.size()]; - int idI = 0; - for (Key key : prod.ids) { - intProd[idI] = asmKey(key, md); - idI++; - } - HComponent intIdComponent = new HComponent(prod.value, intProd); - components[componentI] = intIdComponent; - componentI++; - } - hResult = new HPending(components); - } else { - Effects wrapper = (Effects)rhs; - Set effects = wrapper.effects; - Set hEffects = new HashSet<>(); - for (EffectQuantum effect : effects) { - if (effect == EffectQuantum.TopEffectQuantum) { - hEffects.add(HEffectQuantum.TopEffectQuantum); - } - else if (effect == EffectQuantum.ThisChangeQuantum) { - hEffects.add(HEffectQuantum.ThisChangeQuantum); - } - else if (effect instanceof EffectQuantum.ParamChangeQuantum) { - EffectQuantum.ParamChangeQuantum paramChangeQuantum = (EffectQuantum.ParamChangeQuantum)effect; - hEffects.add(new HEffectQuantum.ParamChangeQuantum(paramChangeQuantum.n)); - } - else if (effect instanceof EffectQuantum.CallQuantum) { - EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect; - hEffects.add(new HEffectQuantum.CallQuantum(asmKey(callQuantum.key, md), callQuantum.data, callQuantum.isStatic)); - } - } - hResult = new HEffects(hEffects); - } - return new DirectionResultPair(equation.id.direction.asInt(), hResult); - } - - /** - * Converts an asm method key to a small hash key (HKey) - */ - @NotNull - public static HKey asmKey(@NotNull Key key, @NotNull MessageDigest md) { - byte[] classDigest = md.digest(key.method.internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET)); - md.update(key.method.methodName.getBytes(CharsetToolkit.UTF8_CHARSET)); - md.update(key.method.methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET)); - byte[] sigDigest = md.digest(); - byte[] digest = new byte[HASH_SIZE]; - System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE); - System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE); - return new HKey(digest, key.direction.asInt(), key.stable, key.negated); - } - - /** - * Converts a Psi method to a small hash key (HKey). + * Converts a Psi method to a small hash key (Key). * Returns null if conversion is impossible (something is not resolvable). */ @Nullable - public static HKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) { + public static EKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) { final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false); if (psiClass == null) { return null; @@ -155,7 +85,7 @@ public class BytecodeAnalysisConverter { byte[] digest = new byte[HASH_SIZE]; System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE); System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE); - return new HKey(digest, direction.asInt(), true, false); + return new EKey(new HMethod(digest), direction, true, false); } @Nullable @@ -317,16 +247,16 @@ public class BytecodeAnalysisConverter { /** - * Given a PSI method and its primary HKey enumerate all contract keys for it. + * Given a PSI method and its primary Key enumerate all contract keys for it. * * @param psiMethod psi method * @param primaryKey primary stable keys * @return corresponding (stable!) keys */ @NotNull - public static ArrayList mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull HKey primaryKey) { + public static ArrayList mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull EKey primaryKey) { PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); - ArrayList keys = new ArrayList<>(parameters.length * 2 + 2); + ArrayList keys = new ArrayList<>(parameters.length * 2 + 2); keys.add(primaryKey); for (int i = 0; i < parameters.length; i++) { if (!(parameters[i].getType() instanceof PsiPrimitiveType)) { @@ -352,21 +282,21 @@ public class BytecodeAnalysisConverter { * @param methodKey a primary key of a method being analyzed. not it is stable * @param arity arity of this method (hint for constructing @Contract annotations) */ - public static void addMethodAnnotations(@NotNull Map solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) { + public static void addMethodAnnotations(@NotNull Map solution, @NotNull MethodAnnotations methodAnnotations, @NotNull EKey methodKey, int arity) { List contractClauses = new ArrayList<>(); - Set notNulls = methodAnnotations.notNulls; - Set pures = methodAnnotations.pures; - Map contracts = methodAnnotations.contractsValues; + Set notNulls = methodAnnotations.notNulls; + Set pures = methodAnnotations.pures; + Map contracts = methodAnnotations.contractsValues; - for (Map.Entry entry : solution.entrySet()) { + for (Map.Entry entry : solution.entrySet()) { // NB: keys from Psi are always stable, so we need to stabilize keys from equations Value value = entry.getValue(); if (value == Value.Top || value == Value.Bot || (value == Value.Fail && !pures.contains(methodKey))) { continue; } - HKey key = entry.getKey().mkStable(); + EKey key = entry.getKey().mkStable(); Direction direction = key.getDirection(); - HKey baseKey = key.mkBase(); + EKey baseKey = key.mkBase(); if (!methodKey.equals(baseKey)) { continue; } @@ -433,18 +363,18 @@ public class BytecodeAnalysisConverter { return contractClauses; } - public static void addEffectAnnotations(Map> puritySolutions, + public static void addEffectAnnotations(Map> puritySolutions, MethodAnnotations result, - HKey methodKey, + EKey methodKey, boolean constructor) { - for (Map.Entry> entry : puritySolutions.entrySet()) { - Set effects = entry.getValue(); - HKey key = entry.getKey().mkStable(); - HKey baseKey = key.mkBase(); + for (Map.Entry> entry : puritySolutions.entrySet()) { + Set effects = entry.getValue(); + EKey key = entry.getKey().mkStable(); + EKey baseKey = key.mkBase(); if (!methodKey.equals(baseKey)) { continue; } - if (effects.isEmpty() || (constructor && effects.size() == 1 && effects.contains(HEffectQuantum.ThisChangeQuantum))) { + if (effects.isEmpty() || (constructor && effects.size() == 1 && effects.contains(EffectQuantum.ThisChangeQuantum))) { // Pure constructor is allowed to change "this" object as this is a new object anyways result.pures.add(methodKey); } 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 ed2ae157cabe..a4665a48fa8b 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 @@ -49,21 +49,21 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys /** * @author lambdamix */ -public class BytecodeAnalysisIndex extends ScalarIndexExtension { - private static final ID NAME = ID.create("bytecodeAnalysis"); +public class BytecodeAnalysisIndex extends ScalarIndexExtension { + private static final ID NAME = ID.create("bytecodeAnalysis"); private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor(); - private static final VirtualFileGist> ourGist = GistManager.getInstance().newVirtualFileGist( - "BytecodeAnalysisIndex", 5, new HEquationsExternalizer(), new ClassDataIndexer()); + private static final VirtualFileGist> ourGist = GistManager.getInstance().newVirtualFileGist( + "BytecodeAnalysisIndex", 7, new EquationsExternalizer(), new ClassDataIndexer()); @NotNull @Override - public ID getName() { + public ID getName() { return NAME; } @NotNull @Override - public DataIndexer getIndexer() { + public DataIndexer getIndexer() { return inputData -> { try { return collectKeys(inputData.getContent()); @@ -81,14 +81,14 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { } @NotNull - private static Map collectKeys(byte[] content) throws NoSuchAlgorithmException { - HashMap map = new HashMap<>(); + private static Map collectKeys(byte[] content) throws NoSuchAlgorithmException { + HashMap map = new HashMap<>(); MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); new ClassReader(content).accept(new KeyedMethodVisitor() { @Nullable @Override - MethodVisitor visitMethod(MethodNode node, Key key) { - map.put(ClassDataIndexer.compressKey(md, key), null); + MethodVisitor visitMethod(MethodNode node, Method method, EKey key) { + map.put(method.hashed(md), null); return null; } }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); @@ -97,7 +97,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { @NotNull @Override - public KeyDescriptor getKeyDescriptor() { + public KeyDescriptor getKeyDescriptor() { return KEY_DESCRIPTOR; } @@ -123,7 +123,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { } @NotNull - static List getEquations(GlobalSearchScope scope, Bytes key) { + static List getEquations(GlobalSearchScope scope, HMethod key) { Project project = ProjectManager.getInstance().getDefaultProject(); // the data is project-independent return ContainerUtil.mapNotNull(FileBasedIndex.getInstance().getContainingFiles(NAME, key, scope), file -> ourGist.getFileData(project, file).get(key)); @@ -132,37 +132,37 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { /** * Externalizer for primary method keys. */ - private static class HKeyDescriptor implements KeyDescriptor, DifferentSerializableBytesImplyNonEqualityPolicy { + private static class HKeyDescriptor implements KeyDescriptor, DifferentSerializableBytesImplyNonEqualityPolicy { @Override - public void save(@NotNull DataOutput out, Bytes value) throws IOException { - out.write(value.bytes); + public void save(@NotNull DataOutput out, HMethod value) throws IOException { + out.write(value.myBytes); } @Override - public Bytes read(@NotNull DataInput in) throws IOException { + public HMethod read(@NotNull DataInput in) throws IOException { byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE]; in.readFully(bytes); - return new Bytes(bytes); + return new HMethod(bytes); } @Override - public int getHashCode(Bytes value) { - return Arrays.hashCode(value.bytes); + public int getHashCode(HMethod value) { + return value.hashCode(); } @Override - public boolean isEqual(Bytes val1, Bytes val2) { - return Arrays.equals(val1.bytes, val2.bytes); + public boolean isEqual(HMethod val1, HMethod val2) { + return val1.equals(val2); } } /** * Externalizer for compressed equations. */ - public static class HEquationsExternalizer implements DataExternalizer> { + public static class EquationsExternalizer implements DataExternalizer> { @Override - public void save(@NotNull DataOutput out, Map value) throws IOException { + public void save(@NotNull DataOutput out, Map value) throws IOException { DataInputOutputUtilRt.writeSeq(out, value.entrySet(), entry -> { KEY_DESCRIPTOR.save(out, entry.getKey()); saveEquations(out, entry.getValue()); @@ -170,53 +170,54 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { } @Override - public Map read(@NotNull DataInput in) throws IOException { + public Map read(@NotNull DataInput in) throws IOException { return DataInputOutputUtilRt.readSeq(in, () -> Pair.create(KEY_DESCRIPTOR.read(in), readEquations(in))). stream().collect(Collectors.toMap(p -> p.getFirst(), p -> p.getSecond())); } - private static void saveEquations(@NotNull DataOutput out, HEquations eqs) throws IOException { + private static void saveEquations(@NotNull DataOutput out, Equations eqs) throws IOException { out.writeBoolean(eqs.stable); + MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); DataInputOutputUtil.writeINT(out, eqs.results.size()); for (DirectionResultPair pair : eqs.results) { DataInputOutputUtil.writeINT(out, pair.directionKey); - HResult rhs = pair.hResult; - if (rhs instanceof HFinal) { - HFinal finalResult = (HFinal)rhs; + Result rhs = pair.hResult; + if (rhs instanceof Final) { + Final finalResult = (Final)rhs; out.writeBoolean(true); // final flag DataInputOutputUtil.writeINT(out, finalResult.value.ordinal()); } - else if (rhs instanceof HPending) { - HPending pendResult = (HPending)rhs; + else if (rhs instanceof Pending) { + Pending pendResult = (Pending)rhs; out.writeBoolean(false); // pending flag DataInputOutputUtil.writeINT(out, pendResult.delta.length); - for (HComponent component : pendResult.delta) { + for (Component component : pendResult.delta) { DataInputOutputUtil.writeINT(out, component.value.ordinal()); - HKey[] ids = component.ids; + EKey[] ids = component.ids; DataInputOutputUtil.writeINT(out, ids.length); - for (HKey hKey : ids) { - out.write(hKey.key); + for (EKey hKey : ids) { + out.write(hKey.method.hashed(md).myBytes); int rawDirKey = hKey.negated ? -hKey.dirKey : hKey.dirKey; DataInputOutputUtil.writeINT(out, rawDirKey); out.writeBoolean(hKey.stable); } } } - else if (rhs instanceof HEffects) { - HEffects effects = (HEffects)rhs; + else if (rhs instanceof Effects) { + Effects effects = (Effects)rhs; DataInputOutputUtil.writeINT(out, effects.effects.size()); - for (HEffectQuantum effect : effects.effects) { - if (effect == HEffectQuantum.TopEffectQuantum) { + for (EffectQuantum effect : effects.effects) { + if (effect == EffectQuantum.TopEffectQuantum) { DataInputOutputUtil.writeINT(out, -1); } - else if (effect == HEffectQuantum.ThisChangeQuantum) { + else if (effect == EffectQuantum.ThisChangeQuantum) { DataInputOutputUtil.writeINT(out, -2); } - else if (effect instanceof HEffectQuantum.CallQuantum) { + else if (effect instanceof EffectQuantum.CallQuantum) { DataInputOutputUtil.writeINT(out, -3); - HEffectQuantum.CallQuantum callQuantum = (HEffectQuantum.CallQuantum)effect; - out.write(callQuantum.key.key); + EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect; + out.write(callQuantum.key.method.hashed(md).myBytes); DataInputOutputUtil.writeINT(out, callQuantum.key.dirKey); out.writeBoolean(callQuantum.key.stable); out.writeBoolean(callQuantum.isStatic); @@ -242,15 +243,15 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { } } } - else if (effect instanceof HEffectQuantum.ParamChangeQuantum) { - DataInputOutputUtil.writeINT(out, ((HEffectQuantum.ParamChangeQuantum)effect).n); + else if (effect instanceof EffectQuantum.ParamChangeQuantum) { + DataInputOutputUtil.writeINT(out, ((EffectQuantum.ParamChangeQuantum)effect).n); } } } } } - private static HEquations readEquations(@NotNull DataInput in) throws IOException { + private static Equations readEquations(@NotNull DataInput in) throws IOException { boolean stable = in.readBoolean(); int size = DataInputOutputUtil.readINT(in); ArrayList results = new ArrayList<>(size); @@ -258,22 +259,22 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { int directionKey = DataInputOutputUtil.readINT(in); Direction direction = Direction.fromInt(directionKey); if (direction == Direction.Pure) { - Set effects = new HashSet<>(); + Set effects = new HashSet<>(); int effectsSize = DataInputOutputUtil.readINT(in); for (int i = 0; i < effectsSize; i++) { int effectMask = DataInputOutputUtil.readINT(in); if (effectMask == -1) { - effects.add(HEffectQuantum.TopEffectQuantum); + effects.add(EffectQuantum.TopEffectQuantum); } else if (effectMask == -2) { - effects.add(HEffectQuantum.ThisChangeQuantum); + effects.add(EffectQuantum.ThisChangeQuantum); } else if (effectMask == -3){ byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE]; in.readFully(bytes); int rawDirKey = DataInputOutputUtil.readINT(in); boolean isStable = in.readBoolean(); - HKey key = new HKey(bytes, Math.abs(rawDirKey), isStable, false); + EKey key = new EKey(new HMethod(bytes), Math.abs(rawDirKey), isStable, false); boolean isStatic = in.readBoolean(); int dataLength = DataInputOutputUtil.readINT(in); DataValue[] data = new DataValue[dataLength]; @@ -298,43 +299,43 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { data[di] = new DataValue.ParameterDataValue(dataI); } } - effects.add(new HEffectQuantum.CallQuantum(key, data, isStatic)); + effects.add(new EffectQuantum.CallQuantum(key, data, isStatic)); } else { - effects.add(new HEffectQuantum.ParamChangeQuantum(effectMask)); + effects.add(new EffectQuantum.ParamChangeQuantum(effectMask)); } } - results.add(new DirectionResultPair(directionKey, new HEffects(effects))); + results.add(new DirectionResultPair(directionKey, new Effects(effects))); } else { boolean isFinal = in.readBoolean(); // flag if (isFinal) { int ordinal = DataInputOutputUtil.readINT(in); Value value = Value.values()[ordinal]; - results.add(new DirectionResultPair(directionKey, new HFinal(value))); + results.add(new DirectionResultPair(directionKey, new Final(value))); } else { int sumLength = DataInputOutputUtil.readINT(in); - HComponent[] components = new HComponent[sumLength]; + Component[] components = new Component[sumLength]; for (int i = 0; i < sumLength; i++) { int ordinal = DataInputOutputUtil.readINT(in); Value value = Value.values()[ordinal]; int componentSize = DataInputOutputUtil.readINT(in); - HKey[] ids = new HKey[componentSize]; + EKey[] ids = new EKey[componentSize]; for (int j = 0; j < componentSize; j++) { byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE]; in.readFully(bytes); int rawDirKey = DataInputOutputUtil.readINT(in); - ids[j] = new HKey(bytes, Math.abs(rawDirKey), in.readBoolean(), rawDirKey < 0); + ids[j] = new EKey(new HMethod(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0); } - components[i] = new HComponent(value, ids); + components[i] = new Component(value, ids); } - results.add(new DirectionResultPair(directionKey, new HPending(components))); + results.add(new DirectionResultPair(directionKey, new Pending(components))); } } } - return new HEquations(results, stable); + return new Equations(results, stable); } } } 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 5f13d72b5245..fb69afcf726e 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 @@ -46,7 +46,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys * * @author lambdamix */ -public class ClassDataIndexer implements VirtualFileGist.GistCalculator> { +public class ClassDataIndexer implements VirtualFileGist.GistCalculator> { public static final Final FINAL_TOP = new Final(Value.Top); public static final Final FINAL_FAIL = new Final(Value.Fail); @@ -56,12 +56,12 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator calcData(@NotNull Project project, @NotNull VirtualFile file) { - HashMap map = new HashMap<>(); + public Map calcData(@NotNull Project project, @NotNull VirtualFile file) { + HashMap map = new HashMap<>(); try { MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); - Map> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl()); - allEquations.forEach((methodKey, equations) -> map.put(compressKey(md, methodKey), convertEquations(md, methodKey, equations))); + Map> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl()); + allEquations.forEach((methodKey, equations) -> map.put(methodKey.method.hashed(md), convertEquations(methodKey, equations))); } catch (ProcessCanceledException e) { throw e; @@ -75,18 +75,13 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator rawMethodEquations) { + private static Equations convertEquations(EKey methodKey, List rawMethodEquations) { List compressedMethodEquations = - ContainerUtil.map(rawMethodEquations, equation -> BytecodeAnalysisConverter.convert(equation, md)); - return new HEquations(compressedMethodEquations, methodKey.stable); + ContainerUtil.map(rawMethodEquations, equation -> new DirectionResultPair(equation.key.dirKey, equation.result)); + return new Equations(compressedMethodEquations, methodKey.stable); } - public static Map> processClass(final ClassReader classReader, final String presentableUrl) { + public static Map> processClass(final ClassReader classReader, final String presentableUrl) { // It is OK to share pending states, actions and results for analyses. // Analyses are designed in such a way that they first write to states/actions/results and then read only those portion @@ -95,11 +90,11 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator> equations = new HashMap<>(); + final Map> equations = new HashMap<>(); classReader.accept(new KeyedMethodVisitor() { - protected MethodVisitor visitMethod(final MethodNode node, final Key key) { + protected MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key) { return new MethodVisitor(Opcodes.API_VERSION, node) { private boolean jsr; @@ -114,7 +109,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator ... contract, !null -> contract - // 3: @NotNull OUT, @Nullable OUT, purity analysis - List equations = new ArrayList<>(argumentTypes.length * 4 + 3); + List equations = new ArrayList<>(); equations.add(PurityAnalysis.analyze(method, methodNode, stable)); try { @@ -300,14 +293,14 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator")) { // Do not infer failing contracts for constructors shouldInferNonTrivialFailingContracts = false; - throwEquation = new Equation(new Key(method, Throw, stable), FINAL_TOP); + throwEquation = new Equation(new EKey(method, Throw, stable), FINAL_TOP); } else { final InThrowAnalysis inThrowAnalysis = new InThrowAnalysis(richControlFlow, Throw, origins, stable, sharedPendingStates); throwEquation = inThrowAnalysis.analyze(); @@ -333,8 +326,8 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculatorfail" - result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT)); + result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), FINAL_BOT)); + result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), outEquation.result)); continue; } } @@ -421,23 +415,23 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator topEquations(Method method, - Type[] argumentTypes, - boolean isReferenceResult, - boolean isInterestingResult, - boolean stable) { + Type[] argumentTypes, + boolean isReferenceResult, + boolean isInterestingResult, + boolean stable) { // 4 = @NotNull parameter, @Nullable parameter, null -> ..., !null -> ... List result = new ArrayList<>(argumentTypes.length * 4 + 2); if (isReferenceResult) { - result.add(new Equation(new Key(method, Out, stable), FINAL_TOP)); - result.add(new Equation(new Key(method, NullableOut, stable), FINAL_BOT)); + result.add(new Equation(new EKey(method, Out, stable), FINAL_TOP)); + result.add(new Equation(new EKey(method, NullableOut, stable), FINAL_BOT)); } for (int i = 0; i < argumentTypes.length; i++) { if (ASMUtils.isReferenceType(argumentTypes[i])) { - result.add(new Equation(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP)); - result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP)); + result.add(new Equation(new EKey(method, new In(i, false), stable), FINAL_TOP)); + result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_TOP)); if (isInterestingResult) { - result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP)); - result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP)); + result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), FINAL_TOP)); + result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), FINAL_TOP)); } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java index 0bb248933334..1444ccd00299 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 @@ -104,14 +104,14 @@ interface CombinedData { } @NotNull - Set getKeysForParameter(int idx, ParamValueBasedDirection direction) { - Set keys = new HashSet<>(); + Set getKeysForParameter(int idx, ParamValueBasedDirection direction) { + Set keys = new HashSet<>(); for (int argI = 0; argI < this.args.size(); argI++) { BasicValue arg = this.args.get(argI); if (arg instanceof NthParamValue) { NthParamValue npv = (NthParamValue)arg; if (npv.n == idx) { - keys.add(new Key(this.method, direction.withIndex(argI), this.stableCall)); + keys.add(new EKey(this.method, direction.withIndex(argI), this.stableCall)); } } } @@ -203,7 +203,7 @@ final class CombinedAnalysis { } final Equation notNullParamEquation(int i, boolean stable) { - final Key key = new Key(method, new In(i, In.NOT_NULL_MASK), stable); + final EKey key = new EKey(method, new In(i, false), stable); final Result result; if (interpreter.dereferencedParams[i]) { result = new Final(Value.NotNull); @@ -214,18 +214,18 @@ final class CombinedAnalysis { result = new Final(Value.Top); } else { - Set keys = new HashSet<>(); + Set keys = new HashSet<>(); for (ParamKey pk: calls) { - keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL_MASK), pk.stable)); + keys.add(new EKey(pk.method, new In(pk.i, false), pk.stable)); } - result = new Pending(new SingletonSet<>(new Product(Value.Top, keys))); + result = new Pending(new SingletonSet<>(new Component(Value.Top, keys))); } } return new Equation(key, result); } final Equation nullableParamEquation(int i, boolean stable) { - final Key key = new Key(method, new In(i, In.NULLABLE_MASK), stable); + final EKey key = new EKey(method, new In(i, true), stable); final Result result; if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) { result = new Final(Value.Top); @@ -236,9 +236,9 @@ final class CombinedAnalysis { result = new Final(Value.Null); } else { - Set sum = new HashSet<>(); + Set sum = new HashSet<>(); for (ParamKey pk: calls) { - sum.add(new Product(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE_MASK), pk.stable)))); + sum.add(new Component(Value.Top, Collections.singleton(new EKey(pk.method, new In(pk.i, true), pk.stable)))); } result = new Pending(sum); } @@ -249,7 +249,7 @@ final class CombinedAnalysis { @Nullable final Equation contractEquation(int i, Value inValue, boolean stable) { final InOut direction = new InOut(i, inValue); - final Key key = new Key(method, direction, stable); + final EKey key = new EKey(method, direction, stable); final Result result; if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) { result = new Final(Value.Bot); @@ -271,14 +271,14 @@ final class CombinedAnalysis { } else if (returnValue instanceof TrackableCallValue) { TrackableCallValue call = (TrackableCallValue)returnValue; - Set keys = call.getKeysForParameter(i, direction); + Set keys = call.getKeysForParameter(i, direction); if (ASMUtils.isReferenceType(call.getType())) { - keys.add(new Key(call.method, Out, call.stableCall)); + keys.add(new EKey(call.method, Out, call.stableCall)); } if (keys.isEmpty()) { return null; } else { - result = new Pending(new SingletonSet<>(new Product(Value.Top, keys))); + result = new Pending(new SingletonSet<>(new Component(Value.Top, keys))); } } else { @@ -289,15 +289,15 @@ final class CombinedAnalysis { @Nullable final Equation failEquation(boolean stable) { - final Key key = new Key(method, Throw, stable); + final EKey key = new EKey(method, Throw, stable); final Result result; if (exception) { result = new Final(Value.Fail); } else if (!interpreter.calls.isEmpty()) { - Set keys = - interpreter.calls.stream().map(call -> new Key(call.method, Throw, call.stableCall)).collect(Collectors.toSet()); - result = new Pending(new SingletonSet<>(new Product(Value.Top, keys))); + Set keys = + interpreter.calls.stream().map(call -> new EKey(call.method, Throw, call.stableCall)).collect(Collectors.toSet()); + result = new Pending(new SingletonSet<>(new Component(Value.Top, keys))); } else { return null; @@ -308,18 +308,18 @@ final class CombinedAnalysis { @Nullable final Equation failEquation(int i, Value inValue, boolean stable) { final InThrow direction = new InThrow(i, inValue); - final Key key = new Key(method, direction, stable); + final EKey key = new EKey(method, direction, stable); final Result result; if (exception) { result = new Final(Value.Fail); } else if (!interpreter.calls.isEmpty()) { - Set keys = new HashSet<>(); + Set keys = new HashSet<>(); for (TrackableCallValue call : interpreter.calls) { keys.addAll(call.getKeysForParameter(i, direction)); - keys.add(new Key(call.method, Throw, call.stableCall)); + keys.add(new EKey(call.method, Throw, call.stableCall)); } - result = new Pending(new SingletonSet<>(new Product(Value.Top, keys))); + result = new Pending(new SingletonSet<>(new Component(Value.Top, keys))); } else { return null; @@ -329,7 +329,7 @@ final class CombinedAnalysis { @Nullable final Equation outContractEquation(boolean stable) { - final Key key = new Key(method, Out, stable); + final EKey key = new EKey(method, Out, stable); final Result result; if (exception) { result = new Final(Value.Bot); @@ -348,9 +348,9 @@ final class CombinedAnalysis { } else if (returnValue instanceof TrackableCallValue) { TrackableCallValue call = (TrackableCallValue)returnValue; - Key callKey = new Key(call.method, Out, call.stableCall); - Set keys = new SingletonSet<>(callKey); - result = new Pending(new SingletonSet<>(new Product(Value.Top, keys))); + EKey callKey = new EKey(call.method, Out, call.stableCall); + Set keys = new SingletonSet<>(callKey); + result = new Pending(new SingletonSet<>(new Component(Value.Top, keys))); } else { return null; @@ -359,7 +359,7 @@ final class CombinedAnalysis { } final Equation nullableResultEquation(boolean stable) { - final Key key = new Key(method, NullableOut, stable); + final EKey key = new EKey(method, NullableOut, stable); final Result result; if (exception || returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) { @@ -367,9 +367,9 @@ final class CombinedAnalysis { } else if (returnValue instanceof TrackableCallValue) { TrackableCallValue call = (TrackableCallValue)returnValue; - Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall); - Set keys = new SingletonSet<>(callKey); - result = new Pending(new SingletonSet<>(new Product(Value.Null, keys))); + EKey callKey = new EKey(call.method, NullableOut, call.stableCall || call.thisCall); + Set keys = new SingletonSet<>(callKey); + result = new Pending(new SingletonSet<>(new Component(Value.Null, keys))); } else if (returnValue instanceof TrackableNullValue) { result = new Final(Value.Null); @@ -735,22 +735,22 @@ final class NegationAnalysis { } final Equation contractEquation(int i, Value inValue, boolean stable) { - final Key key = new Key(method, new InOut(i, inValue), stable); + final EKey key = new EKey(method, new InOut(i, inValue), stable); final Result result; - HashSet keys = new HashSet<>(); + HashSet keys = new HashSet<>(); for (int argI = 0; argI < conditionValue.args.size(); argI++) { BasicValue arg = conditionValue.args.get(argI); if (arg instanceof NthParamValue) { NthParamValue npv = (NthParamValue)arg; if (npv.n == i) { - keys.add(new Key(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true)); + keys.add(new EKey(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true)); } } } if (keys.isEmpty()) { result = new Final(Value.Top); } else { - result = new Pending(new SingletonSet<>(new Product(Value.Top, keys))); + result = new Pending(new SingletonSet<>(new Component(Value.Top, keys))); } return new Equation(key, result); } 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 61310d55e4a3..26685cf682e8 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 @@ -29,7 +29,7 @@ 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.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -65,6 +65,16 @@ abstract class ContractAnalysis extends Analysis { return new Equation(aKey, res); } + static Result checkLimit(Result result) throws AnalyzerException { + if(result instanceof Pending) { + int size = Arrays.stream(((Pending)result).delta).mapToInt(prod -> prod.ids.length).sum(); + if (size > Analysis.EQUATION_SIZE_LIMIT) { + throw new AnalyzerException(null, "Equation size is too big"); + } + } + return result; + } + @NotNull protected Equation analyze() throws AnalyzerException { pendingPush(createStartState()); @@ -279,14 +289,14 @@ class InOutAnalysis extends ContractAnalysis { subResult = new Final(inValue); } else if (stackTop instanceof CallResultValue) { - Set keys = ((CallResultValue) stackTop).inters; - subResult = new Pending(Collections.singleton(new Product(Value.Top, keys))); + Set keys = ((CallResultValue) stackTop).inters; + subResult = new Pending(new Component[] {new Component(Value.Top, keys)}); } else { earlyResult = new Final(Value.Top); return true; } - internalResult = resultUtil.join(internalResult, subResult); + internalResult = checkLimit(resultUtil.join(internalResult, subResult)); if (internalResult instanceof Final && ((Final)internalResult).value == Value.Top) { earlyResult = internalResult; } @@ -507,22 +517,22 @@ class InOutInterpreter extends BasicInterpreter { boolean isRefRetType = retType.getSort() == Type.OBJECT || retType.getSort() == Type.ARRAY; if (!Type.VOID_TYPE.equals(retType)) { if (direction != null) { - HashSet keys = new HashSet<>(); + HashSet keys = new HashSet<>(); for (int i = shift; i < values.size(); i++) { if (values.get(i) instanceof ParamValue) { - keys.add(new Key(method, direction.withIndex(i - shift), stable)); + keys.add(new EKey(method, direction.withIndex(i - shift), stable)); } } if (isRefRetType) { - keys.add(new Key(method, Out, stable)); + keys.add(new EKey(method, Out, stable)); } if (!keys.isEmpty()) { return new CallResultValue(retType, keys); } } else if (isRefRetType) { - HashSet keys = new HashSet<>(); - keys.add(new Key(method, Out, stable)); + HashSet keys = new HashSet<>(); + keys.add(new EKey(method, Out, stable)); return new CallResultValue(retType, keys); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java similarity index 51% rename from java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java rename to java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java index e26e0e352aa1..0883ca6ca3a5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HData.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java @@ -19,19 +19,24 @@ import com.intellij.util.ArrayFactory; import org.jetbrains.annotations.NotNull; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Set; /** * Represents a lattice product of a constant {@link #value} and all {@link #ids}. */ -final class HComponent { - static final HComponent[] EMPTY_ARRAY = new HComponent[0]; - static final ArrayFactory ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new HComponent[count]; +final class Component { + static final Component[] EMPTY_ARRAY = new Component[0]; + static final ArrayFactory ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new Component[count]; @NotNull Value value; - @NotNull final HKey[] ids; + @NotNull final EKey[] ids; - HComponent(@NotNull Value value, @NotNull HKey[] ids) { + Component(@NotNull Value value, @NotNull Set ids) { + this(value, ids.toArray(new EKey[0])); + } + + Component(@NotNull Value value, @NotNull EKey[] ids) { this.value = value; this.ids = ids; } @@ -41,45 +46,17 @@ final class HComponent { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HComponent that = (HComponent)o; + Component that = (Component)o; - if (!Arrays.equals(ids, that.ids)) return false; - if (value != that.value) return false; - - return true; + return value == that.value && Arrays.equals(ids, that.ids); } @Override public int hashCode() { - int result = value.hashCode(); - result = 31 * result + Arrays.hashCode(ids); - return result; + return 31 * value.hashCode() + Arrays.hashCode(ids); } - public boolean remove(@NotNull HKey id) { - return HUtils.remove(ids, id); - } - - public boolean isEmpty() { - return HUtils.isEmpty(ids); - } - - @NotNull - public HComponent copy() { - return new HComponent(value, ids.clone()); - } -} - -class HUtils { - - static boolean isEmpty(HKey[] ids) { - for (HKey id : ids) { - if (id != null) return false; - } - return true; - } - - static boolean remove(HKey[] ids, @NotNull HKey id) { + public boolean remove(@NotNull EKey id) { boolean removed = false; for (int i = 0; i < ids.length; i++) { if (id.equals(ids[i])) { @@ -89,13 +66,25 @@ class HUtils { } return removed; } + + public boolean isEmpty() { + for (EKey id : ids) { + if (id != null) return false; + } + return true; + } + + @NotNull + public Component copy() { + return new Component(value, ids.clone()); + } } -final class HEquation { - @NotNull final HKey key; - @NotNull final HResult result; +final class Equation { + @NotNull final EKey key; + @NotNull final Result result; - HEquation(@NotNull HKey key, @NotNull HResult result) { + Equation(@NotNull EKey key, @NotNull Result result) { this.key = key; this.result = result; } @@ -104,48 +93,26 @@ final class HEquation { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HEquation hEquation = (HEquation)o; - if (!key.equals(hEquation.key)) return false; - if (!result.equals(hEquation.result)) return false; - return true; + Equation equation = (Equation)o; + return key.equals(equation.key) && result.equals(equation.result); } @Override public int hashCode() { - int result1 = key.hashCode(); - result1 = 31 * result1 + result.hashCode(); - return result1; - } -} - -/** - * Bytes of primary HKey of a method. - */ -final class Bytes { - @NotNull - final byte[] bytes; - Bytes(@NotNull byte[] bytes) { - this.bytes = bytes; + return 31 * key.hashCode() + result.hashCode(); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - return Arrays.equals(bytes, ((Bytes)o).bytes); - } - - @Override - public int hashCode() { - return Arrays.hashCode(bytes); + public String toString() { + return "Equation{" + "key=" + key + ", result=" + result + '}'; } } -class HEquations { +class Equations { @NotNull final List results; final boolean stable; - HEquations(@NotNull List results, boolean stable) { + Equations(@NotNull List results, boolean stable) { this.results = results; this.stable = stable; } @@ -155,28 +122,22 @@ class HEquations { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HEquations that = (HEquations)o; - - if (stable != that.stable) return false; - if (!results.equals(that.results)) return false; - - return true; + Equations that = (Equations)o; + return stable == that.stable && results.equals(that.results); } @Override public int hashCode() { - int result = results.hashCode(); - result = 31 * result + (stable ? 1 : 0); - return result; + return 31 * results.hashCode() + (stable ? 1 : 0); } } class DirectionResultPair { final int directionKey; @NotNull - final HResult hResult; + final Result hResult; - DirectionResultPair(int directionKey, @NotNull HResult hResult) { + DirectionResultPair(int directionKey, @NotNull Result hResult) { this.directionKey = directionKey; this.hResult = hResult; } @@ -187,26 +148,20 @@ class DirectionResultPair { if (o == null || getClass() != o.getClass()) return false; DirectionResultPair that = (DirectionResultPair)o; - - if (directionKey != that.directionKey) return false; - if (!hResult.equals(that.hResult)) return false; - - return true; + return directionKey == that.directionKey && hResult.equals(that.hResult); } @Override public int hashCode() { - int result = directionKey; - result = 31 * result + hResult.hashCode(); - return result; + return 31 * directionKey + hResult.hashCode(); } } -interface HResult {} -final class HFinal implements HResult { +interface Result {} +final class Final implements Result { @NotNull final Value value; - HFinal(@NotNull Value value) { + Final(@NotNull Value value) { this.value = value; } @@ -215,23 +170,28 @@ final class HFinal implements HResult { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HFinal hFinal = (HFinal)o; - - if (value != hFinal.value) return false; - - return true; + return value == ((Final)o).value; } @Override public int hashCode() { return value.ordinal(); } + + @Override + public String toString() { + return "Final{" + "value=" + value + '}'; + } } -final class HPending implements HResult { - @NotNull final HComponent[] delta; // sum +final class Pending implements Result { + @NotNull final Component[] delta; // sum - HPending(@NotNull HComponent[] delta) { + Pending(Collection delta) { + this(delta.toArray(Component.EMPTY_ARRAY)); + } + + Pending(@NotNull Component[] delta) { this.delta = delta; } @@ -239,9 +199,7 @@ final class HPending implements HResult { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HPending hPending = (HPending)o; - if (!Arrays.equals(delta, hPending.delta)) return false; - return true; + return Arrays.equals(delta, ((Pending)o).delta); } @Override @@ -250,20 +208,19 @@ final class HPending implements HResult { } @NotNull - HPending copy() { - HComponent[] delta1 = new HComponent[delta.length]; + Pending copy() { + Component[] copy = new Component[delta.length]; for (int i = 0; i < delta.length; i++) { - delta1[i] = delta[i].copy(); - + copy[i] = delta[i].copy(); } - return new HPending(delta1); + return new Pending(copy); } } -final class HEffects implements HResult { - @NotNull final Set effects; +final class Effects implements Result { + @NotNull final Set effects; - HEffects(@NotNull Set effects) { + Effects(@NotNull Set effects) { this.effects = effects; } @@ -271,8 +228,7 @@ final class HEffects implements HResult { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HEffects hEffects = (HEffects)o; - return effects.equals(hEffects.effects); + return this.effects.equals(((Effects)o).effects); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Direction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Direction.java index 50f88faac425..40fd0cb5a4e6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Direction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Direction.java @@ -49,7 +49,7 @@ public abstract class Direction { int subDirectionId = paramKey % DIRECTIONS_PER_PARAM_ID; // 0 - 1 - @NotNull, @Nullable, parameter if (subDirectionId < IN_OUT_OFFSET) { - return new In(paramId, subDirectionId); + return new In(paramId, subDirectionId == 1); } if (subDirectionId < IN_THROW_OFFSET) { int valueId = subDirectionId - IN_OUT_OFFSET; @@ -60,7 +60,7 @@ public abstract class Direction { } /** - * Encodes Direction object as int. + * Encodes Direction object as non-negative int. * * @return unique int for direction */ @@ -114,27 +114,21 @@ public abstract class Direction { } static final class In extends ParamIdBasedDirection { - static final int NOT_NULL_MASK = 0; - static final int NULLABLE_MASK = 1; - /** - * @see #NOT_NULL_MASK - * @see #NULLABLE_MASK - */ - final int nullityMask; + final boolean nullable; - In(int paramIndex, int nullityMask) { + In(int paramIndex, boolean nullable) { super(paramIndex); - this.nullityMask = nullityMask; + this.nullable = nullable; } @Override int asInt() { - return super.asInt() + nullityMask; + return super.asInt() + (nullable ? 1 : 0); } @Override public String toString() { - return "In " + paramIndex; + return "In " + paramIndex + "(" + (nullable ? "nullable" : "not null") + ")"; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/EKey.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/EKey.java new file mode 100644 index 000000000000..cf61734b59f1 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/EKey.java @@ -0,0 +1,107 @@ +/* + * Copyright 2000-2017 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 org.jetbrains.annotations.NotNull; + +import java.security.MessageDigest; + +/** + * Equation key (or variable) + */ +public final class EKey { + @NotNull + final MethodDescriptor method; + final int dirKey; + final boolean stable; + final boolean negated; + + public EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable) { + this(method, direction, stable, false); + } + + EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable, boolean negated) { + this(method, direction.asInt(), stable, negated); + } + + EKey(@NotNull MethodDescriptor method, int dirKey, boolean stable, boolean negated) { + this.method = method; + this.dirKey = dirKey; + this.stable = stable; + this.negated = negated; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EKey key = (EKey) o; + + if (stable != key.stable) return false; + if (negated != key.negated) return false; + if (dirKey != key.dirKey) return false; + if (!method.equals(key.method)) return false; + return true; + } + + @Override + public int hashCode() { + int result = method.hashCode(); + result = 31 * result + dirKey; + result = 31 * result + (stable ? 1 : 0); + result = 31 * result + (negated ? 1 : 0); + return result; + } + + EKey invertStability() { + return new EKey(method, dirKey, !stable, negated); + } + + EKey mkStable() { + return stable ? this : new EKey(method, dirKey, true, negated); + } + + EKey mkUnstable() { + return stable ? new EKey(method, dirKey, false, negated) : this; + } + + public EKey mkBase() { + return withDirection(Direction.Out); + } + + EKey withDirection(Direction dir) { + return dirKey == dir.asInt() ? this : new EKey(method, dir, stable, false); + } + + EKey negate() { + return new EKey(method, dirKey, stable, true); + } + + public EKey hashed(MessageDigest md) { + HMethod hmethod = method.hashed(md); + return hmethod == method ? this : new EKey(hmethod, dirKey, stable, negated); + } + + public Direction getDirection() { + return Direction.fromInt(dirKey); + } + + @Override + public String toString() { + return "Key [" + method + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + Direction.fromInt(dirKey) + "]"; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HKey.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HKey.java deleted file mode 100644 index 422b6106a3eb..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HKey.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2000-2017 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 one.util.streamex.IntStreamEx; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; - -/** - * Small size key, constructed by hashing method signature. - * 'H' in this and related class names stands for 'Hash'. - * @see BytecodeAnalysisConverter for details of construction. - */ -public final class HKey { - @NotNull - final byte[] key; - final int dirKey; - final boolean stable; - final boolean negated; - - HKey(@NotNull byte[] key, int dirKey, boolean stable, boolean negated) { - this.key = key; - this.dirKey = dirKey; - this.stable = stable; - this.negated = negated; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HKey hKey = (HKey)o; - if (dirKey != hKey.dirKey) return false; - if (stable != hKey.stable) return false; - if (negated != hKey.negated) return false; - if (!Arrays.equals(key, hKey.key)) return false; - return true; - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(key); - result = 31 * result + dirKey; - result = 31 * result + (stable ? 1 : 0); - result = 31 * result + (negated ? 1 : 0); - return result; - } - - HKey invertStability() { - return new HKey(key, dirKey, !stable, negated); - } - - HKey mkStable() { - return stable ? this : new HKey(key, dirKey, true, negated); - } - - HKey mkUnstable() { - return stable ? new HKey(key, dirKey, false, negated) : this; - } - - public HKey mkBase() { - return dirKey == 0 ? this : new HKey(key, 0, stable, false); - } - - HKey withDirection(Direction dir) { - return new HKey(key, dir.asInt(), stable, false); - } - - HKey negate() { - return new HKey(key, dirKey, stable, true); - } - - public Direction getDirection() { - return Direction.fromInt(dirKey); - } - - @Override - public String toString() { - return "HKey [" + bytesToString(key) + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + getDirection() + "]"; - } - - static String bytesToString(byte[] key) { - return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining("."); - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Key.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java similarity index 50% rename from java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Key.java rename to java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java index dc499bbf78d4..0f615f8f6cbe 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Key.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java @@ -15,49 +15,46 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; -public final class Key { - final Method method; - final Direction direction; - final boolean stable; - final boolean negated; +import one.util.streamex.IntStreamEx; +import org.jetbrains.annotations.NotNull; - public Key(Method method, Direction direction, boolean stable) { - this.method = method; - this.direction = direction; - this.stable = stable; - this.negated = false; - } +import java.security.MessageDigest; +import java.util.Arrays; - Key(Method method, Direction direction, boolean stable, boolean negated) { - this.method = method; - this.direction = direction; - this.stable = stable; - this.negated = negated; +/** + * Hashed representation of method. + */ +public final class HMethod implements MethodDescriptor { + @NotNull + final byte[] myBytes; + + public HMethod(@NotNull byte[] bytes) { + myBytes = bytes; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - - Key key = (Key) o; - - if (!direction.equals(key.direction)) return false; - if (!method.equals(key.method)) return false; - if (stable != key.stable) return false; - return true; + return Arrays.equals(myBytes, ((HMethod)o).myBytes); } @Override public int hashCode() { - int result = method.hashCode(); - result = 31 * result + direction.hashCode(); - result = 31 * result + (stable ? 1 : 0); - return result; + return Arrays.hashCode(myBytes); } + @NotNull @Override + public HMethod hashed(MessageDigest md) { + return this; + } + public String toString() { - return method + " " + direction + " " + stable; + return bytesToString(myBytes); + } + + static String bytesToString(byte[] key) { + return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining("."); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java index d90d0eb927c3..edefdab97058 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java @@ -49,9 +49,9 @@ abstract class KeyedMethodVisitor extends ClassVisitor { Method method = new Method(className, node.name, node.desc); boolean stable = stableClass || (node.access & STABLE_FLAGS) != 0 || "".equals(node.name); - return visitMethod(node, new Key(method, Out, stable)); + return visitMethod(node, method, new EKey(method, Out, stable)); } @Nullable - abstract MethodVisitor visitMethod(final MethodNode node, final Key key); + abstract MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java index 07745cf4bdf5..c2a7134c58ae 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java @@ -15,9 +15,16 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; +import com.intellij.openapi.vfs.CharsetToolkit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; -public final class Method { +import java.security.MessageDigest; + +import static com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter.*; + +public final class Method implements MethodDescriptor { final String internalClassName; final String methodName; final String methodDesc; @@ -62,6 +69,22 @@ public final class Method { this.methodDesc = mNode.desc; } + @NotNull + @Override + public HMethod hashed(@Nullable MessageDigest md) { + if (md == null) { + md = getMessageDigest(); + } + byte[] classDigest = md.digest(internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET)); + md.update(methodName.getBytes(CharsetToolkit.UTF8_CHARSET)); + md.update(methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET)); + byte[] sigDigest = md.digest(); + byte[] digest = new byte[HASH_SIZE]; + System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE); + System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE); + return new HMethod(digest); + } + @Override public String toString() { return internalClassName + ' ' + methodName + ' ' + methodDesc; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/MethodDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/MethodDescriptor.java new file mode 100644 index 000000000000..a692568b856d --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/MethodDescriptor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2017 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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.security.MessageDigest; + +/** + * An uniquely method (class name+method name+signature) identifier: either {@link Method} or {@link HMethod}. + */ +public interface MethodDescriptor { + /** + * Creates and returns the hashed representation of this method descriptor. + * May return itself if already hashed. Note that hashed descriptor is not equal to + * non-hashed one. + * + * @param md message digest to use for hashing (could be null to use the default one) + * @return a corresponding HMethod. + */ + @NotNull HMethod hashed(@Nullable MessageDigest md); +} 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 index e0d4ffb10284..0e2abef5a9d5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java @@ -149,13 +149,13 @@ class NullableMethodAnalysis { Calls calls = ((Calls)result); int mergedMappedLabels = calls.mergedLabels; if (mergedMappedLabels != 0) { - Set sum = new HashSet<>(); - Key[] createdKeys = interpreter.keys; + Set sum = new HashSet<>(); + EKey[] createdKeys = interpreter.keys; for (int origin = 0; origin < originsMapping.length; origin++) { int mappedOrigin = originsMapping[origin]; - Key createdKey = createdKeys[origin]; + EKey createdKey = createdKeys[origin]; if (createdKey != null && (mergedMappedLabels & (1 << mappedOrigin)) != 0) { - sum.add(new Product(Value.Null, Collections.singleton(createdKey))); + sum.add(new Component(Value.Null, Collections.singleton(createdKey))); } } if (!sum.isEmpty()) { @@ -211,7 +211,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE final InsnList insns; final boolean[] origins; private final int[] originsMapping; - final Key[] keys; + final EKey[] keys; Constraint constraint; int delta; @@ -224,7 +224,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE this.insns = insns; this.origins = origins; this.originsMapping = originsMapping; - keys = new Key[originsMapping.length]; + keys = new EKey[originsMapping.length]; } @Override @@ -344,7 +344,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE 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); + keys[insnIndex] = new EKey(method, Direction.NullableOut, stable); } return new Calls(label); } 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 7a16edd08399..59021e27a087 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 @@ -38,18 +38,18 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*; abstract class PResults { // SoP = sum of products - static Set> join(Set> sop1, Set> sop2) { - Set> sop = new HashSet<>(); + static Set> join(Set> sop1, Set> sop2) { + Set> sop = new HashSet<>(); sop.addAll(sop1); sop.addAll(sop2); return sop; } - static Set> meet(Set> sop1, Set> sop2) { - Set> sop = new HashSet<>(); - for (Set prod1 : sop1) { - for (Set prod2 : sop2) { - Set prod = new HashSet<>(); + static Set> meet(Set> sop1, Set> sop2) { + Set> sop = new HashSet<>(); + for (Set prod1 : sop1) { + for (Set prod2 : sop2) { + Set prod = new HashSet<>(); prod.addAll(prod1); prod.addAll(prod2); sop.add(prod); @@ -83,23 +83,23 @@ abstract class PResults { } }; static final class ConditionalNPE implements PResult { - final Set> sop; - public ConditionalNPE(Set> sop) throws AnalyzerException { + final Set> sop; + public ConditionalNPE(Set> sop) throws AnalyzerException { this.sop = sop; checkLimit(sop); } - public ConditionalNPE(Key key) { + public ConditionalNPE(EKey key) { sop = new HashSet<>(); - Set prod = new HashSet<>(); + Set prod = new HashSet<>(); prod.add(key); sop.add(prod); } - static void checkLimit(Set> sop) throws AnalyzerException { + static void checkLimit(Set> sop) throws AnalyzerException { int size = sop.stream().mapToInt(Set::size).sum(); if (size > Analysis.EQUATION_SIZE_LIMIT) { - throw new AnalyzerException(null, "Equation size is too big"); + throw new AnalyzerException(null, "HEquation size is too big"); } } } @@ -198,7 +198,7 @@ class NonNullInAnalysis extends Analysis { } else { ConditionalNPE condNpe = (ConditionalNPE) result; - Set components = condNpe.sop.stream().map(prod -> new Product(Value.Top, prod)).collect(Collectors.toSet()); + Set components = condNpe.sop.stream().map(prod -> new Component(Value.Top, prod)).collect(Collectors.toSet()); return new Equation(aKey, new Pending(components)); } } @@ -421,7 +421,7 @@ class NullableInAnalysis extends Analysis { } else { ConditionalNPE condNpe = (ConditionalNPE) result; - Set components = condNpe.sop.stream().map(prod -> new Product(Value.Top, prod)).collect(Collectors.toSet()); + Set components = condNpe.sop.stream().map(prod -> new Component(Value.Top, prod)).collect(Collectors.toSet()); return new Equation(aKey, new Pending(components)); } } @@ -593,13 +593,13 @@ class NullableInAnalysis extends Analysis { abstract class NullityInterpreter extends BasicInterpreter { boolean top; final boolean nullableAnalysis; - final int nullityMask; + final boolean nullable; private PResult subResult = Identity; protected boolean taken; - NullityInterpreter(boolean nullableAnalysis, int nullityMask) { + NullityInterpreter(boolean nullableAnalysis, boolean nullable) { this.nullableAnalysis = nullableAnalysis; - this.nullityMask = nullityMask; + this.nullable = nullable; } abstract PResult combine(PResult res1, PResult res2) throws AnalyzerException; @@ -736,8 +736,8 @@ abstract class NullityInterpreter extends BasicInterpreter { boolean stable = opcode == INVOKESTATIC || opcode == INVOKESPECIAL; for (int i = 0; i < values.size(); i++) { BasicValue value = values.get(i); - if (value instanceof ParamValue || (NullValue == value && nullityMask == In.NULLABLE_MASK && "".equals(method.methodName))) { - subResult = combine(subResult, new ConditionalNPE(new Key(method, new In(i, nullityMask), stable))); + if (value instanceof ParamValue || (NullValue == value && nullable && "".equals(method.methodName))) { + subResult = combine(subResult, new ConditionalNPE(new EKey(method, new In(i, nullable), stable))); } } } @@ -747,7 +747,7 @@ abstract class NullityInterpreter extends BasicInterpreter { class NotNullInterpreter extends NullityInterpreter { NotNullInterpreter() { - super(false, In.NOT_NULL_MASK); + super(false, false); } @Override @@ -759,7 +759,7 @@ class NotNullInterpreter extends NullityInterpreter { class NullableInterpreter extends NullityInterpreter { NullableInterpreter() { - super(true, In.NULLABLE_MASK); + super(true, true); } @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 1f69ee8bae5c..3c0bc404d967 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 @@ -57,7 +57,7 @@ public class ProjectBytecodeAnalysis { private final Project myProject; private final boolean nullableMethod; private final boolean nullableMethodTransitivity; - private final Map> myEquationCache = ContainerUtil.createConcurrentSoftValueMap(); + private final Map> myEquationCache = ContainerUtil.createConcurrentSoftValueMap(); public static ProjectBytecodeAnalysis getInstance(@NotNull Project project) { return ServiceManager.getService(project, ProjectBytecodeAnalysis.class); @@ -117,12 +117,12 @@ public class ProjectBytecodeAnalysis { try { MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); - HKey primaryKey = getKey(listOwner, md); + EKey primaryKey = getKey(listOwner, md); if (primaryKey == null) { return PsiAnnotation.EMPTY_ARRAY; } if (listOwner instanceof PsiMethod) { - ArrayList allKeys = collectMethodKeys((PsiMethod)listOwner, primaryKey); + ArrayList allKeys = collectMethodKeys((PsiMethod)listOwner, primaryKey); MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys); return toPsi(primaryKey, methodAnnotations); } else if (listOwner instanceof PsiParameter) { @@ -148,7 +148,7 @@ public class ProjectBytecodeAnalysis { * @return Psi annotations */ @NotNull - private PsiAnnotation[] toPsi(HKey primaryKey, MethodAnnotations methodAnnotations) { + private PsiAnnotation[] toPsi(EKey primaryKey, MethodAnnotations methodAnnotations) { boolean notNull = methodAnnotations.notNulls.contains(primaryKey); boolean nullable = methodAnnotations.nullables.contains(primaryKey); boolean pure = methodAnnotations.pures.contains(primaryKey); @@ -233,7 +233,7 @@ public class ProjectBytecodeAnalysis { } @Nullable - public static HKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) { + public static EKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) { LOG.assertTrue(owner instanceof PsiCompiledElement, owner); if (owner instanceof PsiMethod) { return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, Out, md); @@ -244,7 +244,7 @@ public class ProjectBytecodeAnalysis { PsiElement gParent = parent.getParent(); if (gParent instanceof PsiMethod) { final int index = ((PsiParameterList)parent).getParameterIndex((PsiParameter)owner); - return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, In.NOT_NULL_MASK), md); + return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, false), md); } } } @@ -258,44 +258,44 @@ public class ProjectBytecodeAnalysis { * @param primaryKey primary compressed key for this method * @return compressed keys for this method */ - public static ArrayList collectMethodKeys(@NotNull PsiMethod method, HKey primaryKey) { + public static ArrayList collectMethodKeys(@NotNull PsiMethod method, EKey primaryKey) { return BytecodeAnalysisConverter.mkInOutKeys(method, primaryKey); } - private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey) + private ParameterAnnotations loadParameterAnnotations(@NotNull EKey notNullKey) throws EquationsLimitException { final Solver notNullSolver = new Solver(new ELattice<>(Value.NotNull, Value.Top), Value.Top); collectEquations(Collections.singletonList(notNullKey), notNullSolver); - Map notNullSolutions = notNullSolver.solve(); + Map notNullSolutions = notNullSolver.solve(); // subtle point boolean notNull = (Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable())); final Solver nullableSolver = new Solver(new ELattice<>(Value.Null, Value.Top), Value.Top); - final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true, false); + final EKey nullableKey = new EKey(notNullKey.method, notNullKey.dirKey + 1, true, false); collectEquations(Collections.singletonList(nullableKey), nullableSolver); - Map nullableSolutions = nullableSolver.solve(); + Map nullableSolutions = nullableSolver.solve(); // subtle point boolean nullable = (Value.Null == nullableSolutions.get(nullableKey)) || (Value.Null == nullableSolutions.get(nullableKey.mkUnstable())); return new ParameterAnnotations(notNull, nullable); } - private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull HKey key, ArrayList allKeys) + private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull EKey key, ArrayList allKeys) throws EquationsLimitException { MethodAnnotations result = new MethodAnnotations(); final PuritySolver puritySolver = new PuritySolver(); collectPurityEquations(key.withDirection(Pure), puritySolver); - Map> puritySolutions = puritySolver.solve(); + Map> puritySolutions = puritySolver.solve(); int arity = owner.getParameterList().getParameters().length; BytecodeAnalysisConverter.addEffectAnnotations(puritySolutions, result, key, owner.isConstructor()); - HKey failureKey = key.withDirection(Throw); + EKey failureKey = key.withDirection(Throw); final Solver failureSolver = new Solver(new ELattice<>(Value.Fail, Value.Top), Value.Top); collectEquations(Collections.singletonList(failureKey), failureSolver); if (failureSolver.solve().get(failureKey) == Value.Fail) { @@ -304,20 +304,20 @@ public class ProjectBytecodeAnalysis { } else { final Solver outSolver = new Solver(new ELattice<>(Value.Bot, Value.Top), Value.Top); collectEquations(allKeys, outSolver); - Map solutions = outSolver.solve(); + Map solutions = outSolver.solve(); BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity); } if (nullableMethod) { final Solver nullableMethodSolver = new Solver(new ELattice<>(Value.Bot, Value.Null), Value.Bot); - HKey nullableKey = key.withDirection(NullableOut); + EKey nullableKey = key.withDirection(NullableOut); if (nullableMethodTransitivity) { collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver); } else { collectSingleEquation(nullableKey, nullableMethodSolver); } - Map nullableSolutions = nullableMethodSolver.solve(); + Map nullableSolutions = nullableMethodSolver.solve(); if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.invertStability()) == Value.Null) { result.nullables.add(key); } @@ -325,18 +325,19 @@ public class ProjectBytecodeAnalysis { return result; } - private List getEquations(Bytes key) { - List result = myEquationCache.get(key); + private List getEquations(MethodDescriptor methodDescriptor) { + HMethod key = methodDescriptor.hashed(null); + List result = myEquationCache.get(key); if (result == null) { myEquationCache.put(key, result = BytecodeAnalysisIndex.getEquations(ProjectScope.getLibrariesScope(myProject), key)); } return result; } - private void collectPurityEquations(HKey key, PuritySolver puritySolver) + private void collectPurityEquations(EKey key, PuritySolver puritySolver) throws EquationsLimitException { - HashSet queued = new HashSet<>(); - Stack queue = new Stack<>(); + HashSet queued = new HashSet<>(); + Stack queue = new Stack<>(); queue.push(key); queued.add(key); @@ -346,19 +347,18 @@ public class ProjectBytecodeAnalysis { throw new EquationsLimitException(); } ProgressManager.checkCanceled(); - HKey hKey = queue.pop(); - Bytes bytes = new Bytes(hKey.key); + EKey hKey = queue.pop(); - for (HEquations hEquations : getEquations(bytes)) { - boolean stable = hEquations.stable; - for (DirectionResultPair pair : hEquations.results) { + for (Equations equations : getEquations(hKey.method)) { + boolean stable = equations.stable; + for (DirectionResultPair pair : equations.results) { int dirKey = pair.directionKey; if (dirKey == hKey.dirKey) { - Set effects = ((HEffects)pair.hResult).effects; - puritySolver.addEquation(new HKey(bytes.bytes, dirKey, stable, false), effects); - for (HEffectQuantum effect : effects) { - if (effect instanceof HEffectQuantum.CallQuantum) { - HKey depKey = ((HEffectQuantum.CallQuantum)effect).key; + Set effects = ((Effects)pair.hResult).effects; + puritySolver.addEquation(new EKey(hKey.method, dirKey, stable, false), effects); + for (EffectQuantum effect : effects) { + if (effect instanceof EffectQuantum.CallQuantum) { + EKey depKey = ((EffectQuantum.CallQuantum)effect).key; if (!queued.contains(depKey)) { queue.push(depKey); queued.add(depKey); @@ -371,11 +371,11 @@ public class ProjectBytecodeAnalysis { } } - private void collectEquations(List keys, Solver solver) throws EquationsLimitException { - HashSet queued = new HashSet<>(); - Stack queue = new Stack<>(); + private void collectEquations(List keys, Solver solver) throws EquationsLimitException { + HashSet queued = new HashSet<>(); + Stack queue = new Stack<>(); - for (HKey key : keys) { + for (EKey key : keys) { queue.push(key); queued.add(key); } @@ -385,21 +385,20 @@ public class ProjectBytecodeAnalysis { throw new EquationsLimitException(); } ProgressManager.checkCanceled(); - HKey hKey = queue.pop(); - Bytes bytes = new Bytes(hKey.key); + EKey hKey = queue.pop(); - for (HEquations hEquations : getEquations(bytes)) { - boolean stable = hEquations.stable; - for (DirectionResultPair pair : hEquations.results) { + for (Equations equations : getEquations(hKey.method)) { + boolean stable = equations.stable; + for (DirectionResultPair pair : equations.results) { int dirKey = pair.directionKey; if (dirKey == hKey.dirKey) { - HResult result = pair.hResult; + Result result = pair.hResult; - solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result)); - if (result instanceof HPending) { - HPending pending = (HPending)result; - for (HComponent component : pending.delta) { - for (HKey depKey : component.ids) { + solver.addEquation(new Equation(new EKey(hKey.method, dirKey, stable, false), result)); + if (result instanceof Pending) { + Pending pending = (Pending)result; + for (Component component : pending.delta) { + for (EKey depKey : component.ids) { if (!queued.contains(depKey)) { queue.push(depKey); queued.add(depKey); @@ -413,17 +412,16 @@ public class ProjectBytecodeAnalysis { } } - private void collectSingleEquation(HKey hKey, Solver solver) throws EquationsLimitException { + private void collectSingleEquation(EKey hKey, Solver solver) throws EquationsLimitException { ProgressManager.checkCanceled(); - Bytes bytes = new Bytes(hKey.key); - for (HEquations hEquations : getEquations(bytes)) { - boolean stable = hEquations.stable; - for (DirectionResultPair pair : hEquations.results) { + for (Equations equations : getEquations(hKey.method)) { + boolean stable = equations.stable; + for (DirectionResultPair pair : equations.results) { int dirKey = pair.directionKey; if (dirKey == hKey.dirKey) { - HResult result = pair.hResult; - solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result)); + Result result = pair.hResult; + solver.addEquation(new Equation(new EKey(hKey.method, dirKey, stable, false), result)); } } } @@ -440,13 +438,13 @@ public class ProjectBytecodeAnalysis { class MethodAnnotations { // @NotNull keys - final Set notNulls = new HashSet<>(1); + final Set notNulls = new HashSet<>(1); // @Nullable keys - final Set nullables = new HashSet<>(1); + final Set nullables = new HashSet<>(1); // @Contract(pure=true) part of contract - final Set pures = new HashSet<>(1); + final Set pures = new HashSet<>(1); // @Contracts - final Map contractsValues = new HashMap<>(); + final Map contractsValues = new HashMap<>(); } class ParameterAnnotations { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java index ea30ecf32557..5d24ca491aa8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java @@ -36,14 +36,14 @@ import java.util.*; */ public class PurityAnalysis { static final Set topEffect = Collections.singleton(EffectQuantum.TopEffectQuantum); - static final Set topHEffect = Collections.singleton(HEffectQuantum.TopEffectQuantum); + static final Set topHEffect = Collections.singleton(EffectQuantum.TopEffectQuantum); static final int UN_ANALYZABLE_FLAG = Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_INTERFACE; @NotNull public static Equation analyze(Method method, MethodNode methodNode, boolean stable) { - Key key = new Key(method, Direction.Pure, stable); - Set hardCodedSolution = HardCodedPurity.getHardCodedSolution(key); + EKey key = new EKey(method, Direction.Pure, stable); + Set hardCodedSolution = HardCodedPurity.getHardCodedSolution(method); if (hardCodedSolution != null) { return new Equation(key, new Effects(hardCodedSolution)); } @@ -170,81 +170,10 @@ abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysi }; } -interface EffectQuantum { - EffectQuantum TopEffectQuantum = new EffectQuantum() { - @Override - public String toString() { - return "Top"; - } - }; - EffectQuantum ThisChangeQuantum = new EffectQuantum() { - @Override - public String toString() { - return "Changes this"; - } - }; - - final class ParamChangeQuantum implements EffectQuantum { - final int n; - public ParamChangeQuantum(int n) { - this.n = n; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ParamChangeQuantum)) return false; - - return n == ((ParamChangeQuantum)o).n; - } - - @Override - public int hashCode() { - return n; - } - - @Override - public String toString() { - return "Changes param#" + n; - } - } - - final class CallQuantum implements EffectQuantum { - final @NotNull Key key; - final @NotNull DataValue[] data; - final boolean isStatic; - - public CallQuantum(@NotNull Key key, @NotNull DataValue[] data, boolean isStatic) { - this.key = key; - this.data = data; - this.isStatic = isStatic; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof CallQuantum)) return false; - - CallQuantum quantum = (CallQuantum)o; - return isStatic == quantum.isStatic && key.equals(quantum.key) && Arrays.equals(data, quantum.data); - } - - @Override - public int hashCode() { - return 31 * (31 * key.hashCode() + Arrays.hashCode(data)) + (isStatic ? 1 : 0); - } - - @Override - public String toString() { - return "Calls " + key; - } - } -} - -abstract class HEffectQuantum { +abstract class EffectQuantum { private final int myHash; - HEffectQuantum(int hash) { + EffectQuantum(int hash) { myHash = hash; } @@ -253,19 +182,19 @@ abstract class HEffectQuantum { return myHash; } - static final HEffectQuantum TopEffectQuantum = new HEffectQuantum(-1) { + static final EffectQuantum TopEffectQuantum = new EffectQuantum(-1) { @Override public String toString() { return "Top"; } }; - static final HEffectQuantum ThisChangeQuantum = new HEffectQuantum(-2) { + static final EffectQuantum ThisChangeQuantum = new EffectQuantum(-2) { @Override public String toString() { return "Changes this"; } }; - static class ParamChangeQuantum extends HEffectQuantum { + static class ParamChangeQuantum extends EffectQuantum { final int n; public ParamChangeQuantum(int n) { super(n); @@ -289,11 +218,11 @@ abstract class HEffectQuantum { return "Changes param#" + n; } } - static class CallQuantum extends HEffectQuantum { - final HKey key; + static class CallQuantum extends EffectQuantum { + final EKey key; final DataValue[] data; final boolean isStatic; - public CallQuantum(HKey key, DataValue[] data, boolean isStatic) { + public CallQuantum(EKey key, DataValue[] data, boolean isStatic) { super((key.hashCode() * 31 + Arrays.hashCode(data)) * 31 + (isStatic ? 1 : 0)); this.key = key; this.data = data; @@ -453,14 +382,15 @@ class DataInterpreter extends Interpreter { boolean stable = opCode == Opcodes.INVOKESPECIAL || opCode == Opcodes.INVOKESTATIC; MethodInsnNode mNode = ((MethodInsnNode)insn); DataValue[] data = values.toArray(new DataValue[0]); - Key key = new Key(new Method(mNode.owner, mNode.name, mNode.desc), Direction.Pure, stable); + Method method = new Method(mNode.owner, mNode.name, mNode.desc); + EKey key = new EKey(method, Direction.Pure, stable); EffectQuantum quantum = new EffectQuantum.CallQuantum(key, data, opCode == Opcodes.INVOKESTATIC); DataValue result = (ASMUtils.getReturnSizeFast(mNode.desc) == 1) ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2; - if (HardCodedPurity.isPureMethod(key)) { + if (HardCodedPurity.isPureMethod(method)) { quantum = null; result = DataValue.LocalDataValue; } - else if (HardCodedPurity.isThisChangingMethod(key)) { + else if (HardCodedPurity.isThisChangingMethod(method)) { DataValue receiver = ArrayUtil.getFirstElement(data); if (receiver == DataValue.ThisDataValue) { quantum = EffectQuantum.ThisChangeQuantum; @@ -468,7 +398,7 @@ class DataInterpreter extends Interpreter { else if (receiver == DataValue.LocalDataValue || receiver == DataValue.OwnedDataValue) { quantum = null; } - if (HardCodedPurity.isBuilderChainCall(key)) { + if (HardCodedPurity.isBuilderChainCall(method)) { // mostly to support string concatenation result = receiver; } @@ -575,16 +505,15 @@ final class HardCodedPurity { solutions.put(new Method("java/lang/Object", "hashCode", "()I"), Collections.emptySet()); } - static Set getHardCodedSolution(Key key) { - return isThisChangingMethod(key) ? thisChange : isPureMethod(key) ? Collections.emptySet() : solutions.get(key.method); + static Set getHardCodedSolution(Method method) { + return isThisChangingMethod(method) ? thisChange : isPureMethod(method) ? Collections.emptySet() : solutions.get(method); } - static boolean isThisChangingMethod(Key key) { - return isBuilderChainCall(key) || thisChangingMethods.contains(key.method); + static boolean isThisChangingMethod(Method method) { + return isBuilderChainCall(method) || thisChangingMethods.contains(method); } - static boolean isBuilderChainCall(Key key) { - Method method = key.method; + static boolean isBuilderChainCall(Method method) { // Those methods are virtual, thus contracts cannot be inferred automatically, // but all possible implementations are controlled // (only final classes j.l.StringBuilder and j.l.StringBuffer extend package-private j.l.AbstractStringBuilder) @@ -592,9 +521,9 @@ final class HardCodedPurity { method.methodName.startsWith("append"); } - static boolean isPureMethod(Key key) { - return key.method.methodName.equals("toString") && key.method.methodDesc.equals("()Ljava/lang/String;") || - pureMethods.contains(key.method); + static boolean isPureMethod(Method method) { + return method.methodName.equals("toString") && method.methodDesc.equals("()Ljava/lang/String;") || + pureMethods.contains(method); } static boolean isOwnedField(FieldInsnNode fieldInsn) { @@ -603,16 +532,16 @@ final class HardCodedPurity { } final class PuritySolver { - private HashMap> solved = new HashMap<>(); - private HashMap> dependencies = new HashMap<>(); - private final Stack moving = new Stack<>(); - private HashMap> pending = new HashMap<>(); + private HashMap> solved = new HashMap<>(); + private HashMap> dependencies = new HashMap<>(); + private final Stack moving = new Stack<>(); + private HashMap> pending = new HashMap<>(); - void addEquation(HKey key, Set effects) { - Set callKeys = new HashSet<>(); - for (HEffectQuantum effect : effects) { - if (effect instanceof HEffectQuantum.CallQuantum) { - callKeys.add(((HEffectQuantum.CallQuantum)effect).key); + void addEquation(EKey key, Set effects) { + Set callKeys = new HashSet<>(); + for (EffectQuantum effect : effects) { + if (effect instanceof EffectQuantum.CallQuantum) { + callKeys.add(((EffectQuantum.CallQuantum)effect).key); } } @@ -621,8 +550,8 @@ final class PuritySolver { moving.add(key); } else { pending.put(key, effects); - for (HKey callKey : callKeys) { - Set deps = dependencies.get(callKey); + for (EKey callKey : callKeys) { + Set deps = dependencies.get(callKey); if (deps == null) { deps = new HashSet<>(); dependencies.put(callKey, deps); @@ -632,41 +561,41 @@ final class PuritySolver { } } - public Map> solve() { + public Map> solve() { while (!moving.isEmpty()) { - HKey key = moving.pop(); - Set effects = solved.get(key); + EKey key = moving.pop(); + Set effects = solved.get(key); - HKey[] propagateKeys; + EKey[] propagateKeys; Set[] propagateEffects; if (key.stable) { - propagateKeys = new HKey[]{key, key.mkUnstable()}; + propagateKeys = new EKey[]{key, key.mkUnstable()}; propagateEffects = new Set[]{effects, effects}; } else { - propagateKeys = new HKey[]{key.mkStable(), key}; + propagateKeys = new EKey[]{key.mkStable(), key}; propagateEffects = new Set[]{effects, PurityAnalysis.topHEffect}; } for (int i = 0; i < propagateKeys.length; i++) { - HKey pKey = propagateKeys[i]; + EKey pKey = propagateKeys[i]; @SuppressWarnings("unchecked") - Set pEffects = propagateEffects[i]; - Set dKeys = dependencies.remove(pKey); + Set pEffects = propagateEffects[i]; + Set dKeys = dependencies.remove(pKey); if (dKeys != null) { - for (HKey dKey : dKeys) { - Set dEffects = pending.remove(dKey); + for (EKey dKey : dKeys) { + Set dEffects = pending.remove(dKey); if (dEffects == null) { // already solved, for example, solution is top continue; } - Set callKeys = new HashSet<>(); - Set newEffects = new HashSet<>(); - Set delta = null; + Set callKeys = new HashSet<>(); + Set newEffects = new HashSet<>(); + Set delta = null; - for (HEffectQuantum dEffect : dEffects) { - if (dEffect instanceof HEffectQuantum.CallQuantum) { - HEffectQuantum.CallQuantum call = ((HEffectQuantum.CallQuantum)dEffect); + for (EffectQuantum dEffect : dEffects) { + if (dEffect instanceof EffectQuantum.CallQuantum) { + EffectQuantum.CallQuantum call = ((EffectQuantum.CallQuantum)dEffect); if (call.key.equals(pKey)) { delta = substitute(pEffects, call.data, call.isStatic); newEffects.addAll(delta); @@ -702,29 +631,29 @@ final class PuritySolver { return solved; } - private static Set substitute(Set effects, DataValue[] data, boolean isStatic) { + private static Set substitute(Set effects, DataValue[] data, boolean isStatic) { if (effects.isEmpty() || PurityAnalysis.topHEffect.equals(effects)) { return effects; } - Set newEffects = new HashSet<>(effects.size()); + Set newEffects = new HashSet<>(effects.size()); int shift = isStatic ? 0 : 1; - for (HEffectQuantum effect : effects) { + for (EffectQuantum effect : effects) { DataValue arg = null; - if (effect == HEffectQuantum.ThisChangeQuantum) { + if (effect == EffectQuantum.ThisChangeQuantum) { arg = data[0]; - } else if (effect instanceof HEffectQuantum.ParamChangeQuantum) { - HEffectQuantum.ParamChangeQuantum paramChange = ((HEffectQuantum.ParamChangeQuantum)effect); + } else if (effect instanceof EffectQuantum.ParamChangeQuantum) { + EffectQuantum.ParamChangeQuantum paramChange = ((EffectQuantum.ParamChangeQuantum)effect); arg = data[paramChange.n + shift]; } if (arg == null || arg == DataValue.LocalDataValue) { continue; } if (arg == DataValue.ThisDataValue || arg == DataValue.OwnedDataValue) { - newEffects.add(HEffectQuantum.ThisChangeQuantum); + newEffects.add(EffectQuantum.ThisChangeQuantum); continue; } if (arg instanceof DataValue.ParameterDataValue) { - newEffects.add(new HEffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n)); + newEffects.add(new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n)); continue; } return PurityAnalysis.topHEffect; 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 ee8cbeec1ed7..877c45a1ca1d 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 @@ -17,7 +17,6 @@ package com.intellij.codeInspection.bytecodeAnalysis; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; import java.util.*; @@ -50,6 +49,7 @@ final class ELattice> { class ResultUtil { + private static final EKey[] EMPTY_PRODUCT = new EKey[0]; private final ELattice lattice; final Value top; ResultUtil(ELattice lattice) { @@ -57,7 +57,7 @@ class ResultUtil { top = lattice.top; } - Result join(Result r1, Result r2) throws AnalyzerException { + Result join(Result r1, Result r2) { if (r1 instanceof Final && ((Final) r1).value == top) { return r1; } @@ -68,160 +68,37 @@ class ResultUtil { return new Final(lattice.join(((Final) r1).value, ((Final) r2).value)); } if (r1 instanceof Final && r2 instanceof Pending) { - Final f1 = (Final)r1; - Pending pending = (Pending) r2; - Set sum1 = new HashSet<>(pending.sum); - sum1.add(new Product(f1.value, Collections.emptySet())); - return new Pending(sum1); + return addSingle((Pending)r2, (Final)r1); } if (r1 instanceof Pending && r2 instanceof Final) { - Final f2 = (Final)r2; - Pending pending = (Pending) r1; - Set sum1 = new HashSet<>(pending.sum); - sum1.add(new Product(f2.value, Collections.emptySet())); - return new Pending(sum1); + return addSingle((Pending)r1, (Final)r2); } + assert r1 instanceof Pending && r2 instanceof Pending; Pending pending1 = (Pending) r1; Pending pending2 = (Pending) r2; - Set sum = new HashSet<>(); - sum.addAll(pending1.sum); - sum.addAll(pending2.sum); - checkLimit(sum); + Set sum = new HashSet<>(); + sum.addAll(Arrays.asList(pending1.delta)); + sum.addAll(Arrays.asList(pending2.delta)); return new Pending(sum); } - private static void checkLimit(Set sum) throws AnalyzerException { - int size = sum.stream().mapToInt(prod -> prod.ids.size()).sum(); - if (size > Analysis.EQUATION_SIZE_LIMIT) { - throw new AnalyzerException(null, "Equation size is too big"); + @NotNull + private static Pending addSingle(Pending pending, Final result) { + Component component = new Component(result.value, EMPTY_PRODUCT); + if(ArrayUtil.contains(component, pending.delta)) { + return pending; } - } -} - -class HResultUtil { - private static final HKey[] EMPTY_PRODUCT = new HKey[0]; - private final ELattice lattice; - final Value top; - - HResultUtil(ELattice lattice) { - this.lattice = lattice; - top = lattice.top; - } - - HResult join(HResult r1, HResult r2) { - if (r1 instanceof HFinal && ((HFinal) r1).value == top) { - return r1; - } - if (r2 instanceof HFinal && ((HFinal) r2).value == top) { - return r2; - } - if (r1 instanceof HFinal && r2 instanceof HFinal) { - return new HFinal(lattice.join(((HFinal) r1).value, ((HFinal) r2).value)); - } - if (r1 instanceof HFinal && r2 instanceof HPending) { - HFinal f1 = (HFinal)r1; - HPending pending = (HPending) r2; - HComponent[] delta = new HComponent[pending.delta.length + 1]; - delta[0] = new HComponent(f1.value, EMPTY_PRODUCT); - System.arraycopy(pending.delta, 0, delta, 1, pending.delta.length); - return new HPending(delta); - } - if (r1 instanceof HPending && r2 instanceof HFinal) { - HFinal f2 = (HFinal)r2; - HPending pending = (HPending) r1; - HComponent[] delta = new HComponent[pending.delta.length + 1]; - delta[0] = new HComponent(f2.value, EMPTY_PRODUCT); - System.arraycopy(pending.delta, 0, delta, 1, pending.delta.length); - return new HPending(delta); - } - HPending pending1 = (HPending) r1; - HPending pending2 = (HPending) r2; - return new HPending(ArrayUtil.mergeArrays(pending1.delta, pending2.delta, HComponent.ARRAY_FACTORY)); - } -} - -final class Product { - @NotNull final Value value; - @NotNull final Set ids; - - Product(@NotNull Value value, @NotNull Set ids) { - this.value = value; - this.ids = ids; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Product product = (Product)o; - - if (!ids.equals(product.ids)) return false; - if (!value.equals(product.value)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = value.hashCode(); - result = 31 * result + ids.hashCode(); - return result; - } -} - -interface Result {} -final class Final implements Result { - final Value value; - Final(Value value) { - this.value = value; - } - - @Override - public String toString() { - return "Final{" + "value=" + value + '}'; - } -} - -final class Pending implements Result { - final Set sum; - - Pending(Set sum) { - this.sum = sum; - } - -} - -final class Effects implements Result { - final Set effects; - - Effects(Set effects) { - this.effects = effects; - } -} - -final class Equation { - final Key id; - final Result rhs; - - Equation(Key id, Result rhs) { - this.id = id; - this.rhs = rhs; - } - - @Override - public String toString() { - return "Equation{" + "id=" + id + ", rhs=" + rhs + '}'; + return new Pending(ArrayUtil.append(pending.delta, component)); } } final class CoreHKey { @NotNull - final byte[] key; + final MethodDescriptor myMethod; final int dirKey; - CoreHKey(@NotNull byte[] key, int dirKey) { - this.key = key; + CoreHKey(@NotNull MethodDescriptor method, int dirKey) { + this.myMethod = method; this.dirKey = dirKey; } @@ -231,83 +108,78 @@ final class CoreHKey { if (o == null || getClass() != o.getClass()) return false; CoreHKey coreHKey = (CoreHKey)o; - - if (dirKey != coreHKey.dirKey) return false; - if (!Arrays.equals(key, coreHKey.key)) return false; - return true; + return dirKey == coreHKey.dirKey && myMethod.equals(coreHKey.myMethod); } @Override public int hashCode() { - int result = Arrays.hashCode(key); - result = 31 * result + dirKey; - return result; + return 31 * myMethod.hashCode() + dirKey; } @Override public String toString() { - return "CoreHKey [" + HKey.bytesToString(key) + "|" + Direction.fromInt(dirKey) + "]"; + return "CoreHKey [" + myMethod + "|" + Direction.fromInt(dirKey) + "]"; } } final class Solver { private final ELattice lattice; - private final HashMap> dependencies = new HashMap<>(); - private final HashMap pending = new HashMap<>(); - private final HashMap solved = new HashMap<>(); - private final Stack moving = new Stack<>(); + private final HashMap> dependencies = new HashMap<>(); + private final HashMap pending = new HashMap<>(); + private final HashMap solved = new HashMap<>(); + private final Stack moving = new Stack<>(); - private final HResultUtil resultUtil; - private final HashMap equations = new HashMap<>(); + private final ResultUtil resultUtil; + private final HashMap equations = new HashMap<>(); private final Value unstableValue; Solver(ELattice lattice, Value unstableValue) { this.lattice = lattice; this.unstableValue = unstableValue; - resultUtil = new HResultUtil(lattice); + resultUtil = new ResultUtil(lattice); } - void addEquation(HEquation equation) { - HKey key = equation.key; - CoreHKey coreKey = new CoreHKey(key.key, key.dirKey); + void addEquation(Equation equation) { + EKey key = equation.key; + CoreHKey coreKey = new CoreHKey(key.method, key.dirKey); - HEquation previousEquation = equations.get(coreKey); + Equation previousEquation = equations.get(coreKey); if (previousEquation == null) { equations.put(coreKey, equation); } else { - HKey joinKey = new HKey(coreKey.key, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true); - HResult joinResult = resultUtil.join(equation.result, previousEquation.result); - HEquation joinEquation = new HEquation(joinKey, joinResult); + EKey joinKey = new EKey(coreKey.myMethod, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true); + Result joinResult = resultUtil.join(equation.result, previousEquation.result); + Equation joinEquation = new Equation(joinKey, joinResult); equations.put(coreKey, joinEquation); } } - void queueEquation(HEquation equation) { - HResult rhs = equation.result; - if (rhs instanceof HFinal) { - solved.put(equation.key, ((HFinal) rhs).value); + void queueEquation(Equation equation) { + Result rhs = equation.result; + if (rhs instanceof Final) { + solved.put(equation.key, ((Final) rhs).value); moving.push(equation.key); - } else if (rhs instanceof HPending) { - HPending pendResult = ((HPending)rhs).copy(); - HResult norm = normalize(pendResult.delta); - if (norm instanceof HFinal) { - solved.put(equation.key, ((HFinal) norm).value); + } else if (rhs instanceof Pending) { + Pending pendResult = ((Pending)rhs).copy(); + Result norm = normalize(pendResult.delta); + if (norm instanceof Final) { + solved.put(equation.key, ((Final) norm).value); moving.push(equation.key); } else { - HPending pendResult1 = ((HPending)rhs).copy(); - for (HComponent component : pendResult1.delta) { - for (HKey trigger : component.ids) { - HashSet set = dependencies.get(trigger); + Pending pendResult1 = ((Pending)rhs).copy(); + for (Component component : pendResult1.delta) { + for (EKey trigger : component.ids) { + HashSet set = dependencies.get(trigger); if (set == null) { set = new HashSet<>(); dependencies.put(trigger, set); } set.add(equation.key); } - pending.put(equation.key, pendResult1); } + pending.put(equation.key, pendResult1); } } } @@ -323,38 +195,38 @@ final class Solver { } } - Map solve() { - for (HEquation hEquation : equations.values()) { - queueEquation(hEquation); + Map solve() { + for (Equation equation : equations.values()) { + queueEquation(equation); } while (!moving.empty()) { - HKey id = moving.pop(); + EKey id = moving.pop(); Value value = solved.get(id); - HKey[] initialPIds = id.stable ? new HKey[]{id, id.invertStability()} : new HKey[]{id.invertStability(), id}; + EKey[] initialPIds = id.stable ? new EKey[]{id, id.invertStability()} : new EKey[]{id.invertStability(), id}; Value[] initialPVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue}; - HKey[] pIds = new HKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()}; + EKey[] pIds = new EKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()}; Value[] pVals = new Value[]{initialPVals[0], initialPVals[1], negate(initialPVals[0]), negate(initialPVals[1])}; for (int i = 0; i < pIds.length; i++) { - HKey pId = pIds[i]; + EKey pId = pIds[i]; Value pVal = pVals[i]; - HashSet dIds = dependencies.get(pId); + HashSet dIds = dependencies.get(pId); if (dIds == null) { continue; } - for (HKey dId : dIds) { - HPending pend = pending.remove(dId); + for (EKey dId : dIds) { + Pending pend = pending.remove(dId); if (pend != null) { - HResult pend1 = substitute(pend, pId, pVal); - if (pend1 instanceof HFinal) { - HFinal fi = (HFinal)pend1; + Result pend1 = substitute(pend, pId, pVal); + if (pend1 instanceof Final) { + Final fi = (Final)pend1; solved.put(dId, fi.value); moving.push(dId); } else { - pending.put(dId, (HPending)pend1); + pending.put(dId, (Pending)pend1); } } } @@ -365,9 +237,9 @@ final class Solver { } // substitute id -> value into pending - HResult substitute(@NotNull HPending pending, @NotNull HKey id, @NotNull Value value) { - HComponent[] sum = pending.delta; - for (HComponent intIdComponent : sum) { + Result substitute(@NotNull Pending pending, @NotNull EKey id, @NotNull Value value) { + Component[] sum = pending.delta; + for (Component intIdComponent : sum) { if (intIdComponent.remove(id)) { intIdComponent.value = lattice.meet(intIdComponent.value, value); } @@ -375,17 +247,17 @@ final class Solver { return normalize(sum); } - @NotNull HResult normalize(@NotNull HComponent[] sum) { + @NotNull Result normalize(@NotNull Component[] sum) { Value acc = lattice.bot; boolean computableNow = true; - for (HComponent prod : sum) { + for (Component prod : sum) { if (prod.isEmpty() || prod.value == lattice.bot) { acc = lattice.join(acc, prod.value); } else { computableNow = false; } } - return (acc == lattice.top || computableNow) ? new HFinal(acc) : new HPending(sum); + return (acc == lattice.top || computableNow) ? new Final(acc) : new Pending(sum); } } diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/io/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/io/annotations.xml index 34a6a4a6126d..501e4acba9e6 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/io/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/io/annotations.xml @@ -281,7 +281,6 @@ - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/invoke/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/invoke/annotations.xml index b262f24eb08a..27474b714e0e 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/invoke/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/invoke/annotations.xml @@ -343,11 +343,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/net/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/net/annotations.xml index 031f83ac9bd6..06389e049ca4 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/net/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/net/annotations.xml @@ -486,11 +486,6 @@ - - - - - @@ -537,11 +532,6 @@ - - - - - @@ -586,30 +576,15 @@ - - - - - - - - - - - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/util/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/util/annotations.xml index 3855ba366d51..be10209e353e 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/util/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/util/annotations.xml @@ -1673,11 +1673,6 @@ - - - - - @@ -1861,11 +1856,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/annotations.xml index f0e05764c0e5..96c51ad5f714 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/annotations.xml @@ -3119,19 +3119,9 @@ - - - - - - - - - - @@ -3217,11 +3207,6 @@ - - - - - @@ -3451,11 +3436,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/math/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/math/annotations.xml index 1b93c472138b..1d909a6cc68f 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/math/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/commons/lang/math/annotations.xml @@ -285,17 +285,12 @@ - - - - - - + @@ -359,11 +354,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/event/implement/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/event/implement/annotations.xml index 41541edcbe9c..6e3046458558 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/event/implement/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/event/implement/annotations.xml @@ -82,11 +82,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/tools/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/tools/annotations.xml index 6b8aabbe537e..0f4027750f37 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/tools/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/app/tools/annotations.xml @@ -34,11 +34,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/convert/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/convert/annotations.xml index ba4fa7874099..5915bed1fb47 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/convert/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/convert/annotations.xml @@ -15,11 +15,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/runtime/parser/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/runtime/parser/annotations.xml index 3a43f0af4e89..cabec0feb9a0 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/runtime/parser/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/runtime/parser/annotations.xml @@ -32,11 +32,6 @@ - - - - - diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/util/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/util/annotations.xml index 917605d4ecc1..adc0c8dcf55d 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/util/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/org/apache/velocity/util/annotations.xml @@ -169,11 +169,6 @@ - - - - - diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java index 77a05b5ee666..09556f3ee6b3 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java @@ -222,7 +222,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase { */ - HKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest); + EKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest); if (noKey) { assertTrue(null == psiKey); return; @@ -230,7 +230,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase { else { assertFalse(null == psiKey); } - HKey asmKey = BytecodeAnalysisConverter.asmKey(new Key(method, Direction.Out, true), myMessageDigest); + EKey asmKey = new EKey(method, Direction.Out, true).hashed(myMessageDigest); Assert.assertEquals(asmKey, psiKey); } From c8633ffc7c4a9d0e13889ba963bed7339725be71 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 30 May 2017 13:47:05 +0700 Subject: [PATCH 03/19] BytecodeAnalysis refactoring: hashing in single place; cosmetics --- .../BytecodeAnalysisConverter.java | 47 ++++--------------- .../BytecodeAnalysisIndex.java | 6 +-- .../codeInspection/bytecodeAnalysis/Data.java | 2 - .../bytecodeAnalysis/HMethod.java | 22 +++++++++ .../bytecodeAnalysis/Method.java | 15 +----- 5 files changed, 34 insertions(+), 58 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java index 3c1b97b43b50..810d46f31531 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 @@ -18,7 +18,6 @@ package com.intellij.codeInspection.bytecodeAnalysis; import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint; import com.intellij.codeInspection.dataFlow.StandardMethodContract; import com.intellij.openapi.util.ThreadLocalCachedValue; -import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.TypeConversionUtil; @@ -38,12 +37,6 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys */ public class BytecodeAnalysisConverter { - // how many bytes are taken from class fqn digest - public static final int CLASS_HASH_SIZE = 10; - // how many bytes are taken from signature digest - public static final int SIGNATURE_HASH_SIZE = 4; - public static final int HASH_SIZE = CLASS_HASH_SIZE + SIGNATURE_HASH_SIZE; - private static final ThreadLocalCachedValue HASHER_CACHE = new ThreadLocalCachedValue() { @Override public MessageDigest create() { @@ -65,49 +58,26 @@ public class BytecodeAnalysisConverter { } /** - * Converts a Psi method to a small hash key (Key). + * Converts a Psi method to a hashed EKey. * Returns null if conversion is impossible (something is not resolvable). */ @Nullable public static EKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) { - final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false); + final PsiClass psiClass = psiMethod.getContainingClass(); if (psiClass == null) { return null; } - byte[] classDigest = psiClassDigest(psiClass, md); - if (classDigest == null) { + String className = descriptor(psiClass, 0, false); + String methodSig = methodSignature(psiMethod); + if (className == null || methodSig == null) { return null; } - byte[] sigDigest = methodDigest(psiMethod, md); - if (sigDigest == null) { - return null; - } - byte[] digest = new byte[HASH_SIZE]; - System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE); - System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE); - return new EKey(new HMethod(digest), direction, true, false); + String methodName = psiMethod.getReturnType() == null ? "" : psiMethod.getName(); + return new EKey(new Method(className, methodName, methodSig).hashed(md), direction, true, false); } @Nullable - private static byte[] psiClassDigest(@NotNull PsiClass psiClass, @NotNull MessageDigest md) { - String descriptor = descriptor(psiClass, 0, false); - if (descriptor == null) { - return null; - } - return md.digest(descriptor.getBytes(CharsetToolkit.UTF8_CHARSET)); - } - - @Nullable - private static byte[] methodDigest(@NotNull PsiMethod psiMethod, @NotNull MessageDigest md) { - String descriptor = descriptor(psiMethod); - if (descriptor == null) { - return null; - } - return md.digest(descriptor.getBytes(CharsetToolkit.UTF8_CHARSET)); - } - - @Nullable - private static String descriptor(@NotNull PsiMethod psiMethod) { + private static String methodSignature(@NotNull PsiMethod psiMethod) { StringBuilder sb = new StringBuilder(); final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false); if (psiClass == null) { @@ -118,7 +88,6 @@ public class BytecodeAnalysisConverter { PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); PsiType returnType = psiMethod.getReturnType(); - sb.append(returnType == null ? "" : psiMethod.getName()); sb.append('('); String desc; 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 a4665a48fa8b..f058bc2c5d26 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 @@ -141,7 +141,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { @Override public HMethod read(@NotNull DataInput in) throws IOException { - byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE]; + byte[] bytes = new byte[HMethod.HASH_SIZE]; in.readFully(bytes); return new HMethod(bytes); } @@ -270,7 +270,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { effects.add(EffectQuantum.ThisChangeQuantum); } else if (effectMask == -3){ - byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE]; + byte[] bytes = new byte[HMethod.HASH_SIZE]; in.readFully(bytes); int rawDirKey = DataInputOutputUtil.readINT(in); boolean isStable = in.readBoolean(); @@ -324,7 +324,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { int componentSize = DataInputOutputUtil.readINT(in); EKey[] ids = new EKey[componentSize]; for (int j = 0; j < componentSize; j++) { - byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE]; + byte[] bytes = new byte[HMethod.HASH_SIZE]; in.readFully(bytes); int rawDirKey = DataInputOutputUtil.readINT(in); ids[j] = new EKey(new HMethod(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0); 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 0883ca6ca3a5..0e4e7947ee83 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 @@ -15,7 +15,6 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; -import com.intellij.util.ArrayFactory; import org.jetbrains.annotations.NotNull; import java.util.Arrays; @@ -28,7 +27,6 @@ import java.util.Set; */ final class Component { static final Component[] EMPTY_ARRAY = new Component[0]; - static final ArrayFactory ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new Component[count]; @NotNull Value value; @NotNull final EKey[] ids; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java index 0f615f8f6cbe..ad27cd199614 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/HMethod.java @@ -15,19 +15,41 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; +import com.intellij.openapi.vfs.CharsetToolkit; import one.util.streamex.IntStreamEx; import org.jetbrains.annotations.NotNull; import java.security.MessageDigest; import java.util.Arrays; +import static com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter.getMessageDigest; + /** * Hashed representation of method. */ public final class HMethod implements MethodDescriptor { + // how many bytes are taken from class fqn digest + private static final int CLASS_HASH_SIZE = 10; + // how many bytes are taken from signature digest + private static final int SIGNATURE_HASH_SIZE = 4; + static final int HASH_SIZE = CLASS_HASH_SIZE + SIGNATURE_HASH_SIZE; + @NotNull final byte[] myBytes; + HMethod(Method method, MessageDigest md) { + if (md == null) { + md = getMessageDigest(); + } + byte[] classDigest = md.digest(method.internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET)); + md.update(method.methodName.getBytes(CharsetToolkit.UTF8_CHARSET)); + md.update(method.methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET)); + byte[] sigDigest = md.digest(); + myBytes = new byte[HASH_SIZE]; + System.arraycopy(classDigest, 0, myBytes, 0, CLASS_HASH_SIZE); + System.arraycopy(sigDigest, 0, myBytes, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE); + } + public HMethod(@NotNull byte[] bytes) { myBytes = bytes; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java index c2a7134c58ae..7c06399f9f88 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Method.java @@ -15,15 +15,12 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; -import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; import java.security.MessageDigest; -import static com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter.*; - public final class Method implements MethodDescriptor { final String internalClassName; final String methodName; @@ -72,17 +69,7 @@ public final class Method implements MethodDescriptor { @NotNull @Override public HMethod hashed(@Nullable MessageDigest md) { - if (md == null) { - md = getMessageDigest(); - } - byte[] classDigest = md.digest(internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET)); - md.update(methodName.getBytes(CharsetToolkit.UTF8_CHARSET)); - md.update(methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET)); - byte[] sigDigest = md.digest(); - byte[] digest = new byte[HASH_SIZE]; - System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE); - System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE); - return new HMethod(digest); + return new HMethod(this, md); } @Override From 99efef718cb3a780a902475d762566a0d36b3caf Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 30 May 2017 10:08:45 +0300 Subject: [PATCH 04/19] build scripts: fixed 'addModule' method It was broken after change of JpsProjectLoader.loadModules signature. --- .../src/org/jetbrains/jps/gant/JpsGantTool.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jps/standalone-builder/src/org/jetbrains/jps/gant/JpsGantTool.groovy b/jps/standalone-builder/src/org/jetbrains/jps/gant/JpsGantTool.groovy index f5764fd67573..a7672a5d8486 100644 --- a/jps/standalone-builder/src/org/jetbrains/jps/gant/JpsGantTool.groovy +++ b/jps/standalone-builder/src/org/jetbrains/jps/gant/JpsGantTool.groovy @@ -115,7 +115,7 @@ final class JpsGantTool { private static JpsModule addModule(File imlFile, JpsModel model, JpsGantProjectBuilder builder) { def pathVariables = JpsModelSerializationDataService.computeAllPathVariables(model.global) - def modules = JpsProjectLoader.loadModules(Collections.singletonList(imlFile), JpsJavaSdkType.INSTANCE, pathVariables) + def modules = JpsProjectLoader.loadModules(Collections.singletonList(imlFile.toPath()), JpsJavaSdkType.INSTANCE, pathVariables) def module = modules.get(0) model.project.addModule(module) builder.info("Module ${module.getName()} added to the project") From 6dc53d56330590a04c80df3576d8d1704965d17b Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Tue, 30 May 2017 09:28:40 +0200 Subject: [PATCH 05/19] correctly work with multithreaded find --- platform/lang-impl/src/com/intellij/find/impl/FindDialog.java | 2 +- .../lang-impl/src/com/intellij/find/impl/FindPopupPanel.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java index 55d2f01ad031..4bea6bf3b55e 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java @@ -492,7 +492,7 @@ public class FindDialog extends DialogWrapper implements FindUI { final FindUsagesProcessPresentation processPresentation = FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation); - Ref lastUsageFileRef = new Ref<>(); + ThreadLocal lastUsageFileRef = new ThreadLocal<>(); FindInProjectUtil.findUsages(findModel, myProject, info -> { if(isCancelled()) { diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java b/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java index 41fe1f038dfa..d8eed5277106 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java @@ -823,8 +823,8 @@ public class FindPopupPanel extends JBPanel implements FindUI, DataProvider { final FindUsagesProcessPresentation processPresentation = FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation); - Ref lastUsageFileRef = new Ref<>(); - Ref recentUsageRef = new Ref<>(); + ThreadLocal lastUsageFileRef = new ThreadLocal<>(); + ThreadLocal recentUsageRef = new ThreadLocal<>(); FindInProjectUtil.findUsages(myHelper.getModel().clone(), myProject, info -> { if(isCancelled()) { From 0a69dfd5f49469a1e0ab55856f8888500261a94c Mon Sep 17 00:00:00 2001 From: Alexander Kass Date: Mon, 29 May 2017 19:18:41 +0300 Subject: [PATCH 06/19] DBE: fix resolve settings for consoles DBE-4652 --- .../com/intellij/lang/PerFileMappingsBase.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/platform/projectModel-api/src/com/intellij/lang/PerFileMappingsBase.java b/platform/projectModel-api/src/com/intellij/lang/PerFileMappingsBase.java index 2b7608a27f8c..206fc2f631a4 100644 --- a/platform/projectModel-api/src/com/intellij/lang/PerFileMappingsBase.java +++ b/platform/projectModel-api/src/com/intellij/lang/PerFileMappingsBase.java @@ -107,16 +107,20 @@ public abstract class PerFileMappingsBase implements PersistentStateComponent if (t != null) return t; t = getMappingForHierarchy(originalFile, mappings); if (t != null) return t; - Project project = getProject(); - if (project == null || file == null || - file.getFileSystem() instanceof NonPhysicalFileSystem || - ProjectFileIndex.getInstance(project).isInContent(file)) { - return mappings.get(null); - } - return null; + return getNotInHierarchy(file, mappings); } } + @Nullable + protected T getNotInHierarchy(@Nullable VirtualFile file, @NotNull Map mappings) { + if (getProject() == null || file == null || + file.getFileSystem() instanceof NonPhysicalFileSystem || + ProjectFileIndex.getInstance(getProject()).isInContent(file)) { + return mappings.get(null); + } + return null; + } + private static T getMappingForHierarchy(@Nullable VirtualFile file, @NotNull Map mappings) { for (VirtualFile cur = file; cur != null; cur = cur.getParent()) { T t = mappings.get(cur); From 8f73ccc2b4ec4e6ab54cf4998d37ab5dbac2408a Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 29 May 2017 13:53:38 +0200 Subject: [PATCH 07/19] =?UTF-8?q?tryLoadRootElement=20=E2=80=94=20do=20not?= =?UTF-8?q?=20attempt=20to=20load=20file=20if=20file=20not=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/serialization/JpsLoaderBase.java | 55 +++++++++++-------- .../model/serialization/JpsMacroExpander.java | 5 +- .../model/serialization/JpsProjectLoader.java | 22 +++----- .../JpsRunConfigurationSerializer.java | 10 +++- 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java index d8dbf90805ac..a198b5cf712d 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java @@ -21,11 +21,13 @@ import com.intellij.openapi.util.SystemInfo; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.TimingLog; import org.jetbrains.jps.model.JpsElement; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; /** @@ -40,6 +42,10 @@ public abstract class JpsLoaderBase { myMacroExpander = macroExpander; } + /** + * Returns null if file doesn't exist + */ + @Nullable protected Element loadRootElement(@NotNull Path file) { return loadRootElement(file, myMacroExpander); } @@ -51,14 +57,7 @@ public abstract class JpsLoaderBase { String fileName = serializer.getConfigFileName(); Path configFile = dir.resolve(fileName != null ? fileName : defaultFileName); Runnable timingLog = TimingLog.startActivity("loading: " + configFile.getFileName() + ":" + serializer.getComponentName()); - Element componentTag; - if (Files.exists(configFile)) { - componentTag = JDomSerializationUtil.findComponent(loadRootElement(configFile), serializer.getComponentName()); - } - else { - componentTag = null; - } - + Element componentTag = JDomSerializationUtil.findComponent(loadRootElement(configFile), serializer.getComponentName()); if (componentTag != null) { serializer.loadExtension(element, componentTag); } @@ -68,35 +67,45 @@ public abstract class JpsLoaderBase { timingLog.run(); } - protected static Element loadRootElement(@NotNull Path file, final JpsMacroExpander macroExpander) { - try { - final Element element = tryLoadRootElement(file); + /** + * Returns null if file doesn't exist + */ + @Nullable + protected static Element loadRootElement(@NotNull Path file, @NotNull JpsMacroExpander macroExpander) { + final Element element = tryLoadRootElement(file); + if (element != null) { macroExpander.substitute(element, SystemInfo.isFileSystemCaseSensitive); - return element; - } - catch (JDOMException e) { - throw new CannotLoadJpsModelException(file.toFile(), "Cannot parse xml file " + file.toAbsolutePath() + ": " + e.getMessage(), e); - } - catch (IOException e) { - throw new CannotLoadJpsModelException(file.toFile(), "Cannot read file " + file.toAbsolutePath() + ": " + e.getMessage(), e); } + return element; } - private static Element tryLoadRootElement(@NotNull Path file) throws IOException, JDOMException { - for (int i = 0; i < MAX_ATTEMPTS - 1; i++) { + @Nullable + private static Element tryLoadRootElement(@NotNull Path file) { + int i = 0; + while (true) { try { return JDOMUtil.load(Files.newBufferedReader(file)); } - catch (Exception e) { + catch (NoSuchFileException e) { + return null; + } + catch (IOException | JDOMException e) { + if (++i == MAX_ATTEMPTS) { + //noinspection InstanceofCatchParameter + throw new CannotLoadJpsModelException(file.toFile(), "Cannot " + (e instanceof IOException ? "read" : "parse") + " file " + file.toAbsolutePath() + ": " + e.getMessage(), e); + } + LOG.info("Loading attempt #" + i + " failed", e); } + //most likely configuration file is being written by IDE so we'll wait a little try { //noinspection BusyWait Thread.sleep(300); } - catch (InterruptedException ignored) { } + catch (InterruptedException ignored) { + return null; + } } - return JDOMUtil.load(Files.newBufferedReader(file)); } } diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsMacroExpander.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsMacroExpander.java index e3d83bad6d73..6695b62241ad 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsMacroExpander.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsMacroExpander.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -19,6 +19,7 @@ import com.intellij.openapi.components.ExpandMacroToPathMap; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import org.jdom.Element; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -59,7 +60,7 @@ public class JpsMacroExpander { } } - public void substitute(Element element, boolean caseSensitive) { + public void substitute(@NotNull Element element, boolean caseSensitive) { myExpandMacroMap.substitute(element, caseSensitive); } diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java index e75084c0c986..1c82c8b821e8 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java @@ -142,11 +142,7 @@ public class JpsProjectLoader extends JpsLoaderBase { for (Path configurationFile : listXmlFiles(dir.resolve("runConfigurations"))) { JpsRunConfigurationSerializer.loadRunConfigurations(myProject, loadRootElement(configurationFile)); } - Path workspaceFile = dir.resolve("workspace.xml"); - if (Files.exists(workspaceFile)) { - Element runManager = JDomSerializationUtil.findComponent(loadRootElement(workspaceFile), "RunManager"); - JpsRunConfigurationSerializer.loadRunConfigurations(myProject, runManager); - } + JpsRunConfigurationSerializer.loadRunConfigurations(myProject, JDomSerializationUtil.findComponent(loadRootElement(dir.resolve("workspace.xml")), "RunManager")); runConfTimingLog.run(); } } @@ -178,7 +174,7 @@ public class JpsProjectLoader extends JpsLoaderBase { String projectName = FileUtil.getNameWithoutExtension(iprFile.getFileName().toString()); myProject.setName(projectName); Path iwsFile = iprFile.getParent().resolve(projectName + ".iws"); - Element iwsRoot = Files.exists(iwsFile) ? loadRootElement(iwsFile) : null; + Element iwsRoot = loadRootElement(iwsFile); JpsSdkType projectSdkType = loadProjectRoot(iprRoot); for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) { @@ -207,7 +203,7 @@ public class JpsProjectLoader extends JpsLoaderBase { } @Nullable - private JpsSdkType loadProjectRoot(Element root) { + private JpsSdkType loadProjectRoot(@Nullable Element root) { JpsSdkType sdkType = null; Element rootManagerElement = JDomSerializationUtil.findComponent(root, "ProjectRootManager"); if (rootManagerElement != null) { @@ -225,7 +221,7 @@ public class JpsProjectLoader extends JpsLoaderBase { JpsLibraryTableSerializer.loadLibraries(libraryTableElement, myProject.getLibraryCollection()); } - private void loadModules(Element root, final @Nullable JpsSdkType projectSdkType) { + private void loadModules(@Nullable Element root, final @Nullable JpsSdkType projectSdkType) { Runnable timingLog = TimingLog.startActivity("loading modules"); Element componentRoot = JDomSerializationUtil.findComponent(root, "ProjectModuleManager"); if (componentRoot == null) return; @@ -263,14 +259,10 @@ public class JpsProjectLoader extends JpsLoaderBase { futureModuleFilesContents.add(ourThreadPool.submit(() -> { final JpsMacroExpander expander = createModuleMacroExpander(pathVariables, file); - Element data = null; - if (Files.exists(file)) { - data = loadRootElement(file, expander); - } - + Element data = loadRootElement(file, expander); Path externalPath = externalModuleDir == null ? null : externalModuleDir.resolve(FileUtilRt.getNameWithoutExtension(file.getFileName().toString()) + ".xml"); - if (externalPath != null && Files.exists(externalPath)) { - Element externalData = loadRootElement(externalPath, expander); + Element externalData = externalPath == null ? null : loadRootElement(externalPath, expander); + if (externalData != null) { if (data == null) { data = externalData; } diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/runConfigurations/JpsRunConfigurationSerializer.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/runConfigurations/JpsRunConfigurationSerializer.java index ea2dc017512b..418518866d53 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/runConfigurations/JpsRunConfigurationSerializer.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/runConfigurations/JpsRunConfigurationSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -26,6 +26,7 @@ import org.jetbrains.jps.model.JpsElementFactory; import org.jetbrains.jps.model.JpsProject; import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; +import java.util.List; import java.util.Map; /** @@ -35,6 +36,11 @@ public class JpsRunConfigurationSerializer { private static final Logger LOG = Logger.getInstance(JpsRunConfigurationSerializer.class); public static void loadRunConfigurations(@NotNull JpsProject project, @Nullable Element runManagerTag) { + List elements = JDOMUtil.getChildren(runManagerTag, "configuration"); + if (elements.isEmpty()) { + return; + } + Map> serializers = new HashMap<>(); for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) { for (JpsRunConfigurationPropertiesSerializer serializer : extension.getRunConfigurationPropertiesSerializers()) { @@ -42,7 +48,7 @@ public class JpsRunConfigurationSerializer { } } - for (Element configurationTag : JDOMUtil.getChildren(runManagerTag, "configuration")) { + for (Element configurationTag : elements) { if (Boolean.parseBoolean(configurationTag.getAttributeValue("default"))) { continue; } From a23f2a431a14006f92401ec1b13f45bc4fc7242e Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 29 May 2017 13:59:35 +0200 Subject: [PATCH 08/19] reduce dir.resolve for default config files --- .../jps/model/serialization/JpsGlobalLoader.java | 11 ++++++----- .../jps/model/serialization/JpsLoaderBase.java | 4 ++-- .../jps/model/serialization/JpsProjectLoader.java | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java index 4f525406635d..5ab57a25f8b0 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java @@ -55,7 +55,7 @@ public class JpsGlobalLoader extends JpsLoaderBase { public static void loadGlobalSettings(JpsGlobal global, String optionsPath) throws IOException { Path optionsDir = Paths.get(FileUtil.toCanonicalPath(optionsPath)); - new JpsGlobalLoader(global, Collections.emptyMap()).loadGlobalComponents(optionsDir, new PathVariablesSerializer()); + new JpsGlobalLoader(global, Collections.emptyMap()).loadGlobalComponents(optionsDir, optionsDir.resolve("other.xml"), new PathVariablesSerializer()); Map pathVariables = JpsModelSerializationDataService.computeAllPathVariables(global); new JpsGlobalLoader(global, pathVariables).load(optionsDir); } @@ -69,19 +69,20 @@ public class JpsGlobalLoader extends JpsLoaderBase { } private void load(@NotNull Path optionsDir) { + Path defaultConfigFile = optionsDir.resolve("other.xml"); LOG.debug("Loading config from " + optionsDir.toAbsolutePath()); for (JpsGlobalExtensionSerializer serializer : SERIALIZERS) { - loadGlobalComponents(optionsDir, serializer); + loadGlobalComponents(optionsDir, defaultConfigFile, serializer); } for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) { for (JpsGlobalExtensionSerializer serializer : extension.getGlobalExtensionSerializers()) { - loadGlobalComponents(optionsDir, serializer); + loadGlobalComponents(optionsDir, defaultConfigFile, serializer); } } } - private void loadGlobalComponents(@NotNull Path optionsDir, JpsGlobalExtensionSerializer serializer) { - loadComponents(optionsDir, "other.xml", serializer, myGlobal); + private void loadGlobalComponents(@NotNull Path optionsDir, @NotNull Path defaultConfigFile, JpsGlobalExtensionSerializer serializer) { + loadComponents(optionsDir, defaultConfigFile.getParent(), serializer, myGlobal); } public static class PathVariablesSerializer extends JpsGlobalExtensionSerializer { diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java index a198b5cf712d..2211f06e6872 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsLoaderBase.java @@ -51,11 +51,11 @@ public abstract class JpsLoaderBase { } protected void loadComponents(@NotNull Path dir, - final String defaultFileName, + @NotNull Path defaultConfigFile, JpsElementExtensionSerializerBase serializer, final E element) { String fileName = serializer.getConfigFileName(); - Path configFile = dir.resolve(fileName != null ? fileName : defaultFileName); + Path configFile = fileName == null ? defaultConfigFile : dir.resolve(fileName); Runnable timingLog = TimingLog.startActivity("loading: " + configFile.getFileName() + ":" + serializer.getComponentName()); Element componentTag = JDomSerializationUtil.findComponent(loadRootElement(configFile), serializer.getComponentName()); if (componentTag != null) { diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java index 1c82c8b821e8..289ca95555dc 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java @@ -117,10 +117,11 @@ public class JpsProjectLoader extends JpsLoaderBase { private void loadFromDirectory(@NotNull Path dir) { myProject.setName(getDirectoryBaseProjectName(dir)); - JpsSdkType projectSdkType = loadProjectRoot(loadRootElement(dir.resolve("misc.xml"))); + Path defaultConfigFile = dir.resolve("misc.xml"); + JpsSdkType projectSdkType = loadProjectRoot(loadRootElement(defaultConfigFile)); for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) { for (JpsProjectExtensionSerializer serializer : extension.getProjectExtensionSerializers()) { - loadComponents(dir, "misc.xml", serializer, myProject); + loadComponents(dir, defaultConfigFile, serializer, myProject); } } loadModules(loadRootElement(dir.resolve("modules.xml")), projectSdkType); From 135eee942e69fb8183bc7d758eb6701cdf96d11e Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 29 May 2017 15:19:32 +0200 Subject: [PATCH 09/19] =?UTF-8?q?External=20project=20storage=20=E2=80=94?= =?UTF-8?q?=20JPS=20support=20libraries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../intellij/compiler/server/BuildManager.java | 2 +- .../org/jetbrains/jps/api/GlobalOptions.java | 7 ++++++- .../model/serialization/JpsProjectLoader.java | 18 +++++++++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java index b3b67f114ff8..4a6c0e189394 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java @@ -1141,7 +1141,7 @@ public class BuildManager implements Disposable { } if (StreamProviderKt.isExternalStorageEnabled()) { - cmdLine.addParameter("-Dexternal.project.config=" + ProjectUtil.getExternalConfigurationDir(project)); + cmdLine.addParameter("-D" + GlobalOptions.EXTERNAL_PROJECT_CONFIG + "=" + ProjectUtil.getExternalConfigurationDir(project)); } final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION); diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java index 0e7e003033ec..bcb650e19a74 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -38,4 +38,9 @@ public interface GlobalOptions { String JPS_SYSTEM_BUILDER_ID = "JPS"; // notification about the files changed during compilation, but not compiled in current compilation session String JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID = "!unprocessed_fs_changes_detected!"; + + /** + * The path to external project config directory (used for external system projects). + */ + String EXTERNAL_PROJECT_CONFIG = "external.project.config"; } diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java index 289ca95555dc..63fbcdccc34e 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java @@ -130,6 +130,13 @@ public class JpsProjectLoader extends JpsLoaderBase { for (Path libraryFile : listXmlFiles(dir.resolve("libraries"))) { loadProjectLibraries(loadRootElement(libraryFile)); } + + Path externalConfigDir = resolveExternalProjectConfig("project"); + if (externalConfigDir != null) { + LOG.info("External project config dir is used: " + externalConfigDir); + loadProjectLibraries(loadRootElement(externalConfigDir.resolve("libraries.xml"))); + } + timingLog.run(); Runnable artifactsTimingLog = TimingLog.startActivity("loading artifacts"); @@ -245,15 +252,20 @@ public class JpsProjectLoader extends JpsLoaderBase { timingLog.run(); } + @Nullable + private static Path resolveExternalProjectConfig(@NotNull String subDirName) { + String externalProjectConfigDir = System.getProperty("external.project.config"); + return StringUtil.isEmptyOrSpaces(externalProjectConfigDir) ? null : Paths.get(externalProjectConfigDir, subDirName); + } + @NotNull public static List loadModules(@NotNull List moduleFiles, @Nullable final JpsSdkType projectSdkType, @NotNull final Map pathVariables) { List modules = new ArrayList<>(); List>> futureModuleFilesContents = new ArrayList<>(); - String externalProjectConfigDir = System.getProperty("external.project.config"); - Path externalModuleDir = StringUtil.isEmptyOrSpaces(externalProjectConfigDir) ? null : Paths.get(externalProjectConfigDir, "modules"); + Path externalModuleDir = resolveExternalProjectConfig("modules"); if (externalModuleDir != null) { - LOG.info("External project config dir is used: " + externalProjectConfigDir); + LOG.info("External project config dir is used for modules: " + externalModuleDir); } for (Path file : moduleFiles) { From 5e8d36181ccf1bbbc310020d182b2d1914e592d6 Mon Sep 17 00:00:00 2001 From: Sergey Ignatov Date: Tue, 30 May 2017 11:46:34 +0300 Subject: [PATCH 10/19] introduce java tests --- platform/testFramework/testSrc/tests/testGroups.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform/testFramework/testSrc/tests/testGroups.properties b/platform/testFramework/testSrc/tests/testGroups.properties index 8c8f9abf21e9..7170758a2630 100644 --- a/platform/testFramework/testSrc/tests/testGroups.properties +++ b/platform/testFramework/testSrc/tests/testGroups.properties @@ -58,6 +58,9 @@ com.intellij.junit4.JUnit4IntegrationTest [GUI_TESTS] com.intellij.testGuiFramework.tests.* +[JAVA_TESTS] +com.intellij.java.* + [GROOVY_TESTS] org.jetbrains.plugins.groovy.* From 8da03edcea7faa51c39a8143f90b4add0f3bcef0 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Tue, 30 May 2017 11:51:28 +0300 Subject: [PATCH 11/19] check for file read-only status in IntroduceEmptyVariableHandler (following IDEA-CR-21447) --- .../introduceVariable/IntroduceEmptyVariableHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java index 153683a0ed7b..90ee54d91a7e 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java @@ -45,6 +45,7 @@ public class IntroduceEmptyVariableHandler { public void invoke(@NotNull Editor editor, @NotNull PsiFile file, @NotNull PsiType type) { Project project = file.getProject(); + if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return; int offset = editor.getCaretModel().getOffset(); PsiElement at = file.findElementAt(offset); PsiElement anchorStatement = RefactoringUtil.getParentStatement(at, false); From e8123cfd7419a072862e7bb70e528a9f8b106847 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Tue, 30 May 2017 10:40:38 +0200 Subject: [PATCH 12/19] more trace for unregistered serializers --- .../intellij/psi/stubs/StubSerializationHelper.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java index abc6b4bb578c..c296c834505c 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java @@ -19,6 +19,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.LogUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; +import com.intellij.psi.tree.IElementType; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.RecentStringInterner; import com.intellij.util.io.AbstractStringEnumerator; @@ -114,7 +115,14 @@ public class StubSerializationHelper { private int getClassId(final ObjectStubSerializer serializer) { final int idValue = mySerializerToId.get(serializer); - assert idValue > 0: "No ID found for serializer " + LogUtil.objectAndClass(serializer); + if (idValue <= 0) { + assert idValue > 0 : "No ID found for serializer " + + LogUtil.objectAndClass(serializer) + + ", external id:" + + serializer.getExternalId() + + (serializer instanceof IElementType ? ", language:" + ((IElementType)serializer).getLanguage() : "") + ; + } return idValue; } From c6be97c5843251a95558416f855055ac4dcb5b94 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Tue, 30 May 2017 10:58:12 +0200 Subject: [PATCH 13/19] dump more information about unregistered serializer --- .../intellij/psi/stubs/StubSerializationHelper.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java index c296c834505c..2080aee5b95d 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubSerializationHelper.java @@ -116,11 +116,12 @@ public class StubSerializationHelper { private int getClassId(final ObjectStubSerializer serializer) { final int idValue = mySerializerToId.get(serializer); if (idValue <= 0) { - assert idValue > 0 : "No ID found for serializer " + - LogUtil.objectAndClass(serializer) + - ", external id:" + - serializer.getExternalId() + - (serializer instanceof IElementType ? ", language:" + ((IElementType)serializer).getLanguage() : "") + assert false : "No ID found for serializer " + + LogUtil.objectAndClass(serializer) + + ", external id:" + + serializer.getExternalId() + + (serializer instanceof IElementType ? + ", language:" + ((IElementType)serializer).getLanguage() + ", " + serializer : "") ; } return idValue; From b232b7f4bbf12c612cb4a9ea723119b17b16ee34 Mon Sep 17 00:00:00 2001 From: Denis Fokin Date: Tue, 30 May 2017 12:15:13 +0300 Subject: [PATCH 14/19] Enable dark decorations on FrameWrapper (Mac OS X) --- .../src/com/intellij/openapi/ui/FrameWrapper.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java b/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java index 3872e1cb9d90..ade47b18950a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java @@ -141,6 +141,9 @@ public class FrameWrapper implements Disposable, DataProvider { } else { ((JDialog)frame).setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); } + + ((RootPaneContainer)frame).getRootPane().putClientProperty("jetbrains.awt.windowDarkAppearance" , UIUtil.isUnderDarcula()); + final WindowAdapter focusListener = new WindowAdapter() { @Override public void windowOpened(WindowEvent e) { From 9a8e861f4967933da32e568c26a578410d371114 Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Tue, 30 May 2017 12:42:44 +0300 Subject: [PATCH 15/19] AbstractExternalSystemTaskManager deprecation message (IJSDK-251) --- .../externalSystem/task/AbstractExternalSystemTaskManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/AbstractExternalSystemTaskManager.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/AbstractExternalSystemTaskManager.java index b98bbd6bae6a..249765228b28 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/AbstractExternalSystemTaskManager.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/AbstractExternalSystemTaskManager.java @@ -27,8 +27,9 @@ import java.util.List; /** * @author Vladislav.Soroka * @since 12/19/13 + * + * @deprecated use {@link ExternalSystemTaskManager} interface */ -@Deprecated public abstract class AbstractExternalSystemTaskManager implements ExternalSystemTaskManager { public abstract void executeTasks(@NotNull ExternalSystemTaskId id, From b33909520547d5a50e88f62b7ff8b828657f0b73 Mon Sep 17 00:00:00 2001 From: "Irina.Chernushina" Date: Mon, 29 May 2017 22:10:41 +0200 Subject: [PATCH 16/19] json schema: simplify test --- .../JsonSchemaCrossReferencesTest.java | 77 +++---------------- 1 file changed, 12 insertions(+), 65 deletions(-) diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java index 5ed06493f7a7..d04b8feaa2db 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java @@ -36,6 +36,7 @@ import com.jetbrains.jsonSchema.ide.JsonSchemaService; import com.jetbrains.jsonSchema.impl.JsonSchemaObject; import com.jetbrains.jsonSchema.impl.JsonSchemaReferenceContributor; import com.jetbrains.jsonSchema.schemaFile.TestJsonSchemaMappingsProjectConfiguration; +import org.jetbrains.annotations.NotNull; import org.junit.Assert; import java.util.*; @@ -52,7 +53,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { return BASE_PATH; } - @CanChangeDocumentDuringHighlighting public void testJsonSchemaCrossReferenceCompletion() throws Exception { skeleton(new Callback() { @Override @@ -95,7 +95,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { assertStringItems(strings); } - @CanChangeDocumentDuringHighlighting public void testRefreshSchemaCompletionSimpleVariant() throws Exception { skeleton(new Callback() { private String myModuleDir; @@ -126,7 +125,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testJsonSchemaCrossReferenceCompletionWithSchemaEditing() throws Exception { skeleton(new Callback() { private String myModuleDir; @@ -197,7 +195,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { assertStringItems("\"one1\"", "\"two1\""); } - @CanChangeDocumentDuringHighlighting public void testJsonSchemaRefsCrossResolve() throws Exception { skeleton(new Callback() { @Override @@ -233,7 +230,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testJsonSchemaGlobalRefsCrossResolve() throws Exception { skeleton(new Callback() { @Override @@ -270,7 +266,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testJson2SchemaPropertyResolve() throws Exception { skeleton(new Callback() { @Override @@ -313,7 +308,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testFindRefInOtherFile() throws Exception { skeleton(new Callback() { @Override @@ -343,7 +337,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testFindRefToOtherFile() throws Exception { skeleton(new Callback() { @Override @@ -374,7 +367,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNavigateToPropertyDefinitionInPackageJsonSchema() throws Exception { skeleton(new Callback() { @Override @@ -405,7 +397,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNavigateToPropertyDefinitionNestedDefinitions() throws Exception { skeleton(new Callback() { @Override @@ -434,7 +425,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNavigateToAllOfOneOfDefinitions() throws Exception { skeleton(new Callback() { @Override @@ -465,7 +455,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNestedAllOneAnyWithInheritanceNavigation() throws Exception { final String prefix = "nestedAllOneAnyWithInheritance/"; skeleton(new Callback() { @@ -496,7 +485,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNestedAllOneAnyWithInheritanceCompletion() throws Exception { final String prefix = "nestedAllOneAnyWithInheritance/"; skeleton(new Callback() { @@ -521,7 +509,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNestedAllOneAnyWithInheritanceHighlighting() throws Exception { final String prefix = "nestedAllOneAnyWithInheritance/"; skeleton(new Callback() { @@ -546,7 +533,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNavigateToDefinitionByRef() throws Exception { skeleton(new Callback() { @Override @@ -606,7 +592,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNavigateFromSchemaDefinitionToMainSchema() throws Exception { skeleton(new Callback() { @Override @@ -637,7 +622,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { }); } - @CanChangeDocumentDuringHighlighting public void testNavigateToRefInsideMainSchema() throws Exception { final JsonSchemaService service = JsonSchemaService.Impl.get(myProject); final List providers = new JsonSchemaProjectSelfProviderFactory().getProviders(myProject); @@ -672,7 +656,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { Assert.assertEquals("positiveInteger", ((JsonProperty) resolve.getParent()).getName()); } - @CanChangeDocumentDuringHighlighting public void testNavigateToDefinitionByRefInFileWithIncorrectReference() throws Exception { skeleton(new Callback() { @Override @@ -691,16 +674,20 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { @Override public void doCheck() { final String midia = "midia"; - checkNavigationIntoDefinition(midia); + checkNavigationTo(midia, JsonSchemaObject.DEFINITIONS); } }); } - private void checkNavigationIntoDefinition(String name) { + private void checkNavigationTo(@NotNull String name, @NotNull String base) { int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset(); final PsiElement element = myFile.findElementAt(offset); Assert.assertNotNull(element); + checkNavigationTo(name, offset, base); + } + + private void checkNavigationTo(@NotNull String name, int offset, @NotNull String base) { final PsiReference referenceAt = myFile.findReferenceAt(offset); Assert.assertNotNull(referenceAt); final PsiElement resolve = referenceAt.resolve(); @@ -710,10 +697,9 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { Assert.assertTrue(parent instanceof JsonProperty); Assert.assertEquals(name, ((JsonProperty)parent).getName()); Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty); - Assert.assertEquals(JsonSchemaObject.DEFINITIONS, ((JsonProperty)parent.getParent().getParent()).getName()); + Assert.assertEquals(base, ((JsonProperty)parent.getParent().getParent()).getName()); } - @CanChangeDocumentDuringHighlighting public void testInsideCycledSchemaNavigation() throws Exception { skeleton(new Callback() { @Override @@ -730,12 +716,11 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { @Override public void doCheck() { - checkNavigationIntoDefinition("all"); + checkNavigationTo("all", JsonSchemaObject.DEFINITIONS); } }); } - @CanChangeDocumentDuringHighlighting public void testNavigationIntoCycledSchema() throws Exception { skeleton(new Callback() { @Override @@ -753,20 +738,7 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { @Override public void doCheck() { - int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset(); - final PsiElement element = myFile.findElementAt(offset); - Assert.assertNotNull(element); - - final PsiReference referenceAt = myFile.findReferenceAt(offset); - Assert.assertNotNull(referenceAt); - final PsiElement resolve = referenceAt.resolve(); - Assert.assertNotNull(resolve); - Assert.assertEquals("\"bbb\"", resolve.getText()); - final PsiElement parent = resolve.getParent(); - Assert.assertTrue(parent instanceof JsonProperty); - Assert.assertEquals("bbb", ((JsonProperty)parent).getName()); - Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty); - Assert.assertEquals("properties", ((JsonProperty)parent.getParent().getParent()).getName()); + checkNavigationTo("bbb", JsonSchemaObject.PROPERTIES); } }); } @@ -792,19 +764,7 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { final List list = strings.stream() .filter(expression -> expression.getText().contains("#/definitions")).collect(Collectors.toList()); Assert.assertEquals(3, list.size()); - list.forEach(literal -> { - final PsiReference ref = myFile.findReferenceAt(literal.getTextRange().getStartOffset() + 1); - Assert.assertNotNull(ref); - final PsiElement resolve = ref.resolve(); - Assert.assertNotNull(literal.getText(), resolve); - Assert.assertTrue(resolve.isValid()); - Assert.assertEquals("\"cycle.schema\"", resolve.getText()); - final PsiElement parent = resolve.getParent(); - Assert.assertTrue(parent instanceof JsonProperty); - Assert.assertEquals("cycle.schema", ((JsonProperty)parent).getName()); - Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty); - Assert.assertEquals("definitions", ((JsonProperty)parent.getParent().getParent()).getName()); - }); + list.forEach(literal -> checkNavigationTo("cycle.schema", literal.getTextRange().getStartOffset() + 1, JsonSchemaObject.DEFINITIONS)); } }); } @@ -827,20 +787,7 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { @Override public void doCheck() { - int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset(); - final PsiElement element = myFile.findElementAt(offset); - Assert.assertNotNull(element); - - final PsiReference referenceAt = myFile.findReferenceAt(offset); - Assert.assertNotNull(referenceAt); - final PsiElement resolve = referenceAt.resolve(); - Assert.assertNotNull(resolve); - Assert.assertEquals("\"id\"", resolve.getText()); - final PsiElement parent = resolve.getParent(); - Assert.assertTrue(parent instanceof JsonProperty); - Assert.assertEquals("id", ((JsonProperty)parent).getName()); - Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty); - Assert.assertEquals(JsonSchemaObject.PROPERTIES, ((JsonProperty)parent.getParent().getParent()).getName()); + checkNavigationTo("id", JsonSchemaObject.PROPERTIES); } }); } From 18a495861e888b1167f2f7a9bb106513a74b8fc3 Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Tue, 30 May 2017 12:58:35 +0300 Subject: [PATCH 17/19] Pass vmOptions and scriptParameters to the deprecated method of ExternalSystemTaskManager --- .../externalSystem/task/ExternalSystemTaskManager.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java index 570654cb6248..a3b981173d8b 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java @@ -19,10 +19,10 @@ import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; import java.util.List; /** @@ -52,7 +52,9 @@ public interface ExternalSystemTaskManager vmOptions = settings == null ? ContainerUtil.emptyList() : ContainerUtil.newArrayList(settings.getVmOptions()); + List arguments = settings == null ? ContainerUtil.emptyList() : ContainerUtil.newArrayList(settings.getArguments()); + executeTasks(id, taskNames, projectPath, settings, vmOptions, arguments, jvmAgentSetup, listener); } boolean cancelTask(@NotNull ExternalSystemTaskId id, From 9d8991c7cfcc2aea0c97e967984f371fb161ea65 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Tue, 30 May 2017 12:41:09 +0300 Subject: [PATCH 18/19] moving context services to platform --- .../src/META-INF/PlatformExtensionPoints.xml | 3 ++ .../src/META-INF/PlatformExtensions.xml | 10 +++++++ plugins/git4idea/git4idea.iml | 1 + .../git4idea/branch/GitBranchContextTest.kt | 28 +++++++++++++++++++ .../tasks/tasks-core/src/META-INF/plugin.xml | 9 ------ 5 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 plugins/git4idea/tests/git4idea/branch/GitBranchContextTest.kt diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index de92652e8522..4419b87ce75b 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -286,5 +286,8 @@ + + + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index f7e78e9662a0..0a383fea75a1 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -490,5 +490,15 @@ + + + + + + + + + + diff --git a/plugins/git4idea/git4idea.iml b/plugins/git4idea/git4idea.iml index 47b0422100b7..e69fb5bc32b5 100644 --- a/plugins/git4idea/git4idea.iml +++ b/plugins/git4idea/git4idea.iml @@ -54,5 +54,6 @@ + \ No newline at end of file diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchContextTest.kt b/plugins/git4idea/tests/git4idea/branch/GitBranchContextTest.kt new file mode 100644 index 000000000000..084715a911e4 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/branch/GitBranchContextTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2017 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 git4idea.branch + +import com.intellij.tasks.context.WorkingContextManager +import git4idea.test.GitPlatformTest +import junit.framework.TestCase + +class GitBranchContextTest: GitPlatformTest() { + + fun testContextManager() { + val contextManager = WorkingContextManager.getInstance(project) + TestCase.assertNotNull(contextManager) + } +} \ No newline at end of file diff --git a/plugins/tasks/tasks-core/src/META-INF/plugin.xml b/plugins/tasks/tasks-core/src/META-INF/plugin.xml index 88e3e1b2ade6..d98076e1c6aa 100644 --- a/plugins/tasks/tasks-core/src/META-INF/plugin.xml +++ b/plugins/tasks/tasks-core/src/META-INF/plugin.xml @@ -96,7 +96,6 @@ - @@ -111,19 +110,11 @@ - - - - - - - - From 895fd03c027ee445054bf3b3f841317f2a94f597 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 30 May 2017 11:14:16 +0200 Subject: [PATCH 19/19] don't show memory-disk conflict dialog if the conflict is already resolved --- .../openapi/fileEditor/impl/MemoryDiskConflictResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/MemoryDiskConflictResolver.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/MemoryDiskConflictResolver.java index 369440ebac12..48ec4fa8297c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/MemoryDiskConflictResolver.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/MemoryDiskConflictResolver.java @@ -82,7 +82,7 @@ class MemoryDiskConflictResolver { for (VirtualFile file : conflicts) { Document document = FileDocumentManager.getInstance().getCachedDocument(file); - if (document != null && askReloadFromDisk(file, document)) { + if (document != null && file.getModificationStamp() != document.getModificationStamp() && askReloadFromDisk(file, document)) { FileDocumentManager.getInstance().reloadFromDisk(document); } }