diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/PathReferenceTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/PathReferenceTest.java index fcc7171e82bb..632b3c8ba990 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/PathReferenceTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/PathReferenceTest.java @@ -2,7 +2,10 @@ package com.intellij.java.codeInsight.daemon; import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; public class PathReferenceTest extends LightJavaCodeInsightFixtureTestCase { @@ -17,4 +20,14 @@ public class PathReferenceTest extends LightJavaCodeInsightFixtureTestCase { PsiReference[] references = reference.getElement().getReferences(); assertEquals(1, references.length); } + + public void testDeepPathDoesNotOverflowStack() { + // resolving reference #i used to recurse into #i-1, so a path with many segments blew the stack + String path = "dir/".repeat(20_000) + "file.txt"; + PsiFile file = myFixture.configureByText("a.txt", path); + FileReferenceSet set = new FileReferenceSet(path, file, 0, null, true); + FileReference last = set.getLastReference(); + assertNotNull(last); + assertEmpty(last.multiResolve(false)); + } } diff --git a/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java b/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java index 4426ad9f8cb0..6d41cc93bf9a 100644 --- a/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java +++ b/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java @@ -66,6 +66,12 @@ public class FileReference implements PsiFileReference, FileReferenceOwner, PsiP public static final FileReference[] EMPTY = new FileReference[0]; + /** + * The number of path segments beyond which the context chain is resolved iteratively + * instead of relying on the recursion in {@link #getContexts()}. + */ + private static final int ITERATIVE_CONTEXT_RESOLVE_THRESHOLD = 64; + private final int myIndex; private TextRange myRange; private final String myText; @@ -134,6 +140,24 @@ public class FileReference implements PsiFileReference, FileReferenceOwner, PsiP return ResolveCache.getInstance(file.getProject()).resolveWithCaching(this, MyResolver.INSTANCE, false, false, file); } + /** + * {@link #getContexts()} resolves the context reference, which resolves its own context reference and so on down to + * index 0, so a path with many segments would exhaust the stack. Resolve a long chain bottom-up instead: every level + * then finds its context reference already cached and returns without recursing. + *
+ * Called only on a cache miss, from {@link MyResolver#INSTANCE}. The references resolved here go through
+ * {@link MyResolver#WITHOUT_CONTEXT_CHAIN} so that they don't walk the chain again.
+ */
+ private void resolveContextChain(@NotNull PsiFile containingFile) {
+ if (myIndex < ITERATIVE_CONTEXT_RESOLVE_THRESHOLD) return;
+ ResolveCache cache = ResolveCache.getInstance(containingFile.getProject());
+ FileReference[] references = myFileReferenceSet.getAllReferences();
+ int limit = Math.min(myIndex, references.length);
+ for (int i = 0; i < limit; i++) {
+ cache.resolveWithCaching(references[i], MyResolver.WITHOUT_CONTEXT_CHAIN, false, false, containingFile);
+ }
+ }
+
protected ResolveResult @NotNull [] innerResolve(boolean caseSensitive, @NotNull PsiFile containingFile) {
final String referenceText = getText();
if (referenceText.isEmpty() && myIndex == 0) {
@@ -599,11 +623,23 @@ public class FileReference implements PsiFileReference, FileReferenceOwner, PsiP
return myFileReferenceSet.getLastReference();
}
- private static class MyResolver implements ResolveCache.PolyVariantContextResolver