diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAnnotatable.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAnnotatable.cs index a82a9d54f7..0dba3d6446 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAnnotatable.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAnnotatable.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -115,6 +116,8 @@ public AnnotationList(int initialCapacity) : base(initialCapacity) { } + [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", + Justification = "AnnotationList is a private nested type — the surrounding Annotatable class deliberately locks on the AnnotationList instance to serialize annotation reads/writes; external code cannot obtain a reference to it.")] public object Clone() { lock (this) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ExtensionDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ExtensionDeclaration.cs index 532d102d4b..019fd4e74b 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ExtensionDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ExtensionDeclaration.cs @@ -24,7 +24,7 @@ public class ExtensionDeclaration : EntityDeclaration { public readonly static TokenRole ExtensionKeywordRole = new TokenRole("extension"); - public override SymbolKind SymbolKind => throw new System.NotImplementedException(); + public override SymbolKind SymbolKind => SymbolKind.TypeDefinition; public AstNodeCollection TypeParameters { get { return GetChildrenByRole(Roles.TypeParameter); } diff --git a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs index c84711e832..eab1cbb290 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs @@ -215,7 +215,7 @@ internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, return false; // operator not supported on pointer types } } - else if ((type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) && type.Kind is not TypeKind.NInt or TypeKind.NUInt) + else if ((type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) && type.Kind is not (TypeKind.NInt or TypeKind.NUInt)) { // If the LHS is C# 9 IntPtr (but not nint or C# 11 IntPtr): // "target.intptr *= 2;" is compiler error, but diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs index b5cd4dd17d..d24cd5a0f8 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs @@ -318,7 +318,7 @@ static unsafe string GetRealPath(string path, Encoding encoding) fixed (byte* input = bytes) { - byte* output = GetRealPath(input, null); + byte* output = NativeMethods.GetRealPath(input, null); if (output == null) { return null; @@ -330,15 +330,18 @@ static unsafe string GetRealPath(string path, Encoding encoding) } byte[] result = new byte[len]; Marshal.Copy((IntPtr)output, result, 0, result.Length); - Free(output); + NativeMethods.Free(output); return encoding.GetString(result); } } - [DllImport("libc", EntryPoint = "realpath")] - static extern unsafe byte* GetRealPath(byte* path, byte* resolvedPath); + static class NativeMethods + { + [DllImport("libc", EntryPoint = "realpath")] + internal static extern unsafe byte* GetRealPath(byte* path, byte* resolvedPath); - [DllImport("libc", EntryPoint = "free")] - static extern unsafe void Free(void* ptr); + [DllImport("libc", EntryPoint = "free")] + internal static extern unsafe void Free(void* ptr); + } } } diff --git a/ICSharpCode.Decompiler/Metadata/MetadataFile.cs b/ICSharpCode.Decompiler/Metadata/MetadataFile.cs index 0e00052d63..398cf3fe06 100644 --- a/ICSharpCode.Decompiler/Metadata/MetadataFile.cs +++ b/ICSharpCode.Decompiler/Metadata/MetadataFile.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; @@ -45,7 +46,7 @@ namespace ICSharpCode.Decompiler.Metadata /// decompiled type systems. /// [DebuggerDisplay("{Kind}: {FileName}")] - public class MetadataFile + public class MetadataFile : IDisposable { public enum MetadataFileKind { @@ -285,6 +286,8 @@ public virtual int GetContainingSectionIndex(int rva) throw new BadImageFormatException("This metadata file does not support sections."); } + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", + Justification = "Throw signals that this MetadataFileKind has no PE sections; derived PE-like kinds override.")] public virtual ImmutableArray SectionHeaders => throw new BadImageFormatException("This metadata file does not support sections."); /// @@ -297,6 +300,16 @@ public IModuleReference WithOptions(TypeSystemOptions options) return new MetadataFileWithOptions(this, options); } + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + private class MetadataFileWithOptions : IModuleReference { readonly MetadataFile peFile; diff --git a/ICSharpCode.Decompiler/Metadata/PEFile.cs b/ICSharpCode.Decompiler/Metadata/PEFile.cs index b3eb7be4cb..3d9cf168b2 100644 --- a/ICSharpCode.Decompiler/Metadata/PEFile.cs +++ b/ICSharpCode.Decompiler/Metadata/PEFile.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.Metadata { [DebuggerDisplay("{FileName}")] - public class PEFile : MetadataFile, IDisposable, IModuleReference + public sealed class PEFile : MetadataFile, IModuleReference { public PEReader Reader { get; } @@ -55,9 +55,11 @@ public PEFile(string fileName, PEReader reader, MetadataReaderOptions metadataOp public override int MetadataOffset => Reader.PEHeaders.MetadataStartOffset; public override bool IsMetadataOnly => false; - public void Dispose() + protected override void Dispose(bool disposing) { - Reader.Dispose(); + if (disposing) + Reader.Dispose(); + base.Dispose(disposing); } IModule TypeSystem.IModuleReference.Resolve(ITypeResolveContext context) diff --git a/ICSharpCode.Decompiler/Metadata/WebCilFile.cs b/ICSharpCode.Decompiler/Metadata/WebCilFile.cs index 4843a5c4c2..47e7d4bd5b 100644 --- a/ICSharpCode.Decompiler/Metadata/WebCilFile.cs +++ b/ICSharpCode.Decompiler/Metadata/WebCilFile.cs @@ -32,7 +32,7 @@ namespace ICSharpCode.Decompiler.Metadata { - public class WebCilFile : MetadataFile, IDisposable, IModuleReference + public sealed class WebCilFile : MetadataFile, IModuleReference { readonly MemoryMappedViewAccessor view; readonly long webcilOffset; @@ -245,9 +245,11 @@ public override unsafe SectionData GetSectionData(int rva) return new MetadataModule(context.Compilation, this, TypeSystemOptions.Default); } - public void Dispose() + protected override void Dispose(bool disposing) { - view.Dispose(); + if (disposing) + view.Dispose(); + base.Dispose(disposing); } public struct WebcilHeader diff --git a/ICSharpCode.Decompiler/Output/PlainTextOutput.cs b/ICSharpCode.Decompiler/Output/PlainTextOutput.cs index 5899ebe9ec..0a8a363f52 100644 --- a/ICSharpCode.Decompiler/Output/PlainTextOutput.cs +++ b/ICSharpCode.Decompiler/Output/PlainTextOutput.cs @@ -28,9 +28,10 @@ namespace ICSharpCode.Decompiler { - public sealed class PlainTextOutput : ITextOutput + public sealed class PlainTextOutput : ITextOutput, IDisposable { readonly TextWriter writer; + readonly bool ownsWriter; int indent; bool needsIndent; @@ -44,11 +45,19 @@ public PlainTextOutput(TextWriter writer) if (writer == null) throw new ArgumentNullException(nameof(writer)); this.writer = writer; + this.ownsWriter = false; } public PlainTextOutput() { this.writer = new StringWriter(); + this.ownsWriter = true; + } + + public void Dispose() + { + if (ownsWriter) + writer.Dispose(); } public TextLocation Location { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/CustomAttribute.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/CustomAttribute.cs index fb706994f2..b071bdbd0a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/CustomAttribute.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/CustomAttribute.cs @@ -40,6 +40,7 @@ sealed class CustomAttribute : IAttribute CustomAttributeValue value; bool valueDecoded; bool hasDecodeErrors; + readonly object syncRoot = new object(); internal CustomAttribute(MetadataModule module, IMethod attrCtor, CustomAttributeHandle handle) { @@ -76,7 +77,7 @@ public bool HasDecodeErrors { void DecodeValue() { - lock (this) + lock (syncRoot) { try { diff --git a/ICSharpCode.Decompiler/Util/EmptyList.cs b/ICSharpCode.Decompiler/Util/EmptyList.cs index 31f69f08a1..8d066332f5 100644 --- a/ICSharpCode.Decompiler/Util/EmptyList.cs +++ b/ICSharpCode.Decompiler/Util/EmptyList.cs @@ -20,6 +20,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace ICSharpCode.Decompiler.Util { @@ -99,6 +100,8 @@ object IEnumerator.Current { get { throw new NotSupportedException(); } } + [SuppressMessage("Usage", "CA1063:Implement IDisposable Correctly", + Justification = "Explicit IDisposable implementation for IEnumerator; intentional no-op for the singleton.")] void IDisposable.Dispose() { } diff --git a/ICSharpCode.Decompiler/Util/LongSet.cs b/ICSharpCode.Decompiler/Util/LongSet.cs index e4e1329290..f4c64aa807 100644 --- a/ICSharpCode.Decompiler/Util/LongSet.cs +++ b/ICSharpCode.Decompiler/Util/LongSet.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace ICSharpCode.Decompiler.Util @@ -28,6 +29,8 @@ namespace ICSharpCode.Decompiler.Util /// /// An immutable set of longs, that is implemented as a list of intervals. /// + [SuppressMessage("Usage", "CA2231:Overload operator equals on overriding value type Equals", + Justification = "Equality on LongSet is intentionally only available via SetEquals — the IEquatable.Equals overload is itself [Obsolete] in favor of SetEquals.")] public struct LongSet : IEquatable { /// @@ -362,6 +365,8 @@ public override bool Equals(object? obj) return obj is LongSet && SetEquals((LongSet)obj); } + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", + Justification = "Throw is an explicit guard against using LongSet in hash-based containers; use SetEquals for comparison.")] public override int GetHashCode() { throw new NotImplementedException(); diff --git a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs index 09fa9f8650..bc27927589 100644 --- a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs +++ b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs @@ -31,6 +31,7 @@ // includes code by Mike Krüger and Lluis Sanchez using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; @@ -40,6 +41,10 @@ namespace ICSharpCode.Decompiler.Util { + [SuppressMessage("Usage", "CA1063:Implement IDisposable Correctly", + Justification = "Ported from the Mono ResXResourceWriter implementation; preserved verbatim for fidelity with the upstream.")] + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", + Justification = "By design: the writer doesn't own the underlying stream/textwriter passed in by the caller. Mono behavior preserved.")] #if INSIDE_SYSTEM_WEB internal #else diff --git a/ICSharpCode.Decompiler/Util/ResourcesFile.cs b/ICSharpCode.Decompiler/Util/ResourcesFile.cs index 762edc53f8..4034b2d5a0 100644 --- a/ICSharpCode.Decompiler/Util/ResourcesFile.cs +++ b/ICSharpCode.Decompiler/Util/ResourcesFile.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.Util /// /// .resources file. /// - public class ResourcesFile : IEnumerable>, IDisposable + public sealed class ResourcesFile : IEnumerable>, IDisposable { sealed class MyBinaryReader : BinaryReader { diff --git a/ICSharpCode.ILSpyX/AssemblyList.cs b/ICSharpCode.ILSpyX/AssemblyList.cs index e21894815e..cb4c425b9e 100644 --- a/ICSharpCode.ILSpyX/AssemblyList.cs +++ b/ICSharpCode.ILSpyX/AssemblyList.cs @@ -340,6 +340,8 @@ LoadedAssembly OpenAssembly(string file, Func load) { VerifyAccess(); file = Path.GetFullPath(file); + LoadedAssembly evicted; + LoadedAssembly newAsm; lock (lockObj) { if (!byFilename.TryGetValue(file, out LoadedAssembly? target)) @@ -348,7 +350,7 @@ LoadedAssembly OpenAssembly(string file, Func load) if (index < 0) return null; - var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream), + newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream), fileLoaders: manager?.LoaderRegistry, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols); newAsm.IsAutoLoaded = target.IsAutoLoaded; @@ -356,8 +358,10 @@ LoadedAssembly OpenAssembly(string file, Func load) Debug.Assert(newAsm.FileName == file); byFilename[file] = newAsm; this.assemblies[index] = newAsm; - return newAsm; + evicted = target; } + evicted.Dispose(); + return newAsm; } public LoadedAssembly? ReloadAssembly(string file) @@ -387,6 +391,7 @@ LoadedAssembly OpenAssembly(string file, Func load) this.assemblies.Remove(target); this.assemblies.Insert(index, newAsm); } + target.Dispose(); return newAsm; } @@ -398,16 +403,21 @@ public void Unload(LoadedAssembly assembly) assemblies.Remove(assembly); byFilename.Remove(assembly.FileName); } + assembly.Dispose(); } public void Clear() { VerifyAccess(); + LoadedAssembly[] removed; lock (lockObj) { + removed = assemblies.ToArray(); assemblies.Clear(); byFilename.Clear(); } + foreach (var asm in removed) + asm.Dispose(); } public void Sort(IComparer comparer) { diff --git a/ICSharpCode.ILSpyX/LoadedAssembly.cs b/ICSharpCode.ILSpyX/LoadedAssembly.cs index 7f7e81eee1..a126221737 100644 --- a/ICSharpCode.ILSpyX/LoadedAssembly.cs +++ b/ICSharpCode.ILSpyX/LoadedAssembly.cs @@ -52,7 +52,7 @@ namespace ICSharpCode.ILSpyX /// * a file that is still being loaded in the background /// [DebuggerDisplay("[LoadedAssembly {shortName}]")] - public sealed class LoadedAssembly + public sealed class LoadedAssembly : IDisposable { /// /// Maps from MetadataFile (successfully loaded .NET module) back to the LoadedAssembly instance @@ -652,5 +652,14 @@ public AssemblyReferenceClassifier GetAssemblyReferenceClassifier(bool applyWinR } UniversalAssemblyResolver? universalResolver; + + public void Dispose() + { + if (loadingTask.Status == TaskStatus.RanToCompletion) + { + loadingTask.Result.MetadataFile?.Dispose(); + } + (debugInfoProvider as IDisposable)?.Dispose(); + } } } diff --git a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs index a5432f47cb..d118c85c83 100644 --- a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs +++ b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs @@ -32,7 +32,7 @@ namespace ICSharpCode.ILSpyX.PdbProvider { - public class PortableDebugInfoProvider : IDebugInfoProvider + public sealed class PortableDebugInfoProvider : IDebugInfoProvider, IDisposable { string? pdbFileName; string moduleFileName; @@ -252,5 +252,10 @@ public MetadataFile ToMetadataFile() var kind = IsEmbedded || Path.GetExtension(SourceFileName).Equals(".pdb", StringComparison.OrdinalIgnoreCase) ? MetadataFileKind.ProgramDebugDatabase : MetadataFileKind.Metadata; return new MetadataFile(kind, SourceFileName, provider, options, 0, IsEmbedded); } + + public void Dispose() + { + provider.Dispose(); + } } } diff --git a/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs b/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs index 61f1224e96..68b831944b 100644 --- a/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs +++ b/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs @@ -8,7 +8,8 @@ [assembly: TargetPlatform("Windows10.0")] [assembly: SupportedOSPlatform("Windows7.0")] -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: NeutralResourcesLanguage("en-US")] +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index de72f0696f..48a1b12961 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -162,15 +162,18 @@ static string Unescape(string p) static T FromString(string s, T defaultValue) { - if (s == null) + if (string.IsNullOrEmpty(s)) return defaultValue; try { TypeConverter c = TypeDescriptor.GetConverter(typeof(T)); return (T)c.ConvertFromInvariantString(s); } - catch (FormatException) + catch (Exception) { + // TypeConverters for WPF types (e.g. Rect) throw InvalidOperationException, not + // FormatException, on malformed input. Treat any conversion failure as "use the + // default" so a single bad saved value can't crash startup. return defaultValue; } }