Three small follow-ups to #3705's lazy-resource fix:
* Replace `_ = RefreshInternalAsync()` with
`RefreshInternalAsync().HandleExceptions()`. The previous fire-and-
forget discard silently swallowed any exception thrown outside the
explicit Catch helper on GetMetadataFileAsync; HandleExceptions is
the codebase's idiomatic helper that surfaces the failure into the
decompile text view.
* Look up the path-root assembly via AssemblyList.FindAssembly (an
O(1) hash lookup keyed by OrdinalIgnoreCase, with Path.GetFullPath
canonicalisation) instead of an O(n) foreach with case-sensitive
string equality. Same shape used by the rest of the codebase.
* Detect the user navigating during the GetMetadataFileAsync await
(which can take ~2s for a sizeable WPF assembly) and bail out of
the path-restore. Without this, clicking another tree node mid-
refresh would be silently overwritten when the deferred SelectNode
jumped back to the captured pre-refresh path.
No behavior change for the .baml refresh fix itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The helper used to catch only FormatException, but the WPF type
converters (e.g. RectConverter for the saved window placement) raise
InvalidOperationException on malformed input — for instance, an empty
string from a partially-written config file. That was enough to
propagate up through SessionSettings.LoadFromXml and crash startup
before the main window could even open, with no recovery path other
than manually editing the on-disk ILSpy.xml.
Treat any conversion failure as "use the default" instead, and treat
empty strings the same as null at the entry. Effect: a single bad
saved value falls back silently and the application starts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ResXResourceWriter is a verbatim port of the Mono implementation
(see file header). Both warnings flag deliberate decisions in the
upstream port that we preserve for fidelity:
* CA1063 — Dispose() is virtual and the protected Dispose(bool) is
not, the inverse of the canonical pattern; keeping the Mono shape.
* CA2213 — the writer's stream / textwriter fields are caller-owned
and intentionally not disposed by the writer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The C# 9 IntPtr / UIntPtr guard in IsBinaryCompatibleWithType read
type.Kind is not TypeKind.NInt or TypeKind.NUInt
which parses as `(is not NInt) or (is NUInt)` — true unless
Kind == NInt. The intent (per the surrounding comment "but not nint
or C# 11 IntPtr") is "Kind is neither NInt nor NUInt", which needs
parentheses around the alternation:
type.Kind is not (TypeKind.NInt or TypeKind.NUInt)
Effect: when Kind == NUInt the branch no longer mistakenly applies
the C# 9 IntPtr-only restrictions (suppressing compound assignment
without nint, disallowing shifts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ExtensionDeclaration.SymbolKind (CA1065) — was throwing
NotImplementedException; return SymbolKind.TypeDefinition to match
TypeDeclaration / DelegateDeclaration, since `extension` declarations
are type-level.
* CustomAttribute.DecodeValue (CA2002) — replace `lock(this)` on the
sealed-but-internal class with a private syncRoot field.
* PlainTextOutput (CA1001) — implement IDisposable; track an
ownsWriter flag so we only dispose the underlying TextWriter when
the parameterless constructor created its own StringWriter.
* DotNetCorePathFinder (CA1060) — move the libc realpath / free
PInvokes into a private nested NativeMethods class.
* ILSpy.ReadyToRun (CA1016) — add [assembly: AssemblyVersion("1.0.0.0")]
in Properties/AssemblyInfo.cs to match the BamlDecompiler plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four cases where the analyzer rule conflicts with intentional design:
* EmptyList<T>.IDisposable.Dispose (CA1063) — explicit IDisposable on
IEnumerator<T>; making it public would conflict with the rest of the
IList<T> / IEnumerator<T> surface.
* MetadataFile.SectionHeaders (CA1065) — throw documents that this
MetadataFileKind has no PE sections; PE-like derived kinds override.
* LongSet.GetHashCode + LongSet itself (CA1065 + CA2231) — explicit
guards against using LongSet in hash containers / via equality
operators; SetEquals is the supported comparison and
IEquatable<LongSet>.Equals is itself [Obsolete].
* AnnotationList.Clone (CA2002) — AnnotationList is a private nested
type; the surrounding Annotatable class deliberately locks on the
AnnotationList instance to serialize annotation reads/writes, and
external code cannot obtain a reference to it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MetadataFile now declares IDisposable using the canonical pattern
(public non-virtual Dispose() + protected virtual Dispose(bool)).
PEFile and WebCilFile become sealed and override Dispose(bool) to
release the PEReader and MemoryMappedViewAccessor they own;
ResourcesFile is also sealed. PortableDebugInfoProvider disposes the
MetadataReaderProvider it owns. LoadedAssembly implements IDisposable
and disposes both the loaded MetadataFile and the debug-info provider.
AssemblyList.Unload / Clear / ReloadAssembly / HotReplaceAssembly now
dispose the LoadedAssembly instances they evict, fixing a resource leak
where every "Reload Assembly" held the previous PEReader (and the
underlying file handle / memory-mapped view) alive until GC eventually
finalized it.
The disposal contract terminates at the AssemblyList tier: downstream
holders of MetadataFile (MetadataModule, DecompilerTypeSystem,
AssemblyListSnapshot, ...) hold borrowed references rather than owned
ones, so making the base IDisposable does not cascade into CA1001 /
CA2213 warnings elsewhere.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
stdout is the data channel: --resource may be writing a binary payload to
it. Update-check notices on stdout would tail-append to the binary stream
and corrupt extracted files. Move them to stderr (npm/cargo/pip
convention) so meta-output stays out of the data path.
--list-resources prints every leaf in the assembly's embedded
resources (including individual entries inside .resources containers).
--resource <name> extracts a single leaf. Names ending in .baml are
decompiled to XAML; everything else is emitted as raw bytes. Without
-o, BAML goes to stdout as text and binary leaves go to the binary
stdout stream. Unknown names exit with EX_DATAERR and print the
available leaves to stderr.
--decompile-baml is a modifier for -p that swaps in the new
BamlAwareWholeProjectDecompiler so .baml entries become .xaml Page
items in the generated project. Default -p behaviour is unchanged.
ResourceExtensions exposes the leaves of an assembly's embedded
resources — including individual entries inside .resources containers
— so callers can list and extract them by a stable path.
BamlAwareWholeProjectDecompiler subclasses WholeProjectDecompiler and
converts .baml entries to .xaml Page items, mirroring the pattern in
ILSpy.BamlDecompiler.BamlResourceFileHandler so PartialTypeInfo flows
through and the C# decompiler suppresses the duplicate Connect()
overrides.
* .NET 11 RC2 minimal changes
* Heuristic for transport feed Roslyn selection
* Microsoft.CodeAnalysis.NetAnalyzers from main NuGet feed
* Use the VS2026 image
* Switch all test projects to net11
* Extract constants
* Include vsix with plain nuget.config files
* Use releaseTag with fallback to downloadUrl in updates.xml
* Add tests
* Prevent arbitrary downloadUrl - must start with BaseUrl as well
* Remove custom domain ilspy.net in end-user visible places
Quote the $(ProjectDir) and $(TargetDir) MSBuild properties in the
Copy-Item commands so that paths with spaces are handled correctly.
Co-authored-by: Myselfish <38400384+Myselfish@users.noreply.github.com>