mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge pull request #2377 from KazWolfe/assert-blame
feat: Identify the plugin causing an assertion failure
This commit is contained in:
commit
832edaf005
1 changed files with 87 additions and 19 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -55,7 +57,8 @@ internal class AssertHandler : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe void Setup()
|
public unsafe void Setup()
|
||||||
{
|
{
|
||||||
CustomNativeFunctions.igCustom_SetAssertCallback(Marshal.GetFunctionPointerForDelegate(this.callback).ToPointer());
|
CustomNativeFunctions.igCustom_SetAssertCallback(
|
||||||
|
Marshal.GetFunctionPointerForDelegate(this.callback).ToPointer());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -72,16 +75,53 @@ internal class AssertHandler : IDisposable
|
||||||
this.Shutdown();
|
this.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? ExtractImguiFunction(StackTrace stackTrace)
|
||||||
|
{
|
||||||
|
var frame = stackTrace.GetFrames()
|
||||||
|
.FirstOrDefault(f => f.GetMethod()?.DeclaringType?.Namespace == "Dalamud.Bindings.ImGui");
|
||||||
|
if (frame == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var method = frame.GetMethod();
|
||||||
|
if (method == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $"{method.Name}({string.Join(", ", method.GetParameters().Select(p => p.Name))})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StackTrace GenerateStackTrace()
|
||||||
|
{
|
||||||
|
var trace = DiagnosticUtil.GetUsefulTrace(new StackTrace(true));
|
||||||
|
var frames = trace.GetFrames().ToList();
|
||||||
|
|
||||||
|
// Remove everything that happens in the assert context.
|
||||||
|
var lastAssertIdx = frames.FindLastIndex(f => f.GetMethod()?.DeclaringType == typeof(AssertHandler));
|
||||||
|
if (lastAssertIdx >= 0)
|
||||||
|
{
|
||||||
|
frames.RemoveRange(0, lastAssertIdx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstInterfaceManagerIdx = frames.FindIndex(f => f.GetMethod()?.DeclaringType == typeof(InterfaceManager));
|
||||||
|
if (firstInterfaceManagerIdx >= 0)
|
||||||
|
{
|
||||||
|
frames.RemoveRange(firstInterfaceManagerIdx, frames.Count - firstInterfaceManagerIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StackTrace(frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private unsafe void OnImGuiAssert(void* pExpr, void* pFile, int line)
|
private unsafe void OnImGuiAssert(void* pExpr, void* pFile, int line)
|
||||||
{
|
{
|
||||||
var expr = Marshal.PtrToStringAnsi(new IntPtr(pExpr));
|
var expr = Marshal.PtrToStringAnsi(new IntPtr(pExpr));
|
||||||
var file = Marshal.PtrToStringAnsi(new IntPtr(pFile));
|
var file = Marshal.PtrToStringAnsi(new IntPtr(pFile));
|
||||||
if (expr == null || file == null)
|
if (expr == null || file == null)
|
||||||
{
|
{
|
||||||
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (failed to parse)",
|
Log.Warning(
|
||||||
expr,
|
"ImGui assertion failed: {Expr} at {File}:{Line} (failed to parse)",
|
||||||
file,
|
expr,
|
||||||
line);
|
file,
|
||||||
|
line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +133,7 @@ internal class AssertHandler : IDisposable
|
||||||
if (!this.ShowAsserts && !this.everShownAssertThisSession)
|
if (!this.ShowAsserts && !this.everShownAssertThisSession)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Lazy<string> stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace()).ToString());
|
Lazy<StackTrace> stackTrace = new(GenerateStackTrace);
|
||||||
|
|
||||||
if (!this.EnableVerboseLogging)
|
if (!this.EnableVerboseLogging)
|
||||||
{
|
{
|
||||||
|
|
@ -103,11 +143,12 @@ internal class AssertHandler : IDisposable
|
||||||
|
|
||||||
if (count <= HideThreshold || count % HidePrintEvery == 0)
|
if (count <= HideThreshold || count % HidePrintEvery == 0)
|
||||||
{
|
{
|
||||||
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)",
|
Log.Warning(
|
||||||
expr,
|
"ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)",
|
||||||
file,
|
expr,
|
||||||
line,
|
file,
|
||||||
count);
|
line,
|
||||||
|
count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -117,11 +158,12 @@ internal class AssertHandler : IDisposable
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}",
|
Log.Warning(
|
||||||
expr,
|
"ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}",
|
||||||
file,
|
expr,
|
||||||
line,
|
file,
|
||||||
stackTrace.Value);
|
line,
|
||||||
|
stackTrace.Value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.ShowAsserts)
|
if (!this.ShowAsserts)
|
||||||
|
|
@ -145,7 +187,8 @@ internal class AssertHandler : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab the stack trace now that we've decided to show UI.
|
// grab the stack trace now that we've decided to show UI.
|
||||||
_ = stackTrace.Value;
|
var responsiblePlugin = Service<PluginManager>.GetNullable()?.FindCallingPlugin(stackTrace.Value);
|
||||||
|
var responsibleMethodCall = ExtractImguiFunction(stackTrace.Value);
|
||||||
|
|
||||||
var gitHubUrl = GetRepoUrl();
|
var gitHubUrl = GetRepoUrl();
|
||||||
var showOnGitHubButton = new TaskDialogButton
|
var showOnGitHubButton = new TaskDialogButton
|
||||||
|
|
@ -175,12 +218,37 @@ internal class AssertHandler : IDisposable
|
||||||
var ignoreButton = TaskDialogButton.Ignore;
|
var ignoreButton = TaskDialogButton.Ignore;
|
||||||
|
|
||||||
TaskDialogButton? result = null;
|
TaskDialogButton? result = null;
|
||||||
|
|
||||||
void DialogThreadStart()
|
void DialogThreadStart()
|
||||||
{
|
{
|
||||||
// TODO(goat): This is probably not gonna work if we showed the loading dialog
|
// TODO(goat): This is probably not gonna work if we showed the loading dialog
|
||||||
// this session since it already loaded visual styles...
|
// this session since it already loaded visual styles...
|
||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
|
|
||||||
|
string text;
|
||||||
|
if (responsiblePlugin != null)
|
||||||
|
{
|
||||||
|
text = $"The plugin \"{responsiblePlugin.Name}\" appears to have caused an ImGui assertion failure. " +
|
||||||
|
$"Please report this problem to the plugin's developer.\n\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text = "Some code in a plugin or Dalamud itself has caused an ImGui assertion failure. " +
|
||||||
|
"Please report this problem in the Dalamud discord.\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
text += $"You may attempt to continue running the game, but Dalamud UI elements may not work " +
|
||||||
|
$"correctly, or the game may crash after resuming.\n\n";
|
||||||
|
|
||||||
|
if (responsibleMethodCall != null)
|
||||||
|
{
|
||||||
|
text += $"Assertion failed: {expr} when performing {responsibleMethodCall}\n{file}:{line}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text += $"Assertion failed: {expr}\nAt: {file}:{line}";
|
||||||
|
}
|
||||||
|
|
||||||
var page = new TaskDialogPage
|
var page = new TaskDialogPage
|
||||||
{
|
{
|
||||||
Heading = "ImGui assertion failed",
|
Heading = "ImGui assertion failed",
|
||||||
|
|
@ -189,9 +257,9 @@ internal class AssertHandler : IDisposable
|
||||||
{
|
{
|
||||||
CollapsedButtonText = "Show stack trace",
|
CollapsedButtonText = "Show stack trace",
|
||||||
ExpandedButtonText = "Hide stack trace",
|
ExpandedButtonText = "Hide stack trace",
|
||||||
Text = stackTrace.Value,
|
Text = stackTrace.Value.ToString(),
|
||||||
},
|
},
|
||||||
Text = $"Some code in a plugin or Dalamud itself has caused an internal assertion in ImGui to fail. The game will most likely crash now.\n\n{expr}\nAt: {file}:{line}",
|
Text = text,
|
||||||
Icon = TaskDialogIcon.Warning,
|
Icon = TaskDialogIcon.Warning,
|
||||||
Buttons =
|
Buttons =
|
||||||
[
|
[
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue