Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Modules/Minecraft/CrashAnalysis/Detection/CrashStackAnalyzer.cs
T

199 lines
7.1 KiB
C#
Raw Normal View History

using System.Text.RegularExpressions;
using PCL.Core.Logging;
namespace PCL;
internal sealed partial class CrashStackAnalyzer
{
private static readonly HashSet<string> IgnoredPrefixes = new(StringComparer.Ordinal)
{
"java", "sun", "javax", "jdk", "com.sun",
"com.mojang", "net.minecraft", "MojangTricksIntelDriversForPerformance_javaw",
"net.minecraftforge", "cpw.mods", "net.fabricmc", "org.quiltmc", "org.spongepowered", "com.mumfrey",
"org.lwjgl", "paulscode.sound", "com.google", "org.apache", "com.electronwill.nightconfig", "it.unimi.dsi",
"oolloo"
};
private static readonly HashSet<string> IgnoredWords = new(StringComparer.OrdinalIgnoreCase)
{
"com", "org", "net", "top", "dev", "gitlab", "github",
"sun", "lib", "nio", "api", "asm", "reflect", "internal", "microsoft",
"mcp", "fml", "forge", "fabricmc", "neoforge", "neoforged", "minecraftforge", "minecraft", "mojang",
"mod", "mods", "modapi", "jar", "loader", "launch", "preinit", "preload", "init", "setup", "plugin",
"mixin", "mixins", "injection", "transformer", "transformers", "spongepowered",
"game", "world", "server", "client", "common", "entity", "block", "item", "tile", "blockentity",
"gui", "model", "render", "shader", "optifine",
"config", "data", "file", "read", "recipe", "content", "general",
"event", "events", "handler", "listeners", "assist", "override",
"netty", "packet", "channel",
"task", "pool", "scheduler", "systems", "system", "modules", "service", "platform",
"core", "main", "base", "util", "impl", "done", "map", "load", "machine",
"dsi", "unimi", "fastutil", "lwjgl", "oshi", "electronwill",
"compat", "universal", "multipart"
};
public CrashFinding? Analyze(
CrashLogSet logs,
CrashModIndex modIndex)
{
if (!_ShouldAnalyzeStack(logs.All))
{
LogWrapper.Info("Crash", "可能并未安装 Mod,不进行堆栈分析");
return null;
}
var stackText = _CollectStackText(logs);
var packages = _ExtractPackages(stackText).ToList();
var keywords = _ExtractKeywords(packages).ToList();
if (keywords.Count is 0 or > 10)
{
if (keywords.Count > 10)
LogWrapper.Info("Crash", "关键词过多,考虑匹配出错,不纳入考虑");
return null;
}
var mods = modIndex.ResolveMany(keywords).ToList();
if (mods.Count != 0)
return new CrashFinding(
CrashCause.StackModNameFound,
CrashConfidence.Medium,
mods.Select(mod => new CrashEvidence
{
Source = CrashLogKind.All,
Value = mod.DisplayNameOrFileName,
DisplayKind = "mod"
}))
{
ShouldStop = true
};
return new CrashFinding(
CrashCause.StackKeywordFound,
CrashConfidence.Low,
keywords.Select(keyword => new CrashEvidence
{
Source = CrashLogKind.All,
Value = keyword,
DisplayKind = "keyword"
}))
{
ShouldStop = true
};
}
private static bool _ShouldAnalyzeStack(string allLogs)
{
return allLogs.Contains("forge", StringComparison.OrdinalIgnoreCase) ||
allLogs.Contains("fabric", StringComparison.OrdinalIgnoreCase) ||
allLogs.Contains("quilt", StringComparison.OrdinalIgnoreCase) ||
allLogs.Contains("liteloader", StringComparison.OrdinalIgnoreCase);
}
private static string _CollectStackText(CrashLogSet logs)
{
var result = new List<string>();
if (logs.CrashReport?.Text is not null)
{
LogWrapper.Info("Crash", "开始进行崩溃日志堆栈分析");
result.Add(CrashText.BeforeFirst(logs.CrashReport.Text, "System Details"));
}
if (logs.Game?.Text is not null)
{
var fatals = CrashRegex.All(logs.Game.Text, @"/FATAL] .+?(?=[\n]+\[)");
if (logs.Game.Text.Contains("Unreported exception thrown!", StringComparison.Ordinal))
fatals.Add(
CrashText.Between(logs.Game.Text,
"Unreported exception thrown!",
"at oolloo.jlw.Wrapper"));
LogWrapper.Info("Crash", "开始进行 Minecraft 日志堆栈分析,发现 " + fatals.Count + " 个报错项");
result.AddRange(fatals);
}
if (logs.HsErr?.Text is not null)
{
LogWrapper.Info("Crash", "开始进行虚拟机堆栈分析");
result.Add(CrashText.Between(logs.HsErr.Text, "T H R E A D", "Registers:"));
}
return "\n" + string.Join("\n", result) + "\n";
}
private static IEnumerable<string> _ExtractPackages(string stackText)
{
var packages = new List<string>();
packages.AddRange(
PackageRegex()
.Matches(stackText)
.Select(match => match.Value));
packages.AddRange(
MixinStackRegex()
.Matches(stackText)
.Select(match => match.Value.Replace("$", ".")));
var possibleStacks = packages
.Select(item => item.Trim())
.Where(item => !string.IsNullOrWhiteSpace(item))
.Distinct(StringComparer.Ordinal)
.Where(item => !IgnoredPrefixes.Any(prefix => item.StartsWith(prefix, StringComparison.Ordinal)))
.ToList();
LogWrapper.Info("Crash", "找到 " + possibleStacks.Count + " 条可能的堆栈信息");
foreach (var stack in possibleStacks)
LogWrapper.Info("Crash", " - " + stack);
return possibleStacks;
}
private static IEnumerable<string> _ExtractKeywords(IEnumerable<string> packages)
{
var words = new List<string>();
foreach (var package in packages)
{
var split = package.Split('.');
for (var i = 0; i <= Math.Min(3, split.Length - 1); i++)
{
var word = split[i].Trim();
if (word.Length <= 2 ||
word.StartsWith("func_", StringComparison.Ordinal) ||
IgnoredWords.Contains(word))
continue;
words.Add(word);
}
}
var result = words
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
LogWrapper.Info("Crash", "从堆栈信息中找到 " + result.Count + " 个可能的 Mod ID 关键词");
if (result.Count != 0)
LogWrapper.Info("Crash", " - " + string.Join(", ", result));
return result;
}
[GeneratedRegex(
@"(?<=\n[^{]+)[a-zA-Z_]+\w+\.[a-zA-Z_]+[\w\.]+(?=\.[\w\.$]+\.)",
RegexOptions.Compiled)]
private static partial Regex PackageRegex();
[GeneratedRegex(
@"(?<=at [^(]+?\.\w+\$\w+\$)[\w\$]+?(?=\$\w+\()",
RegexOptions.Compiled)]
private static partial Regex MixinStackRegex();
}