diff --git a/buildScripts/tests.ant.xml b/buildScripts/tests.ant.xml index 82292dc0..139ef0da 100644 --- a/buildScripts/tests.ant.xml +++ b/buildScripts/tests.ant.xml @@ -167,6 +167,11 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn + + + + + Running TestJavac on JVM${ant.java.version}, with the javac built into your VM distributon. diff --git a/src/delombok/lombok/delombok/DocCommentIntegrator.java b/src/delombok/lombok/delombok/DocCommentIntegrator.java index dc71fcb4..8c2b4f6a 100644 --- a/src/delombok/lombok/delombok/DocCommentIntegrator.java +++ b/src/delombok/lombok/delombok/DocCommentIntegrator.java @@ -152,6 +152,13 @@ public class DocCommentIntegrator { return pos; } + @SuppressWarnings("unused") // As of JDK27 this is the only getEndPosition; EndPosTable is gone. + public int getEndPosition() { + int end = Javac.getTreeEndPos(node); + if (end > pos) return end; + return pos + docCommentContent_.length(); + } + @SuppressWarnings("unused") // We compile against very old versions of javac intentionally (to support old stuff), but this is the method for newer impls. public int getEndPosition(EndPosTable endPosTable) { int end = endPosTable == null ? 0 : endPosTable.getEndPos(node); diff --git a/src/delombok/lombok/delombok/PrettyPrinter.java b/src/delombok/lombok/delombok/PrettyPrinter.java index f379094a..be99c401 100644 --- a/src/delombok/lombok/delombok/PrettyPrinter.java +++ b/src/delombok/lombok/delombok/PrettyPrinter.java @@ -86,7 +86,6 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWhileLoop; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; -import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Position; @@ -1615,17 +1614,10 @@ public class PrettyPrinter extends JCTree.Visitor { } } - private static final Method getExtendsClause, getEndPosition; + private static final Method getExtendsClause; static { getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class[0]); - - if (getJavaCompilerVersion() < 8) { - getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", java.util.Map.class); - } else { - getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", "com.sun.tools.javac.tree.EndPosTable"); - } - Permit.setAccessible(getEndPosition); } private static Method getMethod(Class clazz, String name, Class... paramTypes) { @@ -1636,18 +1628,6 @@ public class PrettyPrinter extends JCTree.Visitor { } } - private static Method getMethod(Class clazz, String name, String... paramTypes) { - try { - Class[] c = new Class[paramTypes.length]; - for (int i = 0; i < paramTypes.length; i++) c[i] = Class.forName(paramTypes[i]); - return Permit.getMethod(clazz, name, c); - } catch (NoSuchMethodException e) { - throw sneakyThrow(e); - } catch (ClassNotFoundException e) { - throw sneakyThrow(e); - } - } - public static JCTree getExtendsClause(JCClassDecl decl) { return (JCTree) Permit.invokeSneaky(getExtendsClause, decl); } diff --git a/src/stubs/com/sun/tools/javac/parser/JavacParser.java b/src/stubs/com/sun/tools/javac/parser/JavacParser.java index 4f1f3380..f328556e 100644 --- a/src/stubs/com/sun/tools/javac/parser/JavacParser.java +++ b/src/stubs/com/sun/tools/javac/parser/JavacParser.java @@ -6,6 +6,7 @@ package com.sun.tools.javac.parser; import com.sun.tools.javac.tree.JCTree; public class JavacParser { + // Up to javac26 the last boolean is keepEndPositions; as of javac27 (JDK-8372948) it is parseModuleInfo. protected JavacParser(ParserFactory fac, Lexer S, boolean keepDocComments, boolean keepLineMap, boolean keepEndPositions) { } diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java index 90266c26..c6cab588 100644 --- a/src/utils/lombok/javac/CommentCatcher.java +++ b/src/utils/lombok/javac/CommentCatcher.java @@ -44,7 +44,8 @@ public class CommentCatcher { setInCompiler(compiler, context); compiler.keepComments = true; - compiler.genEndPos = true; + // genEndPos no longer exists as of javac27. + if (!Javac.endPosStoredOnTree()) compiler.genEndPos = true; return new CommentCatcher(compiler); } @@ -105,6 +106,9 @@ public class CommentCatcher { parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory"); } else if (javaCompilerVersion == 8) { parserFactory = Class.forName("lombok.javac.java8.CommentCollectingParserFactory"); + } else if (Javac.endPosStoredOnTree()) { + // JDK-8372948 also reshaped JavacParser's constructors. + parserFactory = Class.forName("lombok.javac.java27.CommentCollectingParserFactory"); } else { parserFactory = Class.forName("lombok.javac.java9.CommentCollectingParserFactory"); } diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index 840c32ee..fc4724ce 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -213,37 +213,51 @@ public class Javac { private static final Method getExtendsClause, getEndPosition, storeEnd; + /** As of javac27 (JDK-8372948), end positions live in {@code JCTree.endpos} and {@code EndPosTable} no longer exists. */ + private static final boolean ENDPOS_ON_TREE; + static { getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class[0]); if (getJavaCompilerVersion() < 8) { + ENDPOS_ON_TREE = false; getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", java.util.Map.class); storeEnd = getMethod(java.util.Map.class, "put", Object.class, Object.class); } else { - getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", "com.sun.tools.javac.tree.EndPosTable"); - Method storeEndMethodTemp; Class endPosTable; try { endPosTable = Class.forName("com.sun.tools.javac.tree.EndPosTable"); } catch (ClassNotFoundException ex) { - throw sneakyThrow(ex); + endPosTable = null; } - try { - storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class); - } catch (NoSuchMethodException e) { + ENDPOS_ON_TREE = endPosTable == null; + if (ENDPOS_ON_TREE) { + getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", new Class[0]); + storeEnd = null; + } else { + getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", endPosTable); + Method storeEndMethodTemp; try { - endPosTable = Class.forName("com.sun.tools.javac.parser.JavacParser$AbstractEndPosTable"); storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class); - } catch (NoSuchMethodException ex) { - throw sneakyThrow(ex); - } catch (ClassNotFoundException ex) { - throw sneakyThrow(ex); + } catch (NoSuchMethodException e) { + try { + endPosTable = Class.forName("com.sun.tools.javac.parser.JavacParser$AbstractEndPosTable"); + storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class); + } catch (NoSuchMethodException ex) { + throw sneakyThrow(ex); + } catch (ClassNotFoundException ex) { + throw sneakyThrow(ex); + } } + storeEnd = storeEndMethodTemp; } - storeEnd = storeEndMethodTemp; } Permit.setAccessible(getEndPosition); - Permit.setAccessible(storeEnd); + if (storeEnd != null) Permit.setAccessible(storeEnd); + } + + static boolean endPosStoredOnTree() { + return ENDPOS_ON_TREE; } private static Method getMethod(Class clazz, String name, Class... paramTypes) { @@ -423,6 +437,7 @@ public class Javac { public static int getEndPosition(DiagnosticPosition pos, JCCompilationUnit top) { try { + if (ENDPOS_ON_TREE) return (Integer) getEndPosition.invoke(pos); Object endPositions = JCCOMPILATIONUNIT_ENDPOSITIONS.get(top); return (Integer) getEndPosition.invoke(pos, endPositions); } catch (IllegalAccessException e) { @@ -432,8 +447,22 @@ public class Javac { } } + /** Returns -1 if this javac has no {@code JCTree.endpos} field (pre-27). */ + public static int getTreeEndPos(JCTree tree) { + if (JCTREE_ENDPOS == null) return -1; + try { + return JCTREE_ENDPOS.getInt(tree); + } catch (IllegalAccessException e) { + throw sneakyThrow(e); + } + } + public static void storeEnd(JCTree tree, int pos, JCCompilationUnit top) { try { + if (ENDPOS_ON_TREE) { + if (JCTREE_ENDPOS != null) JCTREE_ENDPOS.setInt(tree, pos); + return; + } Object endPositions = JCCOMPILATIONUNIT_ENDPOSITIONS.get(top); if (endPositions == null) return; storeEnd.invoke(endPositions, tree, pos); @@ -513,6 +542,15 @@ public class Javac { } } + private static final Field JCTREE_ENDPOS; + static { + Field f = null; + try { + f = Permit.getField(JCTree.class, "endpos"); + } catch (NoSuchFieldException e) {} + JCTREE_ENDPOS = f; + } + private static final Field JCCOMPILATIONUNIT_ENDPOSITIONS, JCCOMPILATIONUNIT_DOCCOMMENTS; static { Field f = null; diff --git a/src/utils/lombok/javac/java27/CommentCollectingParser.java b/src/utils/lombok/javac/java27/CommentCollectingParser.java new file mode 100644 index 00000000..01c37aae --- /dev/null +++ b/src/utils/lombok/javac/java27/CommentCollectingParser.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2026 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.java27; + +import static lombok.javac.CommentCatcher.JCCompilationUnit_comments; +import static lombok.javac.CommentCatcher.JCCompilationUnit_textBlockStarts; + +import lombok.javac.java8.CommentCollectingScanner; + +import com.sun.tools.javac.parser.JavacParser; +import com.sun.tools.javac.parser.Lexer; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; + +class CommentCollectingParser extends JavacParser { + private final Lexer lexer; + + // The stub constructor's last param is named keepEndPositions, but on javac27 this descriptor resolves to the constructor ending in parseModuleInfo. + protected CommentCollectingParser(ParserFactory fac, Lexer S, + boolean keepDocComments, boolean keepLineMap, boolean parseModuleInfo) { + super(fac, S, keepDocComments, keepLineMap, parseModuleInfo); + lexer = S; + } + + public JCCompilationUnit parseCompilationUnit() { + JCCompilationUnit result = super.parseCompilationUnit(); + if (lexer instanceof CommentCollectingScanner) { + JCCompilationUnit_comments.set(result, ((CommentCollectingScanner) lexer).getComments()); + JCCompilationUnit_textBlockStarts.set(result, ((CommentCollectingScanner) lexer).getTextBlockStarts()); + } + return result; + } +} diff --git a/src/utils/lombok/javac/java27/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java27/CommentCollectingParserFactory.java new file mode 100644 index 00000000..7e0c1acf --- /dev/null +++ b/src/utils/lombok/javac/java27/CommentCollectingParserFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2026 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.java27; + +import java.lang.reflect.Field; + +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.parser.JavacParser; +import com.sun.tools.javac.parser.Lexer; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.parser.ScannerFactory; +import com.sun.tools.javac.util.Context; + +import lombok.permit.Permit; + +public class CommentCollectingParserFactory extends ParserFactory { + private final Context context; + + static Context.Key key() { + return parserFactoryKey; + } + + protected CommentCollectingParserFactory(Context context) { + super(context); + this.context = context; + } + + public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepLineMap, boolean parseModuleInfo) { + ScannerFactory scannerFactory = ScannerFactory.instance(context); + Lexer lexer = scannerFactory.newScanner(input, true); + Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, parseModuleInfo); + return (JavacParser) x; + // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with + //javac's JavacParser. Either way this will work out. + } + + public static void setInCompiler(JavaCompiler compiler, Context context) { + context.put(CommentCollectingParserFactory.key(), (ParserFactory) null); + Field field; + try { + field = Permit.getField(JavaCompiler.class, "parserFactory"); + field.set(compiler, new CommentCollectingParserFactory(context)); + } catch (Exception e) { + throw new IllegalStateException("Could not set comment sensitive parser in the compiler", e); + } + } +}