From 616dc1d7029d7167a034cc6e6e60194382846370 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:35:47 -0700 Subject: [PATCH 1/2] Provide a WholeProjectDecompiler constructor with no default parameter values --- ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs | 4 ++-- .../CSharp/ProjectDecompiler/IProjectInfoProvider.cs | 2 +- .../ProjectDecompiler/WholeProjectDecompiler.cs | 12 ++++++------ .../Metadata/AssemblyReferences.cs | 8 +++++++- .../BamlAwareWholeProjectDecompiler.cs | 2 +- ILSpy/Languages/CSharpLanguage.cs | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index dc1808f104..6fc0feea93 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -290,8 +290,8 @@ static async Task RunTest(string outputDir, string fileToTest) class TestProjectDecompiler : WholeProjectDecompiler { - public TestProjectDecompiler(Guid projectGuid, IAssemblyResolver resolver, AssemblyReferenceClassifier assemblyReferenceClassifier, DecompilerSettings settings) - : base(settings, projectGuid, resolver, null, assemblyReferenceClassifier, debugInfoProvider: null) + public TestProjectDecompiler(Guid projectGuid, IAssemblyResolver resolver, IAssemblyReferenceClassifier assemblyReferenceClassifier, DecompilerSettings settings) + : base(settings, projectGuid, resolver, IProjectFileWriter.FromSettings(settings), assemblyReferenceClassifier, debugInfoProvider: null) { } } diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs index cd29f1d80f..cdc1ec2dbe 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs @@ -32,7 +32,7 @@ public interface IProjectInfoProvider /// IAssemblyResolver AssemblyResolver { get; } - AssemblyReferenceClassifier AssemblyReferenceClassifier { get; } + IAssemblyReferenceClassifier AssemblyReferenceClassifier { get; } /// /// Gets the C# language version of the project. diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 067c030109..5ded93304b 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -73,7 +73,7 @@ public LanguageVersion LanguageVersion { public IAssemblyResolver AssemblyResolver { get; } - public AssemblyReferenceClassifier AssemblyReferenceClassifier { get; } + public IAssemblyReferenceClassifier AssemblyReferenceClassifier { get; } public IDebugInfoProvider DebugInfoProvider { get; } @@ -111,9 +111,9 @@ public WholeProjectDecompiler( DecompilerSettings settings, IAssemblyResolver assemblyResolver, IProjectFileWriter projectWriter, - AssemblyReferenceClassifier assemblyReferenceClassifier, + IAssemblyReferenceClassifier assemblyReferenceClassifier, IDebugInfoProvider debugInfoProvider) - : this(settings, Guid.NewGuid(), assemblyResolver, projectWriter, assemblyReferenceClassifier, debugInfoProvider) + : this(settings, Guid.NewGuid(), assemblyResolver, projectWriter ?? IProjectFileWriter.FromSettings(settings), assemblyReferenceClassifier ?? new AssemblyReferenceClassifier(), debugInfoProvider) { } @@ -122,15 +122,15 @@ protected WholeProjectDecompiler( Guid projectGuid, IAssemblyResolver assemblyResolver, IProjectFileWriter projectWriter, - AssemblyReferenceClassifier assemblyReferenceClassifier, + IAssemblyReferenceClassifier assemblyReferenceClassifier, IDebugInfoProvider debugInfoProvider) { Settings = settings ?? throw new ArgumentNullException(nameof(settings)); ProjectGuid = projectGuid; AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver)); - AssemblyReferenceClassifier = assemblyReferenceClassifier ?? new AssemblyReferenceClassifier(); + AssemblyReferenceClassifier = assemblyReferenceClassifier ?? throw new ArgumentNullException(nameof(assemblyReferenceClassifier)); DebugInfoProvider = debugInfoProvider; - this.projectWriter = projectWriter ?? IProjectFileWriter.FromSettings(settings); + this.projectWriter = projectWriter ?? throw new ArgumentNullException(nameof(projectWriter)); } // per-run members diff --git a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs index d59ff406ec..834334aa8d 100644 --- a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs +++ b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs @@ -67,7 +67,13 @@ public interface IAssemblyResolver #endif } - public class AssemblyReferenceClassifier + public interface IAssemblyReferenceClassifier + { + bool IsGacAssembly(IAssemblyReference reference); + bool IsSharedAssembly(IAssemblyReference reference, [NotNullWhen(true)] out string? runtimePack); + } + + public class AssemblyReferenceClassifier : IAssemblyReferenceClassifier { /// /// For GAC assembly references, the WholeProjectDecompiler will omit the HintPath in the diff --git a/ICSharpCode.ILSpyCmd/BamlAwareWholeProjectDecompiler.cs b/ICSharpCode.ILSpyCmd/BamlAwareWholeProjectDecompiler.cs index 9fd07c685c..c03334e09c 100644 --- a/ICSharpCode.ILSpyCmd/BamlAwareWholeProjectDecompiler.cs +++ b/ICSharpCode.ILSpyCmd/BamlAwareWholeProjectDecompiler.cs @@ -37,7 +37,7 @@ sealed class BamlAwareWholeProjectDecompiler : WholeProjectDecompiler public BamlAwareWholeProjectDecompiler( DecompilerSettings settings, IAssemblyResolver assemblyResolver, - AssemblyReferenceClassifier assemblyReferenceClassifier, + IAssemblyReferenceClassifier assemblyReferenceClassifier, IDebugInfoProvider debugInfoProvider, BamlDecompilerTypeSystem bamlTypeSystem, BamlDecompilerSettings bamlSettings) diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index fc027541e3..67edae098b 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -581,7 +581,7 @@ public ResourceHandlerProjectDecompiler( DecompilerSettings settings, IAssemblyResolver resolver, IProjectFileWriter? projectWriter, - AssemblyReferenceClassifier? assemblyReferenceClassifier, + IAssemblyReferenceClassifier? assemblyReferenceClassifier, ICSharpCode.Decompiler.DebugInfo.IDebugInfoProvider? debugInfoProvider) : base(settings, resolver, projectWriter!, assemblyReferenceClassifier!, debugInfoProvider!) { From e3f45c8c39e95c22daa8ddcf9241447088fda037 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:17:35 -0700 Subject: [PATCH 2/2] Enable nullable in WholeProjectDecompiler.cs --- .../WholeProjectDecompiler.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 5ded93304b..c6ce3eada9 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -41,6 +41,8 @@ using static ICSharpCode.Decompiler.Metadata.MetadataExtensions; +#nullable enable + namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { /// @@ -75,7 +77,7 @@ public LanguageVersion LanguageVersion { public IAssemblyReferenceClassifier AssemblyReferenceClassifier { get; } - public IDebugInfoProvider DebugInfoProvider { get; } + public IDebugInfoProvider? DebugInfoProvider { get; } /// /// The MSBuild ProjectGuid to use for the new project. @@ -89,17 +91,17 @@ public LanguageVersion LanguageVersion { /// This property is set by DecompileProject() and protected so that overridden protected members /// can access it. /// - public string TargetDirectory { get; protected set; } + public string TargetDirectory { get; protected set; } = string.Empty; /// /// Path to the snk file to use for signing. /// null to not sign. /// - public string StrongNameKeyFile { get; set; } + public string? StrongNameKeyFile { get; set; } public int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount; - public IProgress ProgressIndicator { get; set; } + public IProgress? ProgressIndicator { get; set; } #endregion public WholeProjectDecompiler(IAssemblyResolver assemblyResolver) @@ -110,9 +112,9 @@ public WholeProjectDecompiler(IAssemblyResolver assemblyResolver) public WholeProjectDecompiler( DecompilerSettings settings, IAssemblyResolver assemblyResolver, - IProjectFileWriter projectWriter, - IAssemblyReferenceClassifier assemblyReferenceClassifier, - IDebugInfoProvider debugInfoProvider) + IProjectFileWriter? projectWriter, + IAssemblyReferenceClassifier? assemblyReferenceClassifier, + IDebugInfoProvider? debugInfoProvider) : this(settings, Guid.NewGuid(), assemblyResolver, projectWriter ?? IProjectFileWriter.FromSettings(settings), assemblyReferenceClassifier ?? new AssemblyReferenceClassifier(), debugInfoProvider) { } @@ -123,7 +125,7 @@ protected WholeProjectDecompiler( IAssemblyResolver assemblyResolver, IProjectFileWriter projectWriter, IAssemblyReferenceClassifier assemblyReferenceClassifier, - IDebugInfoProvider debugInfoProvider) + IDebugInfoProvider? debugInfoProvider) { Settings = settings ?? throw new ArgumentNullException(nameof(settings)); ProjectGuid = projectGuid; @@ -355,7 +357,7 @@ protected virtual IEnumerable WriteResourceFilesInProject(Metad { foreach (var r in module.Resources.Where(r => r.ResourceType == ResourceType.Embedded)) { - Stream stream = r.TryOpenStream(); + Stream? stream = r.TryOpenStream(); if (stream == null) continue; @@ -373,12 +375,12 @@ protected virtual IEnumerable WriteResourceFilesInProject(Metad foreach (var (name, value) in resourcesFile) { string fileName = SanitizeFileName(name); - string dirName = Path.GetDirectoryName(fileName); + string? dirName = Path.GetDirectoryName(fileName); if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) { CreateDirectory(Path.Combine(TargetDirectory, dirName)); } - Stream entryStream = (Stream)value; + Stream entryStream = (Stream)value!; entryStream.Position = 0; individualResources.AddRange( WriteResourceToFile(fileName, name, entryStream)); @@ -495,14 +497,14 @@ protected virtual IEnumerable WriteMiscellaneousFilesInProject( if (resources == null) yield break; - byte[] appIcon = CreateApplicationIcon(resources); + byte[]? appIcon = CreateApplicationIcon(resources); if (appIcon != null) { File.WriteAllBytes(Path.Combine(TargetDirectory, "app.ico"), appIcon); yield return new ProjectItemInfo("ApplicationIcon", "app.ico"); } - byte[] appManifest = CreateApplicationManifest(resources); + byte[]? appManifest = CreateApplicationManifest(resources); if (appManifest != null && !IsDefaultApplicationManifest(appManifest)) { File.WriteAllBytes(Path.Combine(TargetDirectory, "app.manifest"), appManifest); @@ -520,7 +522,7 @@ protected virtual IEnumerable WriteMiscellaneousFilesInProject( const int RT_ICON = 3; const int RT_GROUP_ICON = 14; - unsafe static byte[] CreateApplicationIcon(Win32ResourceDirectory resources) + unsafe static byte[]? CreateApplicationIcon(Win32ResourceDirectory resources) { var iconGroup = resources.Find(new Win32ResourceName(RT_GROUP_ICON))?.FirstDirectory()?.FirstData()?.Data; if (iconGroup == null) @@ -598,7 +600,7 @@ struct GRPICONDIRENTRY const int RT_MANIFEST = 24; - unsafe static byte[] CreateApplicationManifest(Win32ResourceDirectory resources) + unsafe static byte[]? CreateApplicationManifest(Win32ResourceDirectory resources) { return resources.Find(new Win32ResourceName(RT_MANIFEST))?.FirstDirectory()?.FirstData()?.Data; } @@ -641,12 +643,12 @@ static string CleanUpApplicationManifest(byte[] appManifest) /// /// Cleans up a node name for use as a file name. /// - public static string CleanUpFileName(string text, string extension) + public static string CleanUpFileName(string text, string? extension) { - Debug.Assert(!string.IsNullOrEmpty(extension)); - if (!extension.StartsWith(".")) - extension = "." + extension; - text = text + extension; + if (string.IsNullOrEmpty(extension) || extension.StartsWith(".")) + text = $"{text}{extension}"; + else + text = $"{text}.{extension}"; return CleanUpName(text, separateAtDots: false, treatAsFileName: !string.IsNullOrEmpty(extension), treatAsPath: false); } @@ -668,7 +670,7 @@ public static string SanitizeFileName(string fileName) /// static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName, bool treatAsPath) { - string extension = null; + string? extension = null; int currentSegmentLength = 0; // Extract extension from the end of the name, if valid if (treatAsFileName) @@ -864,9 +866,9 @@ public static bool CanUseSdkStyleProjectFormat(MetadataFile module) public record struct ProjectItemInfo(string ItemType, string FileName) { - public List PartialTypes { get; set; } = null; + public List? PartialTypes { get; set; } = null; - public Dictionary AdditionalProperties { get; set; } = null; + public Dictionary? AdditionalProperties { get; set; } = null; public ProjectItemInfo With(string name, string value) {