mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-251723 resolve long FileReference context chains iteratively
Resolving a FileReference recursed into its context reference (the preceding path segment), which recursed into its own, down to index 0 at ~15 stack frames per segment. A path with thousands of segments -- a generated path, a long command line in a string literal, a base64 data URI -- therefore ended in a StackOverflowError. Neither existing guard applies: multiResolve asks ResolveCache not to prevent recursion, and the RecursionManager key in innerResolve is the reference instance, so it only catches a reference re-entering itself, never a chain of distinct references. Beyond 64 segments the chain is now resolved bottom-up before the reference itself, so every level finds its context reference already cached and returns without recursing. The loop resolves through a second resolver instance that skips the walk; otherwise every level would rescan the whole prefix and the pre-warm would cost O(n^2) cache lookups. GitOrigin-RevId: 62b0966db7ff35395197d5149e53bf8354e92fd7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9d256f09c4
commit
363bd15762
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+38
-2
@@ -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.
|
||||
* <p>
|
||||
* 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<FileReference> {
|
||||
static final MyResolver INSTANCE = new MyResolver();
|
||||
private static final class MyResolver implements ResolveCache.PolyVariantContextResolver<FileReference> {
|
||||
static final MyResolver INSTANCE = new MyResolver(true);
|
||||
|
||||
/** Used while a context chain is being resolved bottom-up, see {@link FileReference#resolveContextChain}. */
|
||||
static final MyResolver WITHOUT_CONTEXT_CHAIN = new MyResolver(false);
|
||||
|
||||
private final boolean myResolveContextChain;
|
||||
|
||||
private MyResolver(boolean resolveContextChain) {
|
||||
myResolveContextChain = resolveContextChain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolveResult @NotNull [] resolve(@NotNull FileReference ref, @NotNull PsiFile containingFile, boolean incompleteCode) {
|
||||
if (myResolveContextChain) {
|
||||
ref.resolveContextChain(containingFile);
|
||||
}
|
||||
return ref.innerResolve(ref.getFileReferenceSet().isCaseSensitive(), containingFile);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user