Skip to content

Commit ddf4508

Browse files
kriegaexslachiewicz
authored andcommitted
Improve forked javac error matching accuracy and flexibility
- Add more error message prefixes to class JavacCompiler.Messages - New method JavacCompiler.getTextStartingWithPrefix handles multi-line Java properties with placeholders and match them correctly in javac log output - Add test verifying that for slightly modified, non-matching error headers at least the stack traces are still recognised and added as error messages, despite the headers missing in those cases
1 parent e7a5c4c commit ddf4508

File tree

3 files changed

+210
-95
lines changed

3 files changed

+210
-95
lines changed

plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java

Lines changed: 145 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,48 @@ protected static class Messages {
133133

134134
// compiler.properties -> compiler.misc.verbose.*
135135
protected static final String[] MISC_PREFIXES = {"["};
136+
137+
// Generic javac error prefix
138+
// TODO: In JDK 8, this generic prefix no longer seems to be in use for javac error messages, at least not in
139+
// the Java part of javac. Maybe in C sources? Does javac even use any native classes?
140+
protected static final String[] JAVAC_GENERIC_ERROR_PREFIXES = {"javac:"};
141+
142+
// Hard-coded, English-only error header in JVM native code, *not* followed by stack trace, but rather
143+
// by another text message
144+
protected static final String[] VM_INIT_ERROR_HEADERS = {"Error occurred during initialization of VM"};
145+
146+
// Hard-coded, English-only error header in class System, followed by stack trace
147+
protected static final String[] BOOT_LAYER_INIT_ERROR_HEADERS = {
148+
"Error occurred during initialization of boot layer"
149+
};
150+
151+
// javac.properties-> javac.msg.proc.annotation.uncaught.exception
152+
// (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-21, ja JDK-21, zh_CN JDK-21, de JDK-21)
153+
protected static final String[] ANNOTATION_PROCESSING_ERROR_HEADERS = {
154+
"\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n",
155+
"\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n\n",
156+
"\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n",
157+
"\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n",
158+
"\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタックトレースで調査してください。\n\n",
159+
"\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n",
160+
"\n\nEin Annotationsprozessor hat eine nicht abgefangene Ausnahme ausgelöst.\nDetails finden Sie im folgenden Stacktrace.\n\n"
161+
};
162+
163+
// javac.properties-> javac.msg.bug
164+
// (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-9, ja JDK-9, zh_CN JDK-9, en JDK-21, ja JDK-21, zh_CN JDK-21, de
165+
// JDK-21)
166+
protected static final String[] FILE_A_BUG_ERROR_HEADERS = {
167+
"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",
168+
"コンパイラで例外が発生しました({0})。Bug Paradeで重複がないかをご確認のうえ、Java Developer Connection (http://java.sun.com/webapps/bugreport)でbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。\n",
169+
"编译器 ({0}) 中出现异常错误。 如果在 Bug Parade 中没有找到该错误, 请在 Java Developer Connection (http://java.sun.com/webapps/bugreport) 中建立 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。\n",
170+
"An exception has occurred in the compiler ({0}). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program and the following diagnostic in your report. Thank you.",
171+
"コンパイラで例外が発生しました({0})。Bug Database (http://bugs.java.com)で重複がないかをご確認のうえ、Java bugレポート・ページ(http://bugreport.java.com)でJavaコンパイラに対するbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。",
172+
"编译器 ({0}) 中出现异常错误。如果在 Bug Database (http://bugs.java.com) 中没有找到该错误, 请通过 Java Bug 报告页 (http://bugreport.java.com) 建立该 Java 编译器 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。",
173+
"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",
174+
"コンパイラで例外が発生しました({0})。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうございます。\n",
175+
"编译器 ({0}) 中出现异常错误。如果在 Bug Database (https://bugs.java.com) 中没有找到有关该错误的 Java 编译器 Bug,请通过 Java Bug 报告页 (https://bugreport.java.com) 提交 Java 编译器 Bug。请在报告中附上您的程序、以下诊断信息以及传递到 Java 编译器的参数。谢谢。\n",
176+
"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"
177+
};
136178
}
137179

