mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-12 16:27:47 +00:00
Backport CrashReportAnalyser to 1.18.2 (#9759)
This commit is contained in:
1 parent
13ee25369c
commit
f86a22ce35
2 files changed
+229
-1
No files matched your search
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/CrashReport.java
|
||||
+++ b/net/minecraft/CrashReport.java
|
||||
@@ -55,14 +_,8 @@
|
||||
@@ -55,14 +_,9 @@
|
||||
if (this.f_127506_ != null && this.f_127506_.length > 0) {
|
||||
p_127520_.append("-- Head --\n");
|
||||
p_127520_.append("Thread: ").append(Thread.currentThread().getName()).append("\n");
|
||||
@@ -12,6 +12,7 @@
|
||||
- }
|
||||
-
|
||||
- p_127520_.append("\n");
|
||||
+ p_127520_.append(net.minecraftforge.logging.CrashReportAnalyser.appendSuspectedMods(this.f_127501_, this.f_127506_));
|
||||
+ p_127520_.append("Stacktrace:");
|
||||
+ p_127520_.append(net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(this.f_127506_));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.logging;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
public final class CrashReportAnalyser {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Map<String, IModInfo> PACKAGE_MOD_CACHE = new HashMap<>();
|
||||
private static final Map<IModInfo, String[]> SUSPECTED_MODS = new HashMap<>();
|
||||
|
||||
private CrashReportAnalyser() {}
|
||||
|
||||
/**
|
||||
* Tries to cache, analyze and append the suspected mods for this crash to the crash report.
|
||||
* */
|
||||
public static String appendSuspectedMods(Throwable throwable, StackTraceElement[] uncategorizedStackTrace) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
try {
|
||||
cacheModList();
|
||||
analyseCrashReport(throwable, uncategorizedStackTrace);
|
||||
buildSuspectedModsSection(stringBuilder);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
LOGGER.error("Failed to append suspected mod(s) to crash report!", t);
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to analyze the given exception and uncategorized stacktrace of the crash report.
|
||||
* It checks the stacktrace for any occurrence of the package names cached in the {@link #PACKAGE_MOD_CACHE},
|
||||
* including mixin classes.
|
||||
* **/
|
||||
private static void analyseCrashReport(Throwable throwable, StackTraceElement[] uncategorizedStackTrace) {
|
||||
scanThrowable(throwable);
|
||||
scanStacktrace(uncategorizedStackTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the suspected mod(s) for this crash to a String to be added to the crash report,
|
||||
* including the mods name, its id, version and an issues link if available,
|
||||
* as well as the position where the exception occurred (either the stacktrace element
|
||||
* or the mixin class and its target(s))
|
||||
* **/
|
||||
private static void buildSuspectedModsSection(StringBuilder stringBuilder) {
|
||||
stringBuilder.append("Suspected Mod");
|
||||
stringBuilder.append(SUSPECTED_MODS.size() == 1 ? ": " : "s: ");
|
||||
if (SUSPECTED_MODS.isEmpty()) {
|
||||
stringBuilder.append("NONE\n");
|
||||
}
|
||||
else {
|
||||
SUSPECTED_MODS.forEach((iModInfo, position) -> {
|
||||
stringBuilder
|
||||
.append("\n\t")
|
||||
.append(iModInfo.getDisplayName())
|
||||
.append(" (").append(iModInfo.getModId()).append("),")
|
||||
.append(" Version: ").append(iModInfo.getVersion());
|
||||
|
||||
iModInfo.getOwningFile().getConfig().<String>getConfigElement("issueTrackerURL").ifPresent(issuesLink -> {
|
||||
stringBuilder
|
||||
.append("\n\t\t")
|
||||
.append("Issue tracker URL: ")
|
||||
.append(issuesLink);
|
||||
});
|
||||
|
||||
stringBuilder.append("\n\t\t");
|
||||
|
||||
for (String s : position) {
|
||||
stringBuilder.append(s);
|
||||
}
|
||||
stringBuilder.append("\n");
|
||||
});
|
||||
SUSPECTED_MODS.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the stacktrace of the given throwable and all its children for any occurrence of the package names cached in the {@link #PACKAGE_MOD_CACHE},
|
||||
* including mixin classes.
|
||||
* **/
|
||||
private static void scanThrowable(Throwable throwable) {
|
||||
scanStacktrace(throwable.getStackTrace());
|
||||
|
||||
if (throwable.getCause() != null && throwable.getCause() != throwable) {
|
||||
scanThrowable(throwable.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given stacktrace for any occurrence of the package names cached in the {@link #PACKAGE_MOD_CACHE},
|
||||
* including mixin classes.
|
||||
* **/
|
||||
private static void scanStacktrace(StackTraceElement[] stackTrace) {
|
||||
for (StackTraceElement stackTraceElement : stackTrace) {
|
||||
identifyByClass(stackTraceElement);
|
||||
identifyByMixins(stackTraceElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates over all loaded mods, resolving and caching their package names with the corresponding {@link IModInfo}.
|
||||
* **/
|
||||
private static void cacheModList() {
|
||||
ModList modList = ModList.get();
|
||||
ModuleLayer gameLayer = FMLLoader.getGameLayer();
|
||||
|
||||
if (modList != null) {
|
||||
modList.getMods().forEach(iModInfo -> {
|
||||
//Don't cache minecraft or forge as they will always be included in the stacktrace
|
||||
if (!iModInfo.getModId().equals("forge") && !iModInfo.getModId().equals("minecraft")) {
|
||||
Set<String> packages = new HashSet<>();
|
||||
|
||||
gameLayer.findModule(iModInfo.getModId()).ifPresent(module -> packages.addAll(module.getPackages()));
|
||||
|
||||
packages.forEach(s -> PACKAGE_MOD_CACHE.put(s, iModInfo));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void identifyByClass(StackTraceElement stackTraceElement) {
|
||||
blameIfPresent(stackTraceElement);
|
||||
}
|
||||
|
||||
private static void identifyByMixins(StackTraceElement stackTraceElement) {
|
||||
IMixinInfo mixinInfo = getMixinInfo(stackTraceElement);
|
||||
|
||||
if (mixinInfo != null) {
|
||||
String elementAsString = stackTraceElement.toString();
|
||||
String mixinClassName = mixinInfo.getClassName();
|
||||
List<String> targetClasses = mixinInfo.getTargetClasses();
|
||||
blameIfPresent(mixinClassName, "Mixin class: ", mixinClassName, "\n\t\tTarget", (targetClasses.size() == 1 ? ": " + targetClasses.get(0) : "s: " + targetClasses).replaceAll("/", "."), "\n\t\tat ", elementAsString);
|
||||
}
|
||||
}
|
||||
|
||||
private static void blameIfPresent(StackTraceElement stackTraceElement) {
|
||||
blameIfPresent(stackTraceElement.getClassName(), "at ", stackTraceElement.toString());
|
||||
}
|
||||
|
||||
private static void blameIfPresent(String className, String... position) {
|
||||
String commonPackage = findMatchingPackage(className);
|
||||
|
||||
if (commonPackage != null) {
|
||||
SUSPECTED_MODS.putIfAbsent(PACKAGE_MOD_CACHE.get(commonPackage), position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the beginning of the given class name is contained in the {@link #PACKAGE_MOD_CACHE}
|
||||
* @return the package name of the class or null if it is not present in the cache
|
||||
* */
|
||||
@Nullable
|
||||
private static String findMatchingPackage(String className) {
|
||||
for (String s : PACKAGE_MOD_CACHE.keySet()) {
|
||||
if (className.startsWith(s)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MixinMerged findMixinMerged(StackTraceElement element) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(element.getClassName());
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(element.getMethodName())) {
|
||||
MixinMerged mixinMerged = method.getAnnotation(MixinMerged.class);
|
||||
if (mixinMerged != null) {
|
||||
return mixinMerged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException | NoClassDefFoundError ignored) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to identify any applied mixin for the class and method provided by the stacktrace element.
|
||||
* @param element the stacktrace element to check
|
||||
* @return the {@link IMixinInfo} for the class and method or null if there is no mixin applied at this position
|
||||
* **/
|
||||
@Nullable
|
||||
private static IMixinInfo getMixinInfo(StackTraceElement element) {
|
||||
MixinMerged mixinMerged = findMixinMerged(element);
|
||||
if (mixinMerged != null) {
|
||||
ClassInfo classInfo = ClassInfo.forName(mixinMerged.mixin().replace('.', '/'));
|
||||
if (classInfo != null) {
|
||||
try {
|
||||
Field mixinField = ClassInfo.class.getDeclaredField("mixin");
|
||||
mixinField.setAccessible(true);
|
||||
return (IMixinInfo) mixinField.get(classInfo);
|
||||
}
|
||||
catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user