Java: reduce call stack usage in the parser for extremely large else-if chains (IDEA-305898)

GitOrigin-RevId: 9d7be3efbe108e604fbd67ce03c7fdfe23c3fb3a
This commit is contained in:
Bas Leijdekkers
2024-09-20 13:38:16 +02:00
committed by intellij-monorepo-bot
parent 8966e5a5b5
commit 3e7bc7ef47
3 changed files with 3294 additions and 21 deletions

View File

@@ -18,6 +18,8 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import static com.intellij.lang.PsiBuilderUtil.*;
import static com.intellij.lang.java.parser.BasicJavaParserUtil.*;
import static com.intellij.psi.impl.source.BasicElementTypes.BASIC_JAVA_COMMENT_OR_WHITESPACE_BIT_SET;
@@ -305,23 +307,37 @@ public class BasicStatementParser {
@NotNull
private PsiBuilder.Marker parseIfStatement(PsiBuilder builder) {
PsiBuilder.Marker statement = builder.mark();
builder.advanceLexer();
ArrayList<PsiBuilder.Marker> stack = null;
PsiBuilder.Marker statement;
while (true) {
// replaced recursion with iteration plus stack to avoid huge call stack for extremely large else-if chains
statement = builder.mark();
builder.advanceLexer();
if (parseExprInParenth(builder)) {
PsiBuilder.Marker thenStatement = parseStatement(builder);
if (thenStatement == null) {
error(builder, JavaPsiBundle.message("expected.statement"));
}
else if (expect(builder, JavaTokenType.ELSE_KEYWORD)) {
PsiBuilder.Marker elseStatement = parseStatement(builder);
if (elseStatement == null) {
if (parseExprInParenth(builder)) {
if (parseStatement(builder) == null) {
error(builder, JavaPsiBundle.message("expected.statement"));
}
else if (expect(builder, JavaTokenType.ELSE_KEYWORD)) {
if (builder.getTokenType() == JavaTokenType.IF_KEYWORD) {
if (stack == null) stack = new ArrayList<>();
stack.add(statement);
continue;
}
else if (parseStatement(builder) == null) {
error(builder, JavaPsiBundle.message("expected.statement"));
}
}
}
break;
}
done(statement, myJavaElementTypeContainer.IF_STATEMENT, myWhiteSpaceAndCommentSetHolder);
if (stack != null) {
for (int i = stack.size() - 1; i >= 0; i--) {
statement = stack.get(i);
done(statement, myJavaElementTypeContainer.IF_STATEMENT, myWhiteSpaceAndCommentSetHolder);
}
}
done(statement, myJavaElementTypeContainer.IF_STATEMENT, myWhiteSpaceAndCommentSetHolder);
return statement;
}

View File

@@ -1,30 +1,44 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.parser.statementParsing;
import com.intellij.java.parser.AbstractBasicJavaParsingTestCase;
import com.intellij.java.parser.AbstractBasicJavaParsingTestConfigurator;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
public abstract class AbstractBasicIfParsingTest extends AbstractBasicJavaParsingTestCase {
public AbstractBasicIfParsingTest(@NotNull AbstractBasicJavaParsingTestConfigurator configurator) {
super("parser-full/statementParsing/if", configurator);
}
public void testNormalWithElse() { doTest(true); }
public void testNormalNoElse() { doTest(true); }
public void testUncomplete1() { doTest(true); }
public void testUncomplete2() { doTest(true); }
public void testUncomplete3() { doTest(true); }
public void testUncomplete4() { doTest(true); }
public void testUncomplete5() { doTest(true); }
public void testUncomplete6() { doTest(true); }
public void testUncomplete7() { doTest(true); }
public void testBigIf() throws IOException {
String name = getTestName();
PsiFile file = createPsiFile(name, loadFile(name + "." + myFileExt));
var visitor = new PsiRecursiveElementWalkingVisitor() {
int count = 0;
@Override
public void visitElement(@NotNull PsiElement element) {
// visit all elements to make sure the file is parsed, because createPsiFile() is lazy
super.visitElement(element);
count++;
}
};
file.accept(visitor);
// psi tree is too big and too deeply nested to fit a debug string into memory
assertEquals(46946, visitor.count);
}
}

File diff suppressed because it is too large Load Diff