138180
private static final Object LOCK = new Object();
@@ -630,10 +672,6 @@ private static CompilerResult compileInProcess0(Class<?> javacClass, String[] ar
630672
private static final Pattern STACK_TRACE_OTHER_LINE =
631673
Pattern.compile("^(?:Caused by:\\s.*|\\s*at .*|\\s*\\.\\.\\.\\s\\d+\\smore)$");
632674

633-
// Match generic javac errors with 'javac:' prefix, JMV init and boot layer init errors
634-
private static final Pattern JAVAC_OR_JVM_ERROR =
635-
Pattern.compile("^(?:javac:|Error occurred during initialization of (?:boot layer|VM)).*", Pattern.DOTALL);
636-
637675
/**
638676
* Parse the compiler output into a list of compiler messages
639677
*
@@ -692,73 +730,131 @@ static List<CompilerMessage> parseModernStream(int exitCode, BufferedReader inpu
692730
}
693731
}
694732

733+
String bufferContent = buffer.toString();
734+
if (bufferContent.isEmpty()) {
735+
return errors;
736+
}
737+
695738
// javac output not detected by other parsing
696739
// maybe better to ignore only the summary and mark the rest as error
697-
String bufferAsString = buffer.toString();
698-
if (!bufferAsString.isEmpty()) {
699-
if (JAVAC_OR_JVM_ERROR.matcher(bufferAsString).matches()) {
700-
errors.add(new CompilerMessage(bufferAsString, ERROR));
701-
} else if (hasPointer) {
702-
// A compiler message remains in buffer at end of parse stream
703-
errors.add(parseModernError(exitCode, bufferAsString));
704-
} else if (stackTraceLineCount > 0) {
705-
// Extract stack trace from end of buffer
706-
String[] lines = bufferAsString.split("\\R");
707-
int linesTotal = lines.length;
708-
buffer = new StringBuilder();
709-
int firstLine = linesTotal - stackTraceLineCount;
710-
711-
// Salvage Javac localized message 'javac.msg.bug' ("An exception has occurred in the
712-
// compiler ... Please file a bug")
713-
if (firstLine > 0) {
714-
final String lineBeforeStackTrace = lines[firstLine - 1];
715-
// One of those two URL substrings should always appear, without regard to JVM locale.
716-
// TODO: Update, if the URL changes, last checked for JDK 21.
717-
if (lineBeforeStackTrace.contains("java.sun.com/webapps/bugreport")
718-
|| lineBeforeStackTrace.contains("bugreport.java.com")) {
719-
firstLine--;
720-
}
721-
}
722-
723-
// Note: For message 'javac.msg.proc.annotation.uncaught.exception' ("An annotation processor
724-
// threw an uncaught exception"), there is no locale-independent substring, and the header is
725-
// also multi-line. It was discarded in the removed method 'parseAnnotationProcessorStream',
726-
// and we continue to do so.
727-
728-
for (int i = firstLine; i < linesTotal; i++) {
729-
buffer.append(lines[i]).append(EOL);
730-
}
731-
errors.add(new CompilerMessage(buffer.toString(), ERROR));
740+
String cleanedUpMessage;
741+
if ((cleanedUpMessage = getJavacGenericError(bufferContent)) != null
742+
|| (cleanedUpMessage = getBootLayerInitError(bufferContent)) != null
743+
|| (cleanedUpMessage = getVMInitError(bufferContent)) != null
744+
|| (cleanedUpMessage = getFileABugError(bufferContent)) != null
745+
|| (cleanedUpMessage = getAnnotationProcessingError(bufferContent)) != null) {
746+
errors.add(new CompilerMessage(cleanedUpMessage, ERROR));
747+
} else if (hasPointer) {
748+
// A compiler message remains in buffer at end of parse stream
749+
errors.add(parseModernError(exitCode, bufferContent));
750+
} else if (stackTraceLineCount > 0) {
751+
// Extract stack trace from end of buffer
752+
String[] lines = bufferContent.split("\\R");
753+
int linesTotal = lines.length;
754+
buffer = new StringBuilder();
755+
int firstLine = linesTotal - stackTraceLineCount;
756+
for (int i = firstLine; i < linesTotal; i++) {
757+
buffer.append(lines[i]).append(EOL);
732758
}
759+
errors.add(new CompilerMessage(buffer.toString(), ERROR));
733760
}
761+
// TODO: Add something like this? Check if it creates more value or more unnecessary log output in general.
762+
// else {
763+
// // Fall-back, if still no error or stack trace was recognised
764+
// errors.add(new CompilerMessage(bufferContent, exitCode == 0 ? OTHER : ERROR));
765+
// }
766+
734767
return errors;
735768
}
736769

737-
private static boolean isMisc(String line) {
738-
return startsWithPrefix(line, MISC_PREFIXES);
770+
private static boolean isMisc(String message) {
771+
return startsWithPrefix(message, MISC_PREFIXES);
772+
}
773+
774+
private static boolean isNote(String message) {
775+
return startsWithPrefix(message, NOTE_PREFIXES);
776+
}
777+
778+
private static boolean isWarning(String message) {
779+
return startsWithPrefix(message, WARNING_PREFIXES);
780+
}
781+
782+
private static boolean isError(String message) {
783+
return startsWithPrefix(message, ERROR_PREFIXES);
739784
}
740785

741-
private static boolean isNote(String line) {
742-
return startsWithPrefix(line, NOTE_PREFIXES);
786+
private static String getJavacGenericError(String message) {
787+
return getTextStartingWithPrefix(message, JAVAC_GENERIC_ERROR_PREFIXES);
743788
}
744789

745-
private static boolean isWarning(String line) {
746-
return startsWithPrefix(line, WARNING_PREFIXES);
790+
private static String getVMInitError(String message) {
791+
return getTextStartingWithPrefix(message, VM_INIT_ERROR_HEADERS);
747792
}
748793

749-
private static boolean isError(String line) {
750-
return startsWithPrefix(line, ERROR_PREFIXES);
794+
private static String getBootLayerInitError(String message) {
795+
return getTextStartingWithPrefix(message, BOOT_LAYER_INIT_ERROR_HEADERS);
751796
}
752797

753-
private static boolean startsWithPrefix(String line, String[] prefixes) {
798+
private static String getFileABugError(String message) {
799+
return getTextStartingWithPrefix(message, FILE_A_BUG_ERROR_HEADERS);
800+
}
801+
802+
private static String getAnnotationProcessingError(String message) {
803+
return getTextStartingWithPrefix(message, ANNOTATION_PROCESSING_ERROR_HEADERS);
804+
}
805+
806+
private static boolean startsWithPrefix(String text, String[] prefixes) {
754807
for (String prefix : prefixes) {
755-
if (line.startsWith(prefix)) {
808+
if (text.startsWith(prefix)) {
756809
return true;
757810
}
758811
}
759812
return false;
760813
}
761814

815+
/**
816+
* Identify and return a known javac error message prefix and all subsequent text - usually a stack trace - from a
817+
* javac log output buffer.
818+
*
819+
* @param text log buffer to search for a javac error message stack trace
820+
* @param prefixes array of strings in Java properties format, e.g. {@code "some error with line feed\nand parameter
821+
* placeholders {0} and {1}"} in multiple locales (hence the array). For the search, the
822+
* placeholders may be represented by any text in the log buffer.
823+
* @return if found, the error message + all subsequent text, otherwise {@code null}
824+
*/
825+
static String getTextStartingWithPrefix(String text, String[] prefixes) {
826+
// Implementation note: The properties format with placeholders makes it easy to just copy & paste values from
827+
// the JDK compared to having to convert them to regular expressions with ".*" instead of "{0}" and quote
828+
// special regex characters. This makes the implementation of this method more complex and potentially a bit
829+
// slower, but hopefully is worth the effort for the convenience of future developers maintaining this class.
830+
831+
// Normalise line feeds to the UNIX format found in JDK multi-line messages in properties files
832+
text = text.replaceAll("\\R", "\n");
833+
834+
// Search text for given error message prefixes/headers, until the first match is found
835+
for (String prefix : prefixes) {
836+
// Split properties message along placeholders like "{0}", "{1}" etc.
837+
String[] prefixParts = prefix.split("\\{\\d+\\}");
838+
for (int i = 0; i < prefixParts.length; i++) {
839+
// Make sure to treat split sections as literal text in search regex by enclosing them in "\Q" and "\E".
840+
// See https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html, search for "Quotation".
841+
prefixParts[i] = "\\Q" + prefixParts[i] + "\\E";
842+
}
843+
// Join message parts, replacing properties placeholders by ".*" regex ones
844+
prefix = String.join(".*?", prefixParts);
845+
// Find prefix + subsequent text in Pattern.DOTALL mode, represented in regex as "(?s)".
846+
// This matches across line break boundaries.
847+
Matcher matcher = Pattern.compile("(?s).*(" + prefix + ".*)").matcher(text);
848+
if (matcher.matches()) {
849+
// Match -> cut off text before header and replace UNIX line breaks by platform ones again
850+
return matcher.replaceFirst("$1").replaceAll("\n", EOL);
851+
}
852+
}
853+
854+
// No match
855+
return null;
856+
}
857+
762858
/**
763859
* Construct a compiler message object from a compiler output line
764860
*

0 commit comments

Comments
 (0)