From c0d0ad0282169a829ffee4808088ebf434ba498d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 14 Apr 2022 23:55:08 -0400 Subject: [PATCH] show simplified stack trace for deprecation notices --- docs/release-notes.md | 1 + src/SMAPI/Framework/DeprecationManager.cs | 45 +++++++++++++++++++++-- src/SMAPI/Framework/ModRegistry.cs | 20 +++++++--- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index fed92d8d..01ba6e30 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -57,6 +57,7 @@ the C# mod that loads them is updated. * Added `helper.Content.ParseAssetName` to get an `IAssetName` for an arbitrary asset key. * If an asset is loaded multiple times in the same tick, `IAssetLoader.CanLoad` and `IAssetEditor.CanEdit` are now cached unless invalidated by `helper.Content.InvalidateCache`. * The `ISemanticVersion` comparison methods (`CompareTo`, `IsBetween`, `IsNewerThan`, and `IsOlderThan`) now allow null values. A null version is always considered older than any non-null version per [best practices](https://docs.microsoft.com/en-us/dotnet/api/system.icomparable-1.compareto#remarks). + * Deprecation notices now show a shorter stack trace in most cases, so it's clearer where the deprecated code is in the mod. * Fixes: * Fixed the `SDate` constructor being case-sensitive. * Fixed support for using locale codes from custom languages in asset names (e.g. `Data/Achievements.eo-EU`). diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index c80fdce7..37a5c8ef 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; namespace StardewModdingAPI.Framework { @@ -62,7 +63,8 @@ namespace StardewModdingAPI.Framework return; // queue warning - this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, new StackTrace(skipFrames: 1))); + var stack = new StackTrace(skipFrames: 1); // skip this method + this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack)); } /// A placeholder method used to track deprecated code for which a separate warning will be shown. @@ -100,11 +102,11 @@ namespace StardewModdingAPI.Framework // log message if (level == LogLevel.Trace) - this.Monitor.Log($"{message}\n{warning.StackTrace}", level); + this.Monitor.Log($"{message}\n{this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod)}", level); else { this.Monitor.Log(message, level); - this.Monitor.Log(warning.StackTrace.ToString(), LogLevel.Debug); + this.Monitor.Log(this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod), LogLevel.Debug); } } @@ -128,5 +130,42 @@ namespace StardewModdingAPI.Framework this.LoggedDeprecations.Add(key); return true; } + + /// Get the simplest stack trace which shows where in the mod the deprecated code was called from. + /// The stack trace. + /// The mod for which to show a stack trace. + private string GetSimplifiedStackTrace(StackTrace stack, IModMetadata? mod) + { + // unknown mod, show entire stack trace + if (mod == null) + return stack.ToString(); + + // get frame info + var frames = stack + .GetFrames() + .Select(frame => (Frame: frame, Mod: this.ModRegistry.GetFrom(frame))) + .ToArray(); + var modIds = new HashSet( + from frame in frames + let id = frame.Mod?.Manifest.UniqueID + where id != null + select id + ); + + // can't filter to the target mod + if (modIds.Count != 1 || !modIds.Contains(mod.Manifest.UniqueID)) + return stack.ToString(); + + // get stack frames for the target mod, plus one for context + var framesStartingAtMod = frames.SkipWhile(p => p.Mod == null).ToArray(); + var displayFrames = framesStartingAtMod.TakeWhile(p => p.Mod != null).ToArray(); + displayFrames = displayFrames.Concat(framesStartingAtMod.Skip(displayFrames.Length).Take(1)).ToArray(); + + // build stack trace + StringBuilder str = new(); + foreach (var frame in displayFrames) + str.Append(new StackTrace(frame.Frame)); + return str.ToString().TrimEnd(); + } } } diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index de16a780..449dccb1 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -59,7 +59,7 @@ namespace StardewModdingAPI.Framework /// Get metadata for a loaded mod. /// The mod's unique ID. - /// Returns the matching mod's metadata, or null if not found. + /// Returns the mod's metadata, or null if not found. public IModMetadata? Get(string uniqueID) { // normalize search ID @@ -73,7 +73,7 @@ namespace StardewModdingAPI.Framework /// Get the mod metadata from one of its assemblies. /// The type to check. - /// Returns the mod name, or null if the type isn't part of a known mod. + /// Returns the mod's metadata, or null if the type isn't part of a known mod. public IModMetadata? GetFrom(Type? type) { // null @@ -89,8 +89,17 @@ namespace StardewModdingAPI.Framework return null; } - /// Get the friendly name for the closest assembly registered as a source of deprecation warnings. - /// Returns the source name, or null if no registered assemblies were found. + /// Get the mod metadata from a stack frame, if any. + /// The stack frame to check. + /// Returns the mod's metadata, or null if the frame isn't part of a known mod. + public IModMetadata? GetFrom(StackFrame frame) + { + MethodBase? method = frame.GetMethod(); + return this.GetFrom(method?.ReflectedType); + } + + /// Get the mod metadata from the closest assembly registered as a source of deprecation warnings. + /// Returns the mod's metadata, or null if no registered assemblies were found. public IModMetadata? GetFromStack() { // get stack frames @@ -100,8 +109,7 @@ namespace StardewModdingAPI.Framework // search stack for a source assembly foreach (StackFrame frame in frames) { - MethodBase? method = frame.GetMethod(); - IModMetadata? mod = this.GetFrom(method?.ReflectedType); + IModMetadata? mod = this.GetFrom(frame); if (mod != null) return mod; }