diff --git a/plexus-compilers/plexus-compiler-javac/pom.xml b/plexus-compilers/plexus-compiler-javac/pom.xml index 91c68b47..35f496dc 100644 --- a/plexus-compilers/plexus-compiler-javac/pom.xml +++ b/plexus-compilers/plexus-compiler-javac/pom.xml @@ -27,6 +27,11 @@ junit-jupiter-api test + + org.junit.jupiter + junit-jupiter-params + test + org.hamcrest hamcrest diff --git a/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java b/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java index 12cace69..1e97f8e7 100644 --- a/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java +++ b/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java @@ -65,6 +65,7 @@ import java.util.Properties; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.regex.Pattern; import org.codehaus.plexus.compiler.AbstractCompiler; import org.codehaus.plexus.compiler.CompilerConfiguration; @@ -627,6 +628,16 @@ private static CompilerResult compileInProcess0(Class javacClass, String[] ar return new CompilerResult(success, messages); } + // Match ~95% of existing JDK exception name patterns (last checked for JDK 21) + private static final Pattern STACK_TRACE_FIRST_LINE = Pattern.compile("^(?:[\\w+.-]+\\.)[\\w$]*?(?:" + + "Exception|Error|Throwable|Failure|Result|Abort|Fault|ThreadDeath|Overflow|Warning|" + + "NotSupported|NotFound|BadArgs|BadClassFile|Illegal|Invalid|Unexpected|Unchecked|Unmatched\\w+" + + ").*$"); + + // Match exception causes, existing and omitted stack trace elements + private static final Pattern STACK_TRACE_OTHER_LINE = + Pattern.compile("^(?:Caused by:\\s.*|\\s*at .*|\\s*\\.\\.\\.\\s\\d+\\smore)$"); + /** * Parse the output from the compiler into a list of CompilerMessage objects * @@ -643,13 +654,14 @@ static List parseModernStream(int exitCode, BufferedReader inpu StringBuilder buffer = new StringBuilder(); boolean hasPointer = false; + int stackTraceLineCount = 0; while (true) { line = input.readLine(); if (line == null) { // javac output not detected by other parsing - // maybe better to ignore only the summary an mark the rest as error + // maybe better to ignore only the summary and mark the rest as error String bufferAsString = buffer.toString(); if (buffer.length() > 0) { if (bufferAsString.startsWith("javac:")) { @@ -659,26 +671,44 @@ static List parseModernStream(int exitCode, BufferedReader inpu } else if (hasPointer) { // A compiler message remains in buffer at end of parse stream errors.add(parseModernError(exitCode, bufferAsString)); + } else if (stackTraceLineCount > 0) { + // Extract stack trace from end of buffer + String[] lines = bufferAsString.split("\\R"); + int linesTotal = lines.length; + buffer = new StringBuilder(); + int firstLine = linesTotal - stackTraceLineCount; + + // Salvage Javac localized message 'javac.msg.bug' ("An exception has occurred in the + // compiler ... Please file a bug") + if (firstLine > 0) { + final String lineBeforeStackTrace = lines[firstLine - 1]; + // One of those two URL substrings should always appear, without regard to JVM locale. + // TODO: Update, if the URL changes, last checked for JDK 21. + if (lineBeforeStackTrace.contains("java.sun.com/webapps/bugreport") + || lineBeforeStackTrace.contains("bugreport.java.com")) { + firstLine--; + } + } + + // Note: For message 'javac.msg.proc.annotation.uncaught.exception' ("An annotation processor + // threw an uncaught exception"), there is no locale-independent substring, and the header is + // also multi-line. It was discarded in the removed method 'parseAnnotationProcessorStream', + // and we continue to do so. + + for (int i = firstLine; i < linesTotal; i++) { + buffer.append(lines[i]).append(EOL); + } + errors.add(new CompilerMessage(buffer.toString(), CompilerMessage.Kind.ERROR)); } } return errors; } - // A compiler error occurred, treat everything that follows as part of the error. - if (line.startsWith("An exception has occurred in the compiler")) { - buffer = new StringBuilder(); - - while (line != null) { - buffer.append(line); - buffer.append(EOL); - line = input.readLine(); - } - - errors.add(new CompilerMessage(buffer.toString(), CompilerMessage.Kind.ERROR)); - return errors; - } else if (line.startsWith("An annotation processor threw an uncaught exception.")) { - CompilerMessage annotationProcessingError = parseAnnotationProcessorStream(input); - errors.add(annotationProcessingError); + if (stackTraceLineCount == 0 && STACK_TRACE_FIRST_LINE.matcher(line).matches() + || STACK_TRACE_OTHER_LINE.matcher(line).matches()) { + stackTraceLineCount++; + } else { + stackTraceLineCount = 0; } // new error block? @@ -714,21 +744,6 @@ static List parseModernStream(int exitCode, BufferedReader inpu } } - private static CompilerMessage parseAnnotationProcessorStream(final BufferedReader input) throws IOException { - String line = input.readLine(); - final StringBuilder buffer = new StringBuilder(); - - while (line != null) { - if (!line.startsWith("Consult the following stack trace for details.")) { - buffer.append(line); - buffer.append(EOL); - } - line = input.readLine(); - } - - return new CompilerMessage(buffer.toString(), CompilerMessage.Kind.ERROR); - } - private static boolean isMisc(String line) { return startsWithPrefix(line, MISC_PREFIXES); } diff --git a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java index bfc416a4..d8f01590 100644 --- a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java +++ b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java @@ -28,20 +28,25 @@ import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; import org.codehaus.plexus.compiler.CompilerMessage; import org.codehaus.plexus.util.Os; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.core.StringEndsWith.endsWith; -import static org.hamcrest.core.StringStartsWith.startsWith; /** * @author Trygve Laugstøl + * @author Alexander Kriegisch */ public class ErrorMessageParserTest { private static final String EOL = System.getProperty("line.separator"); @@ -775,19 +780,79 @@ public void testJava7Error() throws Exception { assertThat(message2.getEndLine(), is(3)); } - @Test - public void testBugParade() throws Exception { - String out = "An exception has occurred in the compiler (1.7.0_80). " - + "Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport) after checking the Bug Parade for duplicates. " - + "Include your program and the following diagnostic in your report. Thank you." + EOL - + "com.sun.tools.javac.code.Symbol$CompletionFailure: class file for java.util.Optional not found"; + @ParameterizedTest(name = "{0}") + @MethodSource("testBugParade_args") + public void testBugParade(String jdkAndLocale, String stackTraceHeader) throws Exception { + String stackTraceWithHeader = stackTraceHeader + stackTraceInternalCompilerError; - List compilerErrors = - JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(out))); + List compilerMessages = + JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(stackTraceWithHeader))); - assertThat(compilerErrors, notNullValue()); + assertThat(compilerMessages, notNullValue()); + assertThat(compilerMessages, hasSize(1)); - assertThat(compilerErrors.size(), is(1)); + String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n"); + // Parser retains stack trace header + assertThat(message, startsWith(stackTraceHeader)); + assertThat(message, endsWith(stackTraceInternalCompilerError)); + } + + private static final String stackTraceInternalCompilerError = + "\tat com.sun.tools.javac.comp.MemberEnter.baseEnv(MemberEnter.java:1388)\n" + + "\tat com.sun.tools.javac.comp.MemberEnter.complete(MemberEnter.java:1046)\n" + + "\tat com.sun.tools.javac.code.Symbol.complete(Symbol.java:574)\n" + + "\tat com.sun.tools.javac.code.Symbol$ClassSymbol.complete(Symbol.java:1037)\n" + + "\tat com.sun.tools.javac.code.Symbol$ClassSymbol.flags(Symbol.java:973)\n" + + "\tat com.sun.tools.javac.code.Symbol$ClassSymbol.getKind(Symbol.java:1101)\n" + + "\tat com.sun.tools.javac.code.Kinds.kindName(Kinds.java:162)\n" + + "\tat com.sun.tools.javac.comp.Check.duplicateError(Check.java:329)\n" + + "\tat com.sun.tools.javac.comp.Check.checkUnique(Check.java:3435)\n" + + "\tat com.sun.tools.javac.comp.Enter.visitTypeParameter(Enter.java:454)\n" + + "\tat com.sun.tools.javac.tree.JCTree$JCTypeParameter.accept(JCTree.java:2224)\n" + + "\tat com.sun.tools.javac.comp.Enter.classEnter(Enter.java:258)\n" + + "\tat com.sun.tools.javac.comp.Enter.classEnter(Enter.java:272)\n" + + "\tat com.sun.tools.javac.comp.Enter.visitClassDef(Enter.java:418)\n" + + "\tat com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:693)\n" + + "\tat com.sun.tools.javac.comp.Enter.classEnter(Enter.java:258)\n" + + "\tat com.sun.tools.javac.comp.Enter.classEnter(Enter.java:272)\n" + + "\tat com.sun.tools.javac.comp.Enter.visitTopLevel(Enter.java:334)\n" + + "\tat com.sun.tools.javac.tree.JCTree$JCCompilationUnit.accept(JCTree.java:518)\n" + + "\tat com.sun.tools.javac.comp.Enter.classEnter(Enter.java:258)\n" + + "\tat com.sun.tools.javac.comp.Enter.classEnter(Enter.java:272)\n" + + "\tat com.sun.tools.javac.comp.Enter.complete(Enter.java:486)\n" + + "\tat com.sun.tools.javac.comp.Enter.main(Enter.java:471)\n" + + "\tat com.sun.tools.javac.main.JavaCompiler.enterTrees(JavaCompiler.java:982)\n" + + "\tat com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:857)\n" + + "\tat com.sun.tools.javac.main.Main.compile(Main.java:523)\n" + + "\tat com.sun.tools.javac.main.Main.compile(Main.java:381)\n" + + "\tat com.sun.tools.javac.main.Main.compile(Main.java:370)\n" + + "\tat com.sun.tools.javac.main.Main.compile(Main.java:361)\n" + + "\tat com.sun.tools.javac.Main.compile(Main.java:56)\n" + + "\tat com.sun.tools.javac.Main.main(Main.java:42)\n"; + + private static Stream testBugParade_args() { + return Stream.of( + Arguments.of( + "JDK 8 English", + "An exception has occurred in the compiler ({0}). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport) after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report. Thank you.\n"), + Arguments.of( + "JDK 8 Japanese", + "コンパイラで例外が発生しました({0})。Bug Paradeで重複がないかをご確認のうえ、Java Developer Connection (http://java.sun.com/webapps/bugreport)でbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。\n"), + Arguments.of( + "JDK 8 Chinese", + "编译器 ({0}) 中出现异常错误。 如果在 Bug Parade 中没有找到该错误, 请在 Java Developer Connection (http://java.sun.com/webapps/bugreport) 中建立 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。\n"), + Arguments.of( + "JDK 21 English", + "An exception has occurred in the compiler ({0}). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.\n"), + Arguments.of( + "JDK 21 Japanese", + "コンパイラで例外が発生しました({0})。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうございます。\n"), + Arguments.of( + "JDK 21 Chinese", + "编译器 ({0}) 中出现异常错误。如果在 Bug Database (https://bugs.java.com) 中没有找到有关该错误的 Java 编译器 Bug,请通过 Java Bug 报告页 (https://bugreport.java.com) 提交 Java 编译器 Bug。请在报告中附上您的程序、以下诊断信息以及传递到 Java 编译器的参数。谢谢。\n"), + Arguments.of( + "JDK 21 German", + "Im Compiler ({0}) ist eine Ausnahme aufgetreten. Erstellen Sie auf der Java-Seite zum Melden von Bugs (https://bugreport.java.com) einen Bugbericht, nachdem Sie die Bugdatenbank (https://bugs.java.com) auf Duplikate geprüft haben. Geben Sie in Ihrem Bericht Ihr Programm, die folgende Diagnose und die Parameter an, die Sie dem Java-Compiler übergeben haben. Vielen Dank.\n")); } @Test diff --git a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java index c8edab96..8a182fcf 100644 --- a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java +++ b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java @@ -4,13 +4,20 @@ import java.io.IOException; import java.io.StringReader; import java.util.List; +import java.util.stream.Stream; import org.codehaus.plexus.compiler.CompilerMessage; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -33,40 +40,67 @@ /** * @author Olivier Lamy + * @author Alexander Kriegisch */ public class JavacCompilerTest extends AbstractJavacCompilerTest { + private static final String EOL = System.getProperty("line.separator"); + @BeforeEach public void setUp() { super.setUp(); setForceJavacCompilerUse(true); } - @Test - void parseModernStream_withAnnotationProcessingErrors() throws IOException { - String input = "\n" + "\n" - + "An annotation processor threw an uncaught exception.\n" - + "Consult the following stack trace for details.\n" - + "java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x1da51a35) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @0x1da51a35\n" - + "\tat lombok.javac.apt.LombokProcessor.getJavacProcessingEnvironment(LombokProcessor.java:433)\n" - + "\tat lombok.javac.apt.LombokProcessor.init(LombokProcessor.java:92)\n" - + "\tat lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:160)\n" - + "\tat lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:213)\n" - + "\tat lombok.launch.AnnotationProcessorHider$AnnotationProcessor.init(AnnotationProcessor.java:64)\n" - + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.(JavacProcessingEnvironment.java:702)\n" - + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:829)\n" - + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:925)\n" - + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1269)\n" - + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1384)\n" - + "\tat jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1261)\n" - + "\tat jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:935)\n" - + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:317)\n" - + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:176)\n" - + "\tat jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:64)\n" - + "\tat jdk.compiler/com.sun.tools.javac.Main.main(Main.java:50)\n"; - + @ParameterizedTest(name = "{0}") + @MethodSource("testParseModernStream_withAnnotationProcessingErrors_args") + void testParseModernStream_withAnnotationProcessingErrors(String jdkAndLocale, String stackTraceHeader) + throws IOException { + String stackTraceWithHeader = stackTraceHeader + stackTraceAnnotationProcessingError; List compilerMessages = - JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(input))); + JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(stackTraceWithHeader))); + assertThat(compilerMessages, notNullValue()); assertThat(compilerMessages, hasSize(1)); + + String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n"); + // Parser does not retain stack trace header, because it is hard to identify in a locale-independent way + assertThat(message, not(startsWith(stackTraceHeader))); + assertThat(message, endsWith(stackTraceAnnotationProcessingError)); + } + + private static final String stackTraceAnnotationProcessingError = + "java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x1da51a35) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @0x1da51a35\n" + + "\tat lombok.javac.apt.LombokProcessor.getJavacProcessingEnvironment(LombokProcessor.java:433)\n" + + "\tat lombok.javac.apt.LombokProcessor.init(LombokProcessor.java:92)\n" + + "\tat lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:160)\n" + + "\tat lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:213)\n" + + "\tat lombok.launch.AnnotationProcessorHider$AnnotationProcessor.init(AnnotationProcessor.java:64)\n" + + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.(JavacProcessingEnvironment.java:702)\n" + + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:829)\n" + + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:925)\n" + + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1269)\n" + + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1384)\n" + + "\tat jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1261)\n" + + "\tat jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:935)\n" + + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:317)\n" + + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:176)\n" + + "\tat jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:64)\n" + + "\tat jdk.compiler/com.sun.tools.javac.Main.main(Main.java:50)\n"; + + private static Stream testParseModernStream_withAnnotationProcessingErrors_args() { + return Stream.of( + Arguments.of( + "JDK 8 English", + "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n"), + Arguments.of("JDK 8 Japanese", "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n\n"), + Arguments.of("JDK 8 Chinese", "\n\n注释处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n"), + Arguments.of( + "JDK 21 English", + "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n"), + Arguments.of("JDK 21 Japanese", "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタックトレースで調査してください。\n\n"), + Arguments.of("JDK 21 Chinese", "\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n"), + Arguments.of( + "JDK 21 German", + "\n\nEin Annotationsprozessor hat eine nicht abgefangene Ausnahme ausgelöst.\nDetails finden Sie im folgenden Stacktrace.\n\n")); } }