Commit Graph
9957 Commits
Author SHA1 Message Date
Siegfried Pammer d74e246e90 Fallback from unsupported netcoreapp assets in nugetfuzz
When a package only offers a newer netcoreapp asset than the available stable Microsoft.NETCore.App.Ref package, nugetfuzz cannot build a reference set for that asset. Prefer the net48 asset when one is available so the package can still be included in a corpus sweep.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-11 14:20:24 +02:00
Siegfried Pammer b4ebe5c79e Merge pull request #4230 from icsharpcode/tools/alloc-profile
Add allocation trace viewer
2026-10-11 14:17:18 +02:00
Christoph Wille 6a5ecc5641 Merge pull request #3963 from icsharpcode/substring-optimizations
Substring optimizations: eliminate hot-path allocations (audit top 8)
2026-10-11 12:48:01 +02:00
Siegfried Pammer ce59ef5703 Document allocview as a standalone recipe
The tool is only useful if someone can run it from the docs alone, and the
README entry left out the parts that decide whether the attempt works: the
POSIX form of the environment variables, that the trace appears only when the
process exits, which speedscope tab to open, and that the numbers are samples
rather than a measurement.

The README keeps a pointer instead of a second copy, so the two cannot drift.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-10-10 22:42:57 +02:00
Siegfried Pammer 9b993de1ce Add allocview: an allocation flamegraph for a decompiler run
Nothing showed where a decompiler run's bytes go. The runtime already writes
a trace on request - DOTNET_EnableEventPipe needs no tool and no code in
ilspycmd or the UI - and PerfView reads it, but only on Windows, and neither
knows that ICSharpCode.Decompiler.IL is a different concern from
ICSharpCode.Decompiler.TypeSystem.

So the tool is a converter, not a profiler: it turns GCAllocationTick events
into a byte-weighted speedscope profile, which is the part the stock
conversion does not do - dotnet-trace's speedscope output is weighted by
time - and groups the same bytes by area of the decompiler. An allocation is
placed by the namespace its type name mentions, and when the runtime reports
a nested or compiler-generated type without one, by the innermost decompiler
frame on its stack, which names the code that wanted the object rather than
the object.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-10-10 22:42:57 +02:00
Siegfried Pammer 8e50cadcc7 Make debug step recording opt in
Non-UI decompilation runs do not consume debug steps, but debug builds still allocated steppers and step nodes unless each context opted out manually. Keep step contexts empty unless the caller enables RecordSteps, while preserving the shared stepper path for the UI debug-step view.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-10 21:18:52 +02:00
Siegfried Pammer d4d430ebe4 Avoid debug assert message allocations
Debug assertions should not allocate formatted messages on successful checks. Route interpolated assertion messages through a DEBUG-only handler so the message is built only when the assertion fails.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-10 18:34:40 +02:00
Christoph Wille 4509babe22 Gate the explicit-impl operator check before allocating the short name
The MetadataMethod constructor cut the post-dot short name for every
static non-generic method whose name contains a dot, only to test it
for an op_ prefix that almost never matches. The prefix is now checked
on a span slice first, so the substring (still required by
OperatorDeclaration.GetOperatorType) is allocated only for actual
explicit-interface operator implementations.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille 0c5425bdc7 Write IL-with-C# comment slices as spans
The "IL with C#" view cut up to four substrings (prefix, trimmed
prefix, highlighted range, suffix) out of every source line emitted
alongside an IL instruction. ISmartTextOutput now accepts a
ReadOnlySpan<char> - as a default interface method falling back to
Write(text.ToString()) so existing implementers keep working - and
AvaloniaEditTextOutput appends the span straight into its
StringBuilder. The overload lives on ISmartTextOutput rather than
ITextOutput because the latter is netstandard2.0 (no default interface
methods there), where a new member would break every external
implementer of the decompiler library; the highlighted-comment path is
typed against ISmartTextOutput already.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille ba78e3cf27 Thread spans through the CleanUpVariableName chain
CleanUpVariableName sits on the per-variable naming path of every
decompiled method and allocated up to three intermediates (backtick
cut, m_/_ prefix strip, lowercase-first concat) before producing its
result, and its callers added further throwaway substrings when
stripping get_/set_/Get/Set and interface-I prefixes. The cuts are now
slices over the original name: ContainsNonPrintableIdentifierChar and
IsValidName gained span overloads, and only the final lowered name is
materialized, in a single allocation via char[] (netstandard2.0 has no
string(span) constructor). IsKeyword keeps its string parameter - it
is called on that final string anyway, and the keyword HashSet has no
span lookup on netstandard2.0.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille 519ed6e98c Skip the StringBuilder in EscapeIdentifier when nothing needs escaping
EscapeIdentifier runs for every identifier token emitted, yet it built
a StringBuilder plus a fresh result string even for the overwhelmingly
common case of an identifier with no escapable characters. A pre-scan
now returns the original instance untouched in that case, and the
surrogate-pair copy appends the two chars directly instead of cutting
a two-char substring. New unit tests pin the escaping behavior and the
identity fast path.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille 298bf6160c Render shortcut-form IL opcodes from fixed lookup tables
WriteOpCode allocated two strings per rendered ldarg.N/ldloc.N/stloc.N
instruction (the digit cut off the mnemonic plus the concatenated
local-reference key), even though the shortcut forms only ever produce
the indices 0-3. The digit text and the param_/loc_ reference keys now
come from static tables indexed by opcode arithmetic.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille 9125b196d4 Fuse the reflection-name and arity parse to cut each string once
SplitTypeParameterCountFromReflectionName allocated the digits after
the backtick only to feed int.TryParse and discard them, and the
TopLevelTypeName constructor cut the name part twice for generic
types (once at the dot, again at the backtick), dropping the first
cut. Both run for every generic reflection name parsed, e.g. for
typeof-valued attribute arguments and string-switch metadata. The
arity is now parsed in place with a digit loop (netstandard2.0 has
no span int.TryParse) and each final string is cut exactly once.

