package net.minecraft; import com.google.common.collect.Lists; import com.mojang.logging.LogUtils; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletionException; import net.minecraft.util.MemoryReserve; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class CrashReport { private static final Logger LOGGER = LogUtils.getLogger(); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT); private final String title; private final Throwable exception; private final List details = Lists.newArrayList(); @Nullable private Path saveFile; private boolean trackingStackTrace = true; private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; private final SystemReport systemReport = new SystemReport(); public CrashReport(String title, Throwable exception) { this.title = title; this.exception = exception; } /** * Returns the description of the Crash Report. */ public String getTitle() { return this.title; } /** * Returns the Throwable object that is the cause for the crash and Crash Report. */ public Throwable getException() { return this.exception; } public String getDetails() { StringBuilder stringBuilder = new StringBuilder(); this.getDetails(stringBuilder); return stringBuilder.toString(); } /** * Gets the various sections of the crash report into the given StringBuilder */ public void getDetails(StringBuilder builder) { if ((this.uncategorizedStackTrace == null || this.uncategorizedStackTrace.length <= 0) && !this.details.isEmpty()) { this.uncategorizedStackTrace = ArrayUtils.subarray(((CrashReportCategory)this.details.get(0)).getStacktrace(), 0, 1); } if (this.uncategorizedStackTrace != null && this.uncategorizedStackTrace.length > 0) { builder.append("-- Head --\n"); builder.append("Thread: ").append(Thread.currentThread().getName()).append("\n"); builder.append("Stacktrace:\n"); for (StackTraceElement stackTraceElement : this.uncategorizedStackTrace) { builder.append("\t").append("at ").append(stackTraceElement); builder.append("\n"); } builder.append("\n"); } for (CrashReportCategory crashReportCategory : this.details) { crashReportCategory.getDetails(builder); builder.append("\n\n"); } this.systemReport.appendToCrashReportString(builder); } /** * Gets the stack trace of the Throwable that caused this crash report, or if that fails, the cause {@code .toString()}. */ public String getExceptionMessage() { StringWriter stringWriter = null; PrintWriter printWriter = null; Throwable throwable = this.exception; if (throwable.getMessage() == null) { if (throwable instanceof NullPointerException) { throwable = new NullPointerException(this.title); } else if (throwable instanceof StackOverflowError) { throwable = new StackOverflowError(this.title); } else if (throwable instanceof OutOfMemoryError) { throwable = new OutOfMemoryError(this.title); } throwable.setStackTrace(this.exception.getStackTrace()); } String var4; try { stringWriter = new StringWriter(); printWriter = new PrintWriter(stringWriter); throwable.printStackTrace(printWriter); var4 = stringWriter.toString(); } finally { IOUtils.closeQuietly(stringWriter); IOUtils.closeQuietly(printWriter); } return var4; } public String getFriendlyReport(ReportType type, List links) { StringBuilder stringBuilder = new StringBuilder(); type.appendHeader(stringBuilder, links); stringBuilder.append("Time: "); stringBuilder.append(DATE_TIME_FORMATTER.format(ZonedDateTime.now())); stringBuilder.append("\n"); stringBuilder.append("Description: "); stringBuilder.append(this.title); stringBuilder.append("\n\n"); stringBuilder.append(this.getExceptionMessage()); stringBuilder.append("\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n"); for (int i = 0; i < 87; i++) { stringBuilder.append("-"); } stringBuilder.append("\n\n"); this.getDetails(stringBuilder); return stringBuilder.toString(); } public String getFriendlyReport(ReportType type) { return this.getFriendlyReport(type, List.of()); } @Nullable public Path getSaveFile() { return this.saveFile; } public boolean saveToFile(Path path, ReportType type, List links) { if (this.saveFile != null) { return false; } else { try { if (path.getParent() != null) { FileUtil.createDirectoriesSafe(path.getParent()); } Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8); try { writer.write(this.getFriendlyReport(type, links)); } catch (Throwable var8) { if (writer != null) { try { writer.close(); } catch (Throwable var7) { var8.addSuppressed(var7); } } throw var8; } if (writer != null) { writer.close(); } this.saveFile = path; return true; } catch (Throwable var9) { LOGGER.error("Could not save crash report to {}", path, var9); return false; } } } public boolean saveToFile(Path path, ReportType type) { return this.saveToFile(path, type, List.of()); } public SystemReport getSystemReport() { return this.systemReport; } /** * Creates a CrashReportCategory */ public CrashReportCategory addCategory(String name) { return this.addCategory(name, 1); } /** * Creates a CrashReportCategory for the given stack trace depth */ public CrashReportCategory addCategory(String categoryName, int stacktraceLength) { CrashReportCategory crashReportCategory = new CrashReportCategory(categoryName); if (this.trackingStackTrace) { int i = crashReportCategory.fillInStackTrace(stacktraceLength); StackTraceElement[] stackTraceElements = this.exception.getStackTrace(); StackTraceElement stackTraceElement = null; StackTraceElement stackTraceElement2 = null; int j = stackTraceElements.length - i; if (j < 0) { LOGGER.error("Negative index in crash report handler ({}/{})", stackTraceElements.length, i); } if (stackTraceElements != null && 0 <= j && j < stackTraceElements.length) { stackTraceElement = stackTraceElements[j]; if (stackTraceElements.length + 1 - i < stackTraceElements.length) { stackTraceElement2 = stackTraceElements[stackTraceElements.length + 1 - i]; } } this.trackingStackTrace = crashReportCategory.validateStackTrace(stackTraceElement, stackTraceElement2); if (stackTraceElements != null && stackTraceElements.length >= i && 0 <= j && j < stackTraceElements.length) { this.uncategorizedStackTrace = new StackTraceElement[j]; System.arraycopy(stackTraceElements, 0, this.uncategorizedStackTrace, 0, this.uncategorizedStackTrace.length); } else { this.trackingStackTrace = false; } } this.details.add(crashReportCategory); return crashReportCategory; } /** * Creates a crash report for the exception */ public static CrashReport forThrowable(Throwable cause, String description) { while (cause instanceof CompletionException && cause.getCause() != null) { cause = cause.getCause(); } CrashReport crashReport; if (cause instanceof ReportedException reportedException) { crashReport = reportedException.getReport(); } else { crashReport = new CrashReport(description, cause); } return crashReport; } public static void preload() { MemoryReserve.allocate(); new CrashReport("Don't panic!", new Throwable()).getFriendlyReport(ReportType.CRASH); } }