diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Scanner.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Scanner.java index b68cb30b7ad..9ec7abb84c3 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Scanner.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Scanner.java @@ -1870,7 +1870,10 @@ else if (c >= LOW_SURROGATE_MIN_VALUE && c <= LOW_SURROGATE_MAX_VALUE) { } } //-----------------end switch while try-------------------- catch (IndexOutOfBoundsException e) { - if (this.tokenizeWhiteSpace && (whiteStart != this.currentPosition - 1)) { + if (this.currentPosition < 0) { + this.currentPosition = 0; + return TokenNameInvalid; + } else if (this.tokenizeWhiteSpace && (whiteStart != this.currentPosition - 1)) { // reposition scanner in case we are interested by spaces as tokens this.currentPosition--; this.startPosition = whiteStart; diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter18Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter18Test.java index b1e89b85b44..4436fbf2b04 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter18Test.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter18Test.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.List; +import java.util.Locale; import junit.framework.Test; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.BindingKey; @@ -29,8 +30,16 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.*; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; import org.eclipse.jdt.internal.core.ResolvedBinaryMethod; +import org.eclipse.jdt.internal.core.dom.SourceRangeVerifier; +import org.eclipse.jdt.internal.core.util.CommentRecorderParser; @SuppressWarnings({"rawtypes"}) public class ASTConverter18Test extends ConverterTestSetup { @@ -5513,4 +5522,80 @@ public void acceptBinding(String bindingKey, IBinding binding) { parser.createASTs(new ICompilationUnit[] {this.workingCopy}, new String[0], requestor, null); } +public void testIssue4712() throws JavaModelException { + String contents = + """ + public class Test { + public String test() { + // comment; insert System.out.println + return "'test' called"; + } + } + """; + this.workingCopy = getWorkingCopy("/Converter18/src/test432051/X.java", contents, true/*computeProblems*/); + CompilerOptions options = new CompilerOptions(); + options.sourceLevel = ClassFileConstants.JDK25; + options.complianceLevel = ClassFileConstants.JDK25; + options.docCommentSupport = true; + CommentRecorderParser parser = + new CommentRecorderParser( + new ProblemReporter( + DefaultErrorHandlingPolicies.proceedWithAllProblems(), + options, + new DefaultProblemFactory(Locale.getDefault())), + false); + char[] sourceChars = this.workingCopy.getSource().toCharArray(); + org.eclipse.jdt.internal.compiler.batch.CompilationUnit unit = + new org.eclipse.jdt.internal.compiler.batch.CompilationUnit(sourceChars, this.workingCopy.getElementName(), null); + CompilationResult compilationResult = new CompilationResult(unit, 0, 0, 10); + CompilationUnitDeclaration cud = parser.parse(unit, compilationResult); + org.eclipse.jdt.internal.compiler.ast.TypeDeclaration typeDecl = cud.types[0]; + char[][] tokens = { "System".toCharArray(), "out".toCharArray() }; + long[] positions = { 0L, 0L }; + org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference systemOut = + new org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference(tokens, positions, 0, 0); + org.eclipse.jdt.internal.compiler.ast.MessageSend printlnCall = + new org.eclipse.jdt.internal.compiler.ast.MessageSend(); + printlnCall.receiver = systemOut; + printlnCall.selector = "println".toCharArray(); + printlnCall.arguments = null; + printlnCall.sourceStart = typeDecl.sourceStart; + printlnCall.sourceEnd = typeDecl.sourceEnd; + printlnCall.nameSourcePosition = 0L; + org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration testMethod = null; + for (org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration method : typeDecl.methods) { + if (new String(method.selector).equals("test")) { + testMethod = method; + break; + } + } + org.eclipse.jdt.internal.compiler.ast.Statement[] stmts = new org.eclipse.jdt.internal.compiler.ast.Statement[2]; + stmts[0] = printlnCall; + stmts[1] = testMethod.statements[0]; + testMethod.statements = stmts; + int reconcileFlags = ICompilationUnit.ENABLE_STATEMENTS_RECOVERY + | ICompilationUnit.ENABLE_BINDINGS_RECOVERY + | ICompilationUnit.FORCE_PROBLEM_DETECTION; + boolean oldDebug = SourceRangeVerifier.DEBUG; + boolean oldDebugThrow = SourceRangeVerifier.DEBUG_THROW; + try { + SourceRangeVerifier.DEBUG = false; + SourceRangeVerifier.DEBUG_THROW = false; + long start = System.currentTimeMillis(); + AST.convertCompilationUnit( + AST.getJLSLatest(), + cud, + options.getMap(), + false, + (org.eclipse.jdt.internal.core.CompilationUnit) this.workingCopy, + reconcileFlags, + null + ); + // The convertCompilationUnit method would need to take less than one second to complete. + assertTrue((System.currentTimeMillis() - start) < 1000); + } finally { + SourceRangeVerifier.DEBUG = oldDebug; + SourceRangeVerifier.DEBUG_THROW = oldDebugThrow; + } +} } diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnit.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnit.java index 1acfe114969..93939ff27f2 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnit.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnit.java @@ -751,8 +751,10 @@ public int lastTrailingCommentIndex(ASTNode node) { * @since 3.0 */ void initCommentMapper(Scanner scanner) { - this.commentMapper = new DefaultCommentMapper(this.optionalCommentTable); - this.commentMapper.initialize(this, scanner); + if (this.optionalCommentTable != null && this.optionalCommentTable.length > 0) { + this.commentMapper = new DefaultCommentMapper(this.optionalCommentTable); + this.commentMapper.initialize(this, scanner); + } } @Override diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DefaultCommentMapper.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DefaultCommentMapper.java index 52ba7986336..59b1c33bbcd 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DefaultCommentMapper.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DefaultCommentMapper.java @@ -562,13 +562,18 @@ class CommentMapperVisitor extends DefaultASTVisitor { @Override protected boolean visitNode(ASTNode node) { + // skip generated node - https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4712 + if (node.getStartPosition() <= 0 && node.getLength() <= 0) { + return false; + } // Get default previous end ASTNode parent = node.getParent(); int previousEnd = parent.getStartPosition(); // Look for sibling node ASTNode sibling = parent == this.topSiblingParent ? (ASTNode) this.siblings[this.siblingPtr] : null; - if (sibling != null) { + // skip generated node - https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4712 + if (sibling != null && sibling.getLength() > 0) { // Found one previous sibling, so compute its trailing comments using current node start position try { previousEnd = storeTrailingComments(sibling, node.getStartPosition(), false, this.parentLineRange[this.siblingPtr]); @@ -624,7 +629,8 @@ protected void endVisitNode(ASTNode node) { // Look if a child node is waiting for trailing comments computing ASTNode sibling = this.topSiblingParent == node ? (ASTNode) this.siblings[this.siblingPtr] : null; - if (sibling != null) { + // skip generated node - https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4712 + if (sibling != null && sibling.getLength() > 0) { try { storeTrailingComments(sibling, node.getStartPosition()+node.getLength()-1, true, this.parentLineRange[this.siblingPtr]); } catch (Exception ex) {