The digit loop only accepts plain ASCII digits, so suffixes like
`+1 that int.TryParse tolerated are now rejected; such names are not
legal reflection names. New unit tests pin the parse edge cases.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille 1699b119c1 Prepare search terms once per run instead of per candidate name
AbstractSearchStrategy.IsMatch runs for every metadata row of every
loaded module on each keystroke, yet it re-stripped the operator prefix
per name and, for fuzzy terms, additionally lowercased both the name
and the term - the largest allocation source in interactive search.
The terms are invariant for the lifetime of a strategy (each keystroke
builds a new request and strategy), so the stripping and lowercasing
now happen once in the constructor, and the noncontiguous matcher
compares spans with per-char ToLowerInvariant. This switches fuzzy
matching from culture-sensitive to invariant lowercasing, which is the
appropriate semantic for matching metadata names.

The new IsMatchTests pin the +, -, =, ~ operator semantics (including
the pre-existing quirk that =Name compares against the backtick-
suffixed name of generic types) as observed before the change.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Christoph Wille 2c80eed848 Parse the digit suffix inline in AssignVariableNames.SplitName
The loop preceding the parse has already proven that the tail consists
solely of ASCII digits, so the Substring+int.TryParse pair only
re-validated them at the cost of a throwaway string allocation.
SplitName runs per variable and per reserved-name registration for
every decompiled method, making this one of the hottest Substring call
sites in the decompiler. Accumulating the digits inline keeps the
TryParse overflow semantics (fall back to number=1 and the unchanged
name) without allocating.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Siegfried Pammer 3491b15e6b Merge pull request #4229 from icsharpcode/avoid-unnecessary-settings-clone
Avoid cloning settings for every method
2026-10-10 15:49:12 +02:00
Siegfried Pammer 098de864b9 Avoid cloning settings for every method
Most methods use the decompiler settings read-only. Clone only for the WinForms InitializeComponent special case that temporarily overrides settings before running the method pipeline.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-10 15:15:52 +02:00
Siegfried Pammer c3da4a8358 Merge pull request #4225 from icsharpcode/fix/continue-leave-in-catch
Fix #2829: Emit continue for leave through try-finally
2026-10-10 14:59:21 +02:00
Siegfried Pammer 803d44e07f Make continue-in-catch test match issue shape
The regression only needs the nested catch inside an enclosing handler from #2829. Removing the explicit try/finally keeps the fixture focused on the failing control-flow shape instead of compiler-generated cleanup details.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-10 14:31:14 +02:00
Siegfried Pammer d77238a4b6 Merge pull request #4227 from icsharpcode/christophwille/magick-net
Replace ImageSharp with Magick.NET in the fixture generator
2026-10-10 11:12:06 +02:00
Christoph Wille 3252a4087b Replace ImageSharp with Magick.NET in the fixture generator
ImageSharp 4.x / ImageSharp.Drawing 3.x add a build-time license check
that fails Release builds without a Six Labors license key, and the
older 3.x / 2.x line is the last one without it. Magick.NET is Apache
2.0, bundles the native ImageMagick library for win/linux/osx, and
covers every encoder the fixtures need (PNG/BMP/JPG/GIF), so the
generator moves to it instead of carrying a license key.

The checked-in binary fixtures are regenerated with the new encoders;
the rendered shape (square, circle, diagonal stripe, border) is the
same, only the encoded bytes differ.

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-09 09:54:27 +02:00
Christoph Wille ad2cc62293 Merge pull request #4221 from icsharpcode/christophwille/mt
-mt runs a build's projects concurrently inside a single MSBuild process
2026-10-09 09:32:57 +02:00
Christoph Wille 14577b99c9 Merge pull request #4149 from icsharpcode/fix/4143-taskbar-icon
Re-send the window icon after a theme flip or resume
2026-10-09 09:31:34 +02:00
Siegfried Pammer 04a0b6f1d0 Fix #2829: Emit continue for leave through try-finally
A catch inside an enclosing EH region can leave to a try body that immediately branches to the loop continuation. Treat that leave as a continue so decompilation does not expose the synthetic end label for the catch path.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-08 23:07:27 +02:00
Siegfried Pammer df79031e93 Merge pull request #4223 from icsharpcode/fix/2744-resx-resource-writer
Fix ResXResourceWriter empty output and type names
2026-10-08 20:57:31 +02:00
Siegfried Pammer aa18f6c075 Merge pull request #4222 from icsharpcode/fix/2199-vb-closure
Fix/2199 vb closure
2026-10-08 20:55:56 +02:00
Siegfried Pammer 73ec67e2af Allow configuring ResX assembly names
Resource export sometimes needs framework-specific ResX type names. Exposing the WinForms and mscorlib assembly suffixes keeps the writer default-compatible while letting callers choose older resource type names when needed.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-08 19:28:07 +02:00
Siegfried Pammer 6b43671564 Write empty ResX files on dispose
ResX export should still produce a valid file when no resources were added and the writer is finalized through Close or Dispose. Calling Generate from Close preserves creation timing while matching the empty-file behavior.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-08 19:26:44 +02:00
Siegfried Pammer 565e1a3442 Handle legacy VB delegate wrappers
VB can lower event-handler closures through generated wrapper methods, closure copy constructors, and immediately invoked VB anonymous delegate helpers. Recognizing and unwrapping those shapes leaves the expected lambda form and lets captured locals be scalar-replaced instead of exposing generated closure artifacts.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-08 17:52:13 +02:00
Siegfried Pammer b379738b7f Merge pull request #4218 from icsharpcode/christophwille/avalonia-followups-4042
Fix the Avalonia follow-ups from #4042
2026-10-08 17:25:43 +02:00
Siegfried Pammer accbd3eda0 Merge pull request #4220 from icsharpcode/fix/3180-nullprop-extension-method
Fix #3180: avoid invalid null-conditional extension calls
2026-10-07 20:15:07 +02:00
Christoph Wille 6d6b2a0b77 -mt runs a build's projects concurrently inside a single MSBuild process https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/msbuild.md#multithreaded-mode-progress 2026-10-07 14:27:01 +02:00
Siegfried Pammer e5d6104c9f Tighten null-propagation extension receiver check
Avoid reusing a temporary receiver's stored value for extension-method lookup when its inferred source type no longer matches the local type.

Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
2026-10-07 07:22:12 +02:00
Siegfried Pammer 7a56e5d9e2 Merge pull request #4217 from icsharpcode/fix/dynamic-constructor-token-references
Improve dynamic reference tooltips
2026-10-06 22:39:56 +02:00
Christoph Wille fa6b5f3b26 Let the editor gutter margins react to the left button only
AvaloniaEdit's line-number margin moves the caret and selects the line,
and a fold marker toggles its fold, on any pointer button; the WPF
margins only reacted to the left button. A middle click opens a
reference in a new tab over the text, so one that lands on the gutter
should do nothing instead of folding code or moving the caret. The text
area itself still sees every button.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 08:24:20 +02:00
Christoph Wille 7efd079adc Guard same-value SetActiveDockable in the dock factory
Dock's ActiveDockable setter re-runs InitActiveDockable, and with it
SetFocusedDockable, even when the value does not change, so activating
the already-active document moved the active-pane highlight to the
documents dock. Two callers guarded against that individually; the
factory override does it for every caller, and ActivateAndFocus keeps
its explicit SetFocusedDockable for the callers that do want the focus.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 08:24:20 +02:00
Christoph Wille 95576cb9e1 Focus a programmatically selected tree row only if the tree owns focus
The tree selection binder focused the selected row on every model-driven
change, so Back/Forward, search-result jumps, go-to-definition, tab
activation and omnibar picks all pulled the keyboard focus into the
assembly tree. WPF's SelectNodes only scrolled the row into view; the
row took the focus when the pane itself was activated. The binder now
checks where the keyboard focus sits when the selection changes and
leaves it alone unless the tree already holds it. The Analyzer pane
keeps focusing its row, as its WPF counterpart did on every selection
change, so an Analyze request still lands the user in the pane.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 08:24:20 +02:00
Christoph Wille 0231a96f12 Let Ctrl+R in the Analyzer pane analyze the pane's own selection
The only Ctrl+R binding sat on the window and always analyzed the
assembly tree's selection, so pressing it on a result row in the
Analyzer pane promoted the wrong entity, or nothing. WPF ILSpy bound the
key on the pane as well. Avalonia walks KeyBindings from the focused
element up to the window, so a pane-level binding takes precedence while
the focus is in the pane and falls through when it has nothing to
analyze. The command takes the selection as its parameter so both
bindings share one implementation.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 07:55:18 +02:00
Christoph Wille bd5544bbbb Share context-menu targeting between the assembly and Analyzer trees
The assembly tree had Thunderbird-style context targeting (right-click a
row outside the selection without moving the selection, highlight the
target, re-focus the row after a keyboard-invoked menu); the Analyzer
pane only rebuilt the menu from its selection, so a right-click on
another row opened the menu for the previous selection. Hosting the
behaviour in one controller keyed on SharpTreeView gives both panes the
same gestures and keeps the assembly pane down to its middle-click
open-in-new-tab handler.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 07:52:15 +02:00
Christoph Wille 4dc9784c0d Judge Analyze's IsEnabled on the same node shape as IsVisible
IsVisible required every selected node to be a member node while
IsEnabled filtered to the member subset first, so a mixed selection
reported itself enabled-but-hidden. Hidden won, so nothing was visible
to the user, but the two predicates disagreed about the same selection.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 07:45:18 +02:00
Siegfried Pammer ef0b4668cc Return symbols for method-group resolve results
Dynamic and token-writer reference paths can receive method-group resolve results directly. Returning the first method keeps ResolveResult.GetSymbol aligned with the existing method-group handling used by AST annotations.

Assisted-by: OpenAI:openai/gpt-5.5:OpenCode
2026-10-06 07:44:01 +02:00
Christoph Wille 85b85e0ea2 Look up the Analyzer "Remove" header through the resource key
Every other context-menu entry names its header by resource key so the
menu builder can localise it; this one shipped the raw string, which
WPF ILSpy did as well. The "Remove" string already exists in the resx.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 07:43:19 +02:00
Christoph Wille 15c8cea75d Let Tab move the focus out of the decompiled text view
AvaloniaEdit marks Tab handled while AcceptsTab is on, so the keyboard
focus could never leave the read-only editor. WPF ILSpy removed the
TabForward/TabBackward editing commands for the same reason.

#4042

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-06 07:41:55 +02:00
Siegfried Pammer 8f66608d82 Improve dynamic reference tooltips
Dynamic member tooltips should describe the visible static target, while punctuation should stay reserved for useful call targets such as constructors. This keeps dynamic object creation tooltips on the constructor parentheses but avoids noisy synthetic member references on dynamic invocation parentheses.

Assisted-by: OpenAI:openai/gpt-5.5:OpenCode
2026-10-06 07:35:36 +02:00
Siegfried Pammer c993befc4b Merge pull request #4206 from icsharpcode/christophwille/fix-startup-binding-errors
Fix startup binding errors and background load sweep failure
2026-10-05 17:01:12 +02:00
Christoph Wille fabd0de38e Keep the background load sweep going past unloadable assemblies
One assembly that failed to load faulted the whole sweep, which skipped
the command requery at its end. 'Remove assemblies with load errors' was
therefore not re-evaluated in the one case it exists for.
ShowAssemblyList(AssemblyList) is internal so the test can show a list
whose load it controls.

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-05 15:37:53 +02:00
Christoph Wille 772085f264 Let tree rows inherit ShowLines instead of binding to an ancestor
The ancestor binding logged 'Ancestor not found' whenever a row's content
was evaluated outside a SharpTreeView. An inherited property needs no
lookup, and leaves the lines on where there is no tree view at all.

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-05 15:37:46 +02:00
Christoph Wille a27a6ed9a8 Host the update banner through a content template
With a DataContext binding of its own the panel first inherited the main
window's view model, and each of its compiled bindings failed the cast to
UpdatePanelViewModel and logged an error at startup. The new test boots the
window with a capturing log sink and fails on any binding error.

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-05 15:37:40 +02:00
Christoph Wille 4b64849e84 Give docks capability overrides so Dock's chrome bindings resolve
Dock's theme binds DockCapabilityOverrides.CanDrag/CanDrop on the tool and
document docks themselves, not only on the panes inside them, and logged a
binding error per property at startup. Filling them in from InitLayout
covers layouts restored from disk, which never pass through CreateLayout.

Assisted-by: Claude:claude-fable-5-1:Claude Code
2026-10-05 15:37:32 +02:00
Christoph Wille 0445108b7e Merge pull request #4205 from icsharpcode/christophwille/nuup10
Clean up package restore (Diasymreader)
2026-10-05 09:19:30 +02:00