diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index d0b9fccde48..abb247922cc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -701,6 +701,27 @@ public class JavacParser implements Parser { protected void storeEnd(JCTree tree, int endpos) { endPosTable.storeEnd(tree, endpos); + + // Module, package, class, method, and variable declarations remember their end positions + switch (tree.getTag()) { + case MODULEDEF: + ((JCModuleDecl)tree).endPos = endpos; + break; + case PACKAGEDEF: + ((JCPackageDecl)tree).endPos = endpos; + break; + case CLASSDEF: + ((JCClassDecl)tree).endPos = endpos; + break; + case METHODDEF: + ((JCMethodDecl)tree).endPos = endpos; + break; + case VARDEF: + ((JCVariableDecl)tree).endPos = endpos; + break; + default: + break; + } } protected T to(T t) { @@ -2761,6 +2782,7 @@ public class JavacParser implements Parser { List defs = classInterfaceOrRecordBody(names.empty, false, false); JCModifiers mods = F.at(Position.NOPOS).Modifiers(0); body = toP(F.at(pos).AnonymousClassDef(mods, defs)); + storeEnd(body, S.prevToken().endPos); } return toP(F.at(newpos).NewClass(encl, typeArgs, t, args, body)); } @@ -4018,6 +4040,7 @@ public class JavacParser implements Parser { JCExpression pid = qualident(false); accept(SEMI); JCPackageDecl pd = toP(F.at(packagePos).PackageDecl(annotations, pid)); + storeEnd(pd, S.prevToken().endPos); attach(pd, firstToken.docComment()); consumedToplevelDoc = true; defs.append(pd); @@ -4127,7 +4150,7 @@ public class JavacParser implements Parser { firstTypeDecl = false; } } - List topLevelDefs = isImplicitClass ? constructImplicitClass(defs.toList()) : defs.toList(); + List topLevelDefs = isImplicitClass ? constructImplicitClass(defs.toList(), S.prevToken().endPos) : defs.toList(); JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs); if (!consumedToplevelDoc) attach(toplevel, firstToken.docComment()); @@ -4143,7 +4166,7 @@ public class JavacParser implements Parser { } // Restructure top level to be an implicitly declared class. - private List constructImplicitClass(List origDefs) { + private List constructImplicitClass(List origDefs, int endPos) { ListBuffer topDefs = new ListBuffer<>(); ListBuffer defs = new ListBuffer<>(); @@ -4173,6 +4196,7 @@ public class JavacParser implements Parser { JCClassDecl implicit = F.at(primaryPos).ClassDef( implicitMods, name, List.nil(), null, List.nil(), List.nil(), defs.toList()); + storeEnd(implicit, endPos); topDefs.append(implicit); return topDefs.toList(); } @@ -4188,9 +4212,11 @@ public class JavacParser implements Parser { accept(LBRACE); directives = moduleDirectiveList(); accept(RBRACE); + int endPos = S.prevToken().endPos; accept(EOF); JCModuleDecl result = toP(F.at(pos).ModuleDef(mods, kind, name, directives)); + storeEnd(result, endPos); attach(result, dc); return result; } @@ -4393,6 +4419,7 @@ public class JavacParser implements Parser { List defs = classInterfaceOrRecordBody(name, false, false); JCClassDecl result = toP(F.at(pos).ClassDef( mods, name, typarams, extending, implementing, permitting, defs)); + storeEnd(result, S.prevToken().endPos); attach(result, dc); return result; } @@ -4416,6 +4443,7 @@ public class JavacParser implements Parser { saveDanglingDocComments(dc); List defs = classInterfaceOrRecordBody(name, false, true); + int endPos = S.prevToken().endPos; java.util.List fields = new ArrayList<>(); for (JCVariableDecl field : headerFields) { fields.add(field); @@ -4441,6 +4469,7 @@ public class JavacParser implements Parser { defs = defs.prepend(field); } JCClassDecl result = toP(F.at(pos).ClassDef(mods, name, typarams, null, implementing, defs)); + storeEnd(result, endPos); attach(result, dc); return result; } @@ -4481,6 +4510,7 @@ public class JavacParser implements Parser { defs = classInterfaceOrRecordBody(name, true, false); JCClassDecl result = toP(F.at(pos).ClassDef( mods, name, typarams, null, extending, permitting, defs)); + storeEnd(result, S.prevToken().endPos); attach(result, dc); return result; } @@ -4529,6 +4559,7 @@ public class JavacParser implements Parser { JCClassDecl result = toP(F.at(pos). ClassDef(mods, name, List.nil(), null, implementing, defs)); + storeEnd(result, S.prevToken().endPos); attach(result, dc); return result; } @@ -4667,10 +4698,12 @@ public class JavacParser implements Parser { createPos = identPos; JCIdent ident = F.at(identPos).Ident(enumName); JCNewClass create = F.at(createPos).NewClass(null, typeArgs, ident, args, body); + int endPos = S.prevToken().endPos; if (createPos != identPos) - storeEnd(create, S.prevToken().endPos); + storeEnd(create, endPos); ident = F.at(identPos).Ident(enumName); JCTree result = toP(F.at(pos).VarDef(mods, name, ident, create)); + storeEnd(result, endPos); attach(result, dc); return result; } @@ -5100,6 +5133,7 @@ public class JavacParser implements Parser { toP(F.at(pos).MethodDef(mods, name, type, typarams, receiverParam, params, thrown, body, defaultValue)); + storeEnd(result, S.prevToken().endPos); attach(result, dc); return result; } finally { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index de86b7e2c57..5d39a914b2d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -641,6 +641,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { /** The tree representing the package clause. */ public JCExpression pid; public PackageSymbol packge; + /** Position of closing semicolon, optional. */ + public int endPos = Position.NOPOS; public JCPackageDecl(List annotations, JCExpression pid) { this.annotations = annotations; this.pid = pid; @@ -837,6 +839,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public List defs; /** the symbol */ public ClassSymbol sym; + /** position of closing brace, optional. */ + public int endPos = Position.NOPOS; protected JCClassDecl(JCModifiers mods, Name name, List typarams, @@ -931,6 +935,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public MethodSymbol sym; /** does this method completes normally */ public boolean completesNormally; + /** position of closing brace or semicolon, optional. */ + public int endPos = Position.NOPOS; protected JCMethodDecl(JCModifiers mods, Name name, @@ -1016,6 +1022,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public VarSymbol sym; /** explicit start pos */ public int startPos = Position.NOPOS; + /** position of closing semicolon, optional. */ + public int endPos = Position.NOPOS; /** declared using `var` */ private boolean declaredUsingVar; @@ -3126,6 +3134,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public JCExpression qualId; public List directives; public ModuleSymbol sym; + /** position of closing brace, optional. */ + public int endPos = Position.NOPOS; protected JCModuleDecl(JCModifiers mods, ModuleKind kind, JCExpression qualId, List directives) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java index 5e3b043fb11..196beced712 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -500,26 +500,47 @@ public class TreeInfo { return tree.pos; } - /** The end position of given tree, if it is a block with - * defined endpos. + /** The end position of the given tree, if defined. */ public static int endPos(JCTree tree) { - if (tree.hasTag(BLOCK) && ((JCBlock) tree).endpos != Position.NOPOS) - return ((JCBlock) tree).endpos; - else if (tree.hasTag(SYNCHRONIZED)) + int endPos; + switch (tree.getTag()) { + case BLOCK: + endPos = ((JCBlock) tree).endpos; + break; + case SYNCHRONIZED: return endPos(((JCSynchronized) tree).body); - else if (tree.hasTag(TRY)) { + case TRY: JCTry t = (JCTry) tree; return endPos((t.finalizer != null) ? t.finalizer : (t.catchers.nonEmpty() ? t.catchers.last().body : t.body)); - } else if (tree.hasTag(SWITCH) && - ((JCSwitch) tree).endpos != Position.NOPOS) { - return ((JCSwitch) tree).endpos; - } else if (tree.hasTag(SWITCH_EXPRESSION) && - ((JCSwitchExpression) tree).endpos != Position.NOPOS) { - return ((JCSwitchExpression) tree).endpos; - } else + case SWITCH: + endPos = ((JCSwitch) tree).endpos; + break; + case SWITCH_EXPRESSION: + endPos = ((JCSwitchExpression) tree).endpos; + break; + case MODULEDEF: + endPos = ((JCModuleDecl) tree).endPos; + break; + case PACKAGEDEF: + endPos = ((JCPackageDecl) tree).endPos; + break; + case CLASSDEF: + endPos = ((JCClassDecl) tree).endPos; + break; + case METHODDEF: + endPos = ((JCMethodDecl) tree).endPos; + break; + case VARDEF: + endPos = ((JCVariableDecl) tree).endPos; + break; + default: return tree.pos; + } + if (endPos != Position.NOPOS) + return endPos; + return tree.pos; } diff --git a/test/langtools/tools/javac/parser/DeclarationEndPositions.java b/test/langtools/tools/javac/parser/DeclarationEndPositions.java new file mode 100644 index 00000000000..12f0eac2a2c --- /dev/null +++ b/test/langtools/tools/javac/parser/DeclarationEndPositions.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8350212 + * @summary Verify ending source positions are calculated for declarations supporting SuppressWarnings + * @modules jdk.compiler/com.sun.tools.javac.tree + * @run main DeclarationEndPositions + */ + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.tree.TreeInfo; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +public class DeclarationEndPositions { + + public static void checkEndPosition(Class nodeType, String input, String marker) throws IOException { + + // Create source + var source = new SimpleJavaFileObject(URI.create("file://T.java"), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return input; + } + }; + + // Parse source + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, List.of(), List.of(), List.of(source)); + Iterable units = ((JavacTask)task).parse(); + + // Find node and check end position + JCTree.JCCompilationUnit unit = (JCTree.JCCompilationUnit)units.iterator().next(); + unit.accept(new TreeScanner() { + @Override + public Void scan(Tree node, Void aVoid) { + if (nodeType.isInstance(node)) { + JCTree tree = (JCTree)node; + int actual = TreeInfo.endPos(tree); + int expected = marker.indexOf('^') + 1; + if (actual != expected) { + throw new AssertionError(String.format( + "wrong end pos %d != %d for \"%s\" @ %d", actual, expected, input, tree.pos)); + } + } + return super.scan(node, aVoid); + } + }, null); + } + + public static void main(String... args) throws Exception { + + // JCModuleDecl + checkEndPosition(JCModuleDecl.class, + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + "/* comment */ module fred { /* comment */ } /* comment */", + " ^ "); + + // JCPackageDecl + checkEndPosition(JCPackageDecl.class, + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + "/* comment */ package fred; /* comment */", + " ^ "); + + // JCClassDecl + checkEndPosition(JCClassDecl.class, + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + "/* comment */ class Fred { /* comment */ } /* comment */", + " ^ "); + + // JCMethodDecl + checkEndPosition(JCMethodDecl.class, + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + "/* comment */ class Fred { void m() { /* comment */ } } /* comment */", + " ^ "); + + // JCVariableDecl + checkEndPosition(JCVariableDecl.class, + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + "/* comment */ class Fred { int x = 123; } /* comment */", + " ^ "); + } +} diff --git a/test/langtools/tools/javac/parser/JavacParserTest.java b/test/langtools/tools/javac/parser/JavacParserTest.java index 40ab577a5d1..ee80553ba74 100644 --- a/test/langtools/tools/javac/parser/JavacParserTest.java +++ b/test/langtools/tools/javac/parser/JavacParserTest.java @@ -2298,10 +2298,9 @@ public class JavacParserTest extends TestCase { @Test //JDK-8310326 void testUnnamedClassPositions() throws IOException { - String code = """ - void main() { - } - """; + // 0 1 2 + // 012345678901234567890 + String code = "void main() { }"; DiagnosticCollector coll = new DiagnosticCollector<>(); JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, List.of("--enable-preview", "--source", System.getProperty("java.specification.version")), @@ -2313,7 +2312,7 @@ public class JavacParserTest extends TestCase { @Override public Void visitClass(ClassTree node, Void p) { assertEquals("Wrong start position", 0, sp.getStartPosition(cut, node)); - assertEquals("Wrong end position", -1, sp.getEndPosition(cut, node)); + assertEquals("Wrong end position", 15, sp.getEndPosition(cut, node)); assertEquals("Wrong modifiers start position", -1, sp.getStartPosition(cut, node.getModifiers())); assertEquals("Wrong modifiers end position", -1, sp.getEndPosition(cut, node.getModifiers())); return super.visitClass(node, p);