Crash reports handle invalid format strings properly

This commit is contained in:
Sergey Karmanov 2020-11-15 23:11:46 +03:00
parent ccda1eff74
commit ec6181aaa8

View File

@ -20,193 +20,213 @@ import java.util.*;
public class CrashReports { public class CrashReports {
private CrashReports() { private CrashReports() {
} }
private static final Path CRASH_REPORTS_PATH = Paths.get("crash-reports"); private static final Path CRASH_REPORTS_PATH = Paths.get("crash-reports");
private static final Collection<ContextProvider> PROVIDERS = Collections.synchronizedCollection(new ArrayList<>()); private static final Collection<ContextProvider> PROVIDERS = Collections.synchronizedCollection(new ArrayList<>());
private static final Collection<Analyzer> ANALYZERS = Collections.synchronizedCollection(new ArrayList<>()); private static final Collection<Analyzer> ANALYZERS = Collections.synchronizedCollection(new ArrayList<>());
private static final Logger LOGGER = LogManager.getLogger("crash"); private static final Logger LOGGER = LogManager.getLogger("crash");
/** /**
* <em>This method never returns.</em> * <em>This method never returns.</em>
* <p> * <p>
* TODO document * TODO document
* *
* @param throwable * @param throwable
* @param messageFormat * @param messageFormat
* @param args * @param args
*/ */
public static void report(Throwable throwable, String messageFormat, Object... args) { public static void report(Throwable throwable, String messageFormat, Object... args) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
appendContextProviders(output); try {
addSeparator(output); String.format(messageFormat, args);
if (appendAnalyzers(output, throwable, messageFormat, args)) { } catch (IllegalFormatException e) {
addSeparator(output); messageFormat = StringUtil.replaceAll(messageFormat, "%", "%%");
}
appendMessageFormat(output, messageFormat, args); if (args.length != 0) {
messageFormat += "\nArgs:";
for (Object arg : args) {
try {
messageFormat += " \"" + arg.toString() + "\"";
} catch (Throwable t) {
messageFormat += " exc: \"" + t.getClass().toString() + "\"";
}
}
args = new Object[0]; // clear args
}
appendStackTrace(output, throwable); messageFormat += "\nCould not format provided description";
}
export(output.toString()); appendContextProviders(output);
addSeparator(output);
if (appendAnalyzers(output, throwable, messageFormat, args)) {
addSeparator(output);
}
System.exit(0); appendMessageFormat(output, messageFormat, args);
}
private static void appendContextProviders(StringBuilder output) { appendStackTrace(output, throwable);
// Do a local copy to avoid deadlocks -OLEGSHA export(output.toString());
ContextProvider[] localProvidersCopy = PROVIDERS.toArray(new ContextProvider[PROVIDERS.size()]);
for (ContextProvider provider : localProvidersCopy) { System.exit(0);
if (provider == null) }
continue;
addSeparator(output); private static void appendContextProviders(StringBuilder output) {
try { // Do a local copy to avoid deadlocks -OLEGSHA
Map<String, String> buf = new HashMap<>(); ContextProvider[] localProvidersCopy = PROVIDERS.toArray(new ContextProvider[PROVIDERS.size()]);
provider.provideContext(buf);
if (!buf.isEmpty()) { for (ContextProvider provider : localProvidersCopy) {
output.append("Provider name: ").append(provider.getName()).append("\n"); if (provider == null)
for (Map.Entry<String, String> entry : buf.entrySet()) { continue;
output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
}
} catch (Throwable t) {
output.append("\n");
String providerName; addSeparator(output);
try { try {
providerName = provider.getName(); Map<String, String> buf = new HashMap<>();
} catch (Throwable t1) { provider.provideContext(buf);
providerName = provider.getClass().getName();
}
output.append(providerName).append(" is broken").append("\n"); if (!buf.isEmpty()) {
// ContextProvider is broken output.append("Provider name: ").append(provider.getName()).append("\n");
} for (Map.Entry<String, String> entry : buf.entrySet()) {
} output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
} }
}
} catch (Throwable t) {
output.append("\n");
private static boolean appendAnalyzers(StringBuilder output, Throwable throwable, String messageFormat, String providerName;
Object[] args) {
boolean analyzerResponsesExist = false;
// Do a local copy to avoid deadlocks -OLEGSHA try {
Analyzer[] localAnalyzersCopy = ANALYZERS.toArray(new Analyzer[ANALYZERS.size()]); providerName = provider.getName();
} catch (Throwable t1) {
providerName = provider.getClass().getName();
}
for (Analyzer analyzer : localAnalyzersCopy) { output.append(providerName).append(" is broken").append("\n");
if (analyzer == null) // ContextProvider is broken
continue; }
}
}
String answer; private static boolean appendAnalyzers(StringBuilder output, Throwable throwable, String messageFormat,
try { Object[] args) {
answer = analyzer.analyze(throwable, messageFormat, args); boolean analyzerResponsesExist = false;
if (answer != null && !answer.isEmpty()) { // Do a local copy to avoid deadlocks -OLEGSHA
analyzerResponsesExist = true; Analyzer[] localAnalyzersCopy = ANALYZERS.toArray(new Analyzer[ANALYZERS.size()]);
output.append(analyzer.getName()).append(": ").append(answer).append("\n");
}
} catch (Throwable t) {
analyzerResponsesExist = true;
output.append("\n"); for (Analyzer analyzer : localAnalyzersCopy) {
if (analyzer == null)
continue;
String analyzerName; String answer;
try {
answer = analyzer.analyze(throwable, messageFormat, args);
try { if (answer != null && !answer.isEmpty()) {
analyzerName = analyzer.getName(); analyzerResponsesExist = true;
} catch (Throwable t1) { output.append(analyzer.getName()).append(": ").append(answer).append("\n");
analyzerName = analyzer.getClass().getName(); }
} } catch (Throwable t) {
analyzerResponsesExist = true;
output.append(analyzerName).append(" is broken").append("\n"); output.append("\n");
// Analyzer is broken
}
}
return analyzerResponsesExist; String analyzerName;
}
private static void appendMessageFormat(StringBuilder output, String messageFormat, Object... arg) { try {
output.append("Provided description: \n").append(String.format(messageFormat, arg)).append("\n"); analyzerName = analyzer.getName();
} catch (Throwable t1) {
analyzerName = analyzer.getClass().getName();
}
addSeparator(output); output.append(analyzerName).append(" is broken").append("\n");
} // Analyzer is broken
}
}
private static void appendStackTrace(StringBuilder output, Throwable throwable) { return analyzerResponsesExist;
output.append("Stacktrace: \n"); }
if (throwable == null) { private static void appendMessageFormat(StringBuilder output, String messageFormat, Object... arg) {
output.append("no Throwable provided").append("\n"); output.append("Provided description: \n").append(String.format(messageFormat, arg)).append("\n");
return;
}
// Formatting to a human-readable string addSeparator(output);
Writer sink = new StringBuilderWriter(output); }
try {
throwable.printStackTrace(new PrintWriter(sink));
} catch (Exception e) {
// PLAK
}
output.append("\n");
}
private static void export(String report) { private static void appendStackTrace(StringBuilder output, Throwable throwable) {
try { output.append("Stacktrace: \n");
LOGGER.fatal("/n" + report);
} catch (Exception e) {
// PLAK
}
System.err.println(report); if (throwable == null) {
output.append("no Throwable provided").append("\n");
return;
}
generateCrashReportFiles(report); // Formatting to a human-readable string
} Writer sink = new StringBuilderWriter(output);
try {
throwable.printStackTrace(new PrintWriter(sink));
} catch (Exception e) {
// PLAK
}
output.append("\n");
}
private static void generateCrashReportFiles(String output) { private static void export(String report) {
Date date = new Date(); try {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss"); LOGGER.fatal("/n" + report);
} catch (Exception e) {
// PLAK
}
try { System.err.println(report);
if (!Files.exists(CRASH_REPORTS_PATH))
Files.createDirectory(CRASH_REPORTS_PATH);
createFileForCrashReport(output, CRASH_REPORTS_PATH.toString() + "/latest.log"); generateCrashReportFiles(report);
createFileForCrashReport(output, }
CRASH_REPORTS_PATH.toString() + "/crash-" + dateFormat.format(date) + ".log");
} catch (Throwable t) {
// Crash Report not created
}
}
private static void createFileForCrashReport(String buffer, String filename) { private static void generateCrashReportFiles(String output) {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), StandardCharsets.UTF_8)) { Date date = new Date();
writer.write(buffer); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss");
} catch (IOException ex) {
// Crash Report not created
}
}
public static void registerProvider(ContextProvider provider) { try {
PROVIDERS.add(provider); if (!Files.exists(CRASH_REPORTS_PATH))
} Files.createDirectory(CRASH_REPORTS_PATH);
public static void registerAnalyzer(Analyzer analyzer) { createFileForCrashReport(output, CRASH_REPORTS_PATH.toString() + "/latest.log");
ANALYZERS.add(analyzer); createFileForCrashReport(output,
} CRASH_REPORTS_PATH.toString() + "/crash-" + dateFormat.format(date) + ".log");
} catch (Throwable t) {
// Crash Report not created
}
}
private static void addSeparator(StringBuilder sb) { private static void createFileForCrashReport(String buffer, String filename) {
sb.append( try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), StandardCharsets.UTF_8)) {
// 80 chars writer.write(buffer);
"--------------------------------------------------------------------------------").append("\n"); } catch (IOException ex) {
} // Crash Report not created
}
}
public static void registerProvider(ContextProvider provider) {
PROVIDERS.add(provider);
}
public static void registerAnalyzer(Analyzer analyzer) {
ANALYZERS.add(analyzer);
}
private static void addSeparator(StringBuilder sb) {
sb.append(
// 80 chars
"--------------------------------------------------------------------------------").append("\n");
}
} }