From 32cb6e21278ccfbab72704cfd77a7519e2854b3d Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 19 Aug 2025 11:07:14 -0700 Subject: [PATCH 1/3] feat: Identify the plugin causing an assertion failure --- .../Internal/Asserts/AssertHandler.cs | 60 +++++++++++++------ 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs index 91323f8ac..d596956c6 100644 --- a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; +using Dalamud.Plugin.Internal; using Dalamud.Utility; using Serilog; @@ -55,7 +56,8 @@ internal class AssertHandler : IDisposable /// public unsafe void Setup() { - CustomNativeFunctions.igCustom_SetAssertCallback(Marshal.GetFunctionPointerForDelegate(this.callback).ToPointer()); + CustomNativeFunctions.igCustom_SetAssertCallback( + Marshal.GetFunctionPointerForDelegate(this.callback).ToPointer()); } /// @@ -78,10 +80,11 @@ internal class AssertHandler : IDisposable var file = Marshal.PtrToStringAnsi(new IntPtr(pFile)); if (expr == null || file == null) { - Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (failed to parse)", - expr, - file, - line); + Log.Warning( + "ImGui assertion failed: {Expr} at {File}:{Line} (failed to parse)", + expr, + file, + line); return; } @@ -93,7 +96,7 @@ internal class AssertHandler : IDisposable if (!this.ShowAsserts && !this.everShownAssertThisSession) return; - Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace()).ToString()); + Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace())); if (!this.EnableVerboseLogging) { @@ -103,11 +106,12 @@ internal class AssertHandler : IDisposable if (count <= HideThreshold || count % HidePrintEvery == 0) { - Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)", - expr, - file, - line, - count); + Log.Warning( + "ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)", + expr, + file, + line, + count); } } else @@ -117,11 +121,12 @@ internal class AssertHandler : IDisposable } else { - Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}", - expr, - file, - line, - stackTrace.Value); + Log.Warning( + "ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}", + expr, + file, + line, + stackTrace.Value.ToString()); } if (!this.ShowAsserts) @@ -145,7 +150,7 @@ internal class AssertHandler : IDisposable } // grab the stack trace now that we've decided to show UI. - _ = stackTrace.Value; + var responsiblePlugin = Service.GetNullable()?.FindCallingPlugin(stackTrace.Value); var gitHubUrl = GetRepoUrl(); var showOnGitHubButton = new TaskDialogButton @@ -175,12 +180,29 @@ internal class AssertHandler : IDisposable var ignoreButton = TaskDialogButton.Ignore; TaskDialogButton? result = null; + void DialogThreadStart() { // TODO(goat): This is probably not gonna work if we showed the loading dialog // this session since it already loaded visual styles... 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" + + $"{expr}\nAt: {file}:{line}"; + var page = new TaskDialogPage { Heading = "ImGui assertion failed", @@ -189,9 +211,9 @@ internal class AssertHandler : IDisposable { CollapsedButtonText = "Show 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, Buttons = [ From 9e405b26d232e5b82a799f19e510a94970cf5e5f Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 19 Aug 2025 12:16:34 -0700 Subject: [PATCH 2/3] feat: include line numbers/file info in stacktrace --- Dalamud/Interface/Internal/Asserts/AssertHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs index d596956c6..d9f48ab54 100644 --- a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -96,7 +96,7 @@ internal class AssertHandler : IDisposable if (!this.ShowAsserts && !this.everShownAssertThisSession) return; - Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace())); + Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace(true))); if (!this.EnableVerboseLogging) { From 0c9176a8b6190ee2e86accb7db9d925b49e64857 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 19 Aug 2025 12:44:34 -0700 Subject: [PATCH 3/3] feat: Reword message overview - Removes extra lines from stack trace - Use clearer-ish wording for messaging --- .../Internal/Asserts/AssertHandler.cs | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs index d9f48ab54..a905ec132 100644 --- a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; @@ -74,6 +75,42 @@ internal class AssertHandler : IDisposable 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) { var expr = Marshal.PtrToStringAnsi(new IntPtr(pExpr)); @@ -96,7 +133,7 @@ internal class AssertHandler : IDisposable if (!this.ShowAsserts && !this.everShownAssertThisSession) return; - Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace(true))); + Lazy stackTrace = new(GenerateStackTrace); if (!this.EnableVerboseLogging) { @@ -151,6 +188,7 @@ internal class AssertHandler : IDisposable // grab the stack trace now that we've decided to show UI. var responsiblePlugin = Service.GetNullable()?.FindCallingPlugin(stackTrace.Value); + var responsibleMethodCall = ExtractImguiFunction(stackTrace.Value); var gitHubUrl = GetRepoUrl(); var showOnGitHubButton = new TaskDialogButton @@ -200,8 +238,16 @@ internal class AssertHandler : IDisposable } 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" + - $"{expr}\nAt: {file}:{line}"; + $"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 {