diff --git a/java/java-tests/testSrc/com/intellij/psi/StubAstSwitchTest.groovy b/java/java-tests/testSrc/com/intellij/psi/StubAstSwitchTest.groovy index 84ef157983b7..f499391ce805 100644 --- a/java/java-tests/testSrc/com/intellij/psi/StubAstSwitchTest.groovy +++ b/java/java-tests/testSrc/com/intellij/psi/StubAstSwitchTest.groovy @@ -27,6 +27,7 @@ import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubTree import com.intellij.psi.util.PsiTreeUtil import com.intellij.reference.SoftReference +import com.intellij.testFramework.LeakHunter import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.util.GCUtil @@ -161,8 +162,8 @@ class B { assert file.treeElement GCUtil.tryGcSoftlyReachableObjects() - assert !file.stub assert !file.treeElement + assert file.stub assert psiClass.nameIdentifier assert !file.stub @@ -190,8 +191,8 @@ class B { assert file.classes[0].nameIdentifier GCUtil.tryGcSoftlyReachableObjects() - assert !((PsiFileImpl)file).getTreeElement() - assert !((PsiFileImpl)file).getStub() + assert !((PsiFileImpl)file).treeElement + assertNoStubLoaded(file) assert file.classes[0].methods[0].modifierList.hasExplicitModifier(PsiModifier.STATIC) assert !((PsiFileImpl)file).getTreeElement() @@ -204,11 +205,12 @@ class B { GCUtil.tryGcSoftlyReachableObjects() assert !file.treeElement - assert !file.greenStub - - assert PsiAnchor.create(psiClass) instanceof PsiAnchor.StubIndexReference + assertNoStubLoaded(file) StubElement hardRefToStub = file.greenStub assert hardRefToStub + assert hardRefToStub == file.stub + + assert file.node GCUtil.tryGcSoftlyReachableObjects() assert !file.treeElement @@ -218,6 +220,10 @@ class B { assert !file.treeElement } + private static assertNoStubLoaded(PsiFile file) { + LeakHunter.checkLeak(file, StubTree) { candidate -> candidate.root.psi == file } + } + void "test node is not deeply parsed when loaded in green stub presence"() { PsiFileImpl file = (PsiFileImpl)myFixture.addFileToProject("a.java", "class A{}") def stubTree = file.stubTree diff --git a/platform/core-api/src/com/intellij/psi/stubs/StubBase.java b/platform/core-api/src/com/intellij/psi/stubs/StubBase.java index 420c4918cce9..8fef8a9f67bb 100644 --- a/platform/core-api/src/com/intellij/psi/stubs/StubBase.java +++ b/platform/core-api/src/com/intellij/psi/stubs/StubBase.java @@ -21,7 +21,6 @@ package com.intellij.psi.stubs; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLock; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.util.ArrayFactory; @@ -86,6 +85,11 @@ public abstract class StubBase extends ObjectStubBase extends ASTDelegateP myStubIndex = stubIndex; } + /** + * Don't invoke this method, it's public for implementation reasons. + */ + public int getStubIndex() { + return myStubIndex; + } + /** * Don't invoke this method, it's public for implementation reasons. */ diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java b/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java index d1368f93a795..48d822bc4b7f 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java @@ -30,6 +30,8 @@ import org.jetbrains.annotations.Nullable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; import java.util.concurrent.ConcurrentMap; /** @@ -90,6 +92,17 @@ class AstPathPsiMap { return psi; } + List> getAllCachedPsi() { + myQueue.cleanupStaleReferences(); + if (myMap.isEmpty()) return Collections.emptyList(); + + List> result = ContainerUtil.newArrayList(); + for (MyReference reference : myMap.values()) { + ContainerUtil.addIfNotNull(result, reference.get()); + } + return result; + } + private static class MyReference extends WeakReference> { final AstPath pathRef; diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index b92cf84896df..4ce29475763d 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -703,7 +703,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF public StubTree getStubTree() { ApplicationManager.getApplication().assertReadAccessAllowed(); - if (myAstLoaded) return null; + if (myAstLoaded && !mayReloadStub()) return null; if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null; final StubTree derefd = derefStub(); @@ -763,7 +763,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF } else { stubTree.setDebugInfo("created in getStubTree(), no AST"); if (eachPsiRoot == this) stubHolder = stubTree; - eachPsiRoot.myStub = new SoftReference(stubTree); + eachPsiRoot.setStubTree(stubTree); } eachPsiRoot.putUserData(ObjectStubTree.LAST_STUB_TREE_HASH, null); } @@ -772,11 +772,31 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF } } + private boolean mayReloadStub() { + StubTreeLoader loader = StubTreeLoader.getInstance(); + if (loader != null && loader.isStubReloadingProhibited()) { + return false; + } + return getTreeElement() == null && !useStrongRefs(); + } + @Nullable private StubTree derefStub() { return SoftReference.dereference(myStub); } + private void setStubTree(StubTree stubTree) { + for (StubBasedPsiElementBase psi : myRefToPsi.getAllCachedPsi()) { + int index = psi.getStubIndex(); + if (index >= 0) { + //noinspection unchecked + ((StubBase)stubTree.getPlainList().get(index)).setPsi(psi); + } + } + myStub = new SoftReference(stubTree); + myAstLoaded = false; + } + protected PsiFileImpl cloneImpl(FileElement treeElementClone) { PsiFileImpl clone = (PsiFileImpl)super.clone(); clone.myRefToPsi = new AstPathPsiMap(getProject()); diff --git a/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java b/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java index 8be6801b007b..6752bdfbc078 100644 --- a/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java +++ b/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java @@ -46,7 +46,11 @@ public abstract class StubTreeLoader { @Nullable public abstract ObjectStubTree readFromVFile(Project project, final VirtualFile vFile); - + + public boolean isStubReloadingProhibited() { + return false; + } + public abstract void rebuildStubTree(VirtualFile virtualFile); public abstract boolean canHaveStub(VirtualFile file); diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java index 196a63914632..ae9a5e13d4ff 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java @@ -27,6 +27,7 @@ import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.util.RecursionManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.impl.source.PsiFileImpl; @@ -43,6 +44,7 @@ import java.util.List; */ public class StubTreeLoaderImpl extends StubTreeLoader { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.StubTreeLoaderImpl"); + private static volatile boolean ourStubReloadingProhibited = false; @Override @Nullable @@ -114,8 +116,9 @@ public class StubTreeLoaderImpl extends StubTreeLoader { catch (SerializerNotFoundException e) { return processError(vFile, "No stub serializer: " + vFile.getPresentableUrl() + ": " + e.getMessage(), e); } - ObjectStubTree tree = stub instanceof PsiFileStub ? new StubTree((PsiFileStub)stub) : new ObjectStubTree((ObjectStubBase)stub, true); + ObjectStubTree tree = stub instanceof PsiFileStub ? new StubTree((PsiFileStub)stub) : new ObjectStubTree((ObjectStubBase)stub, true); tree.setDebugInfo("created from index"); + checkDeserializationCreatesNoPsi(tree); return tree; } else if (size != 0) { @@ -126,6 +129,25 @@ public class StubTreeLoaderImpl extends StubTreeLoader { return null; } + private static void checkDeserializationCreatesNoPsi(ObjectStubTree tree) { + if (ourStubReloadingProhibited) return; + + for (Stub each : tree.getPlainListFromAllRoots()) { + if (each instanceof StubBase) { + PsiElement cachedPsi = ((StubBase)each).getCachedPsi(); + if (cachedPsi != null) { + ourStubReloadingProhibited = true; + throw new AssertionError("Stub deserialization shouldn't create PSI: " + cachedPsi + "; " + each); + } + } + } + } + + @Override + public boolean isStubReloadingProhibited() { + return ourStubReloadingProhibited; + } + private static int getCurrentTextContentLength(Project project, VirtualFile vFile, Document document) { if (vFile.getFileType().isBinary()) { return -1;