ILSpy — Search Feature Analysis

Deep-dive architecture and behavioural analysis of the ILSpy Search subsystem, produced via Abyss code-intelligence queries.

Target: ICSharpCode.ILSpy Namespace: ICSharpCode.ILSpy.Search Language: C# / WPF Entities discovered: 30 Abyss queries executed: 10 Analysis date: 2025

1. Executive Summary

30
Entities discovered
6
Primary classes
7
Strategy classes
12
Search modes
3
User workflows
1,000
Max results (const)

The ILSpy Search subsystem provides interactive, incremental searching across all loaded assemblies. It follows a classic MVVM / strategy / factory layering pattern within a WPF application:

  • SearchPane (UserControl + code-behind) acts as the UI orchestrator and owns the RunningSearch lifecycle.
  • RunningSearch encapsulates one asynchronous search execution: parsing user input, selecting a strategy, running background tasks, and feeding a ConcurrentQueue.
  • SearchPaneModel exposes bindable state (SearchTerm, SearchModes) and integrates with the docking system and session settings.
  • AbstractSearchStrategy hierarchy (7 concrete classes) cleanly separates how each of 12 modes searches the metadata.
  • SearchResultFactory produces typed results (Member, Resource, Assembly, Namespace) from domain objects.
  • Inter-component coordination uses a static MessageBus<T> (pub/sub), keeping components loosely coupled.
Key Architectural Insight: The rendering loop (CompositionTarget.Rendering) drives result insertion into Results: ObservableCollection<SearchResult>, capped at MAX_REFRESH_TIME_MS = 10 ms per frame. This decouples the background search Task from UI updates without an explicit timer or dispatcher.

Quality Gate Summary

GateCriterionResult
QG-1≥ 8 Abyss queries executed✔ PASS (10)
QG-2≥ 6 primary entities with file/symbol✔ PASS (6 primary + 7 strategy + 4 result)
QG-3≥ 3 complete workflows with CRUD✔ PASS (3)
QG-4≥ 4 Mermaid diagrams (class + sequence)✔ PASS (4)
QG-5≥ 5 quality + ≥ 3 security recommendations✔ PASS (5 + 5)

2. Entity Inventory

2.1 Primary Entities (Search/ folder)

#EntityKindFileResponsibility
1 SearchPane PRIMARY UserControl Search/SearchPane.xaml.cs Top-level UI control. Owns search box, results listbox, progress bar, mode selector. Orchestrates RunningSearch lifecycle. Subscribes to MessageBus and CompositionTarget.Rendering.
2 RunningSearch PRIMARY sealed class (nested) Search/SearchPane.xaml.cs Encapsulates one search execution. Parses input, selects strategy, runs Task.Factory.StartNew(LongRunning), feeds ConcurrentQueue<SearchResult>. Cancellable via CancellationTokenSource.
3 SearchPaneModel PRIMARY class (ViewModel) Search/SearchPaneModel.cs Bindable ViewModel: SearchTerm (MVVM SetProperty), SearchModes[12], session settings integration. Subscribes to ShowSearchPageEventArgs and ApplySessionSettingsEventArgs.
4 SearchModeModel PRIMARY class Search/SearchPaneModel.cs Data object representing one search mode: Mode (enum value), Image, Name. Bound to SearchModeComboBox ItemsSource.
5 SearchResultFactory PRIMARY class Search/SearchResultFactory.cs Implements ISearchResultFactory. Creates typed results via overloaded Create(): MemberSearchResult, ResourceSearchResult, AssemblySearchResult, NamespaceSearchResult. Calculates fitness and resolves icons.
6 ISearchResultFactory INTERFACE interface Search/ Contract for result factory. Enables testability — strategies receive the interface, not the concrete class.

2.2 Search Strategies

#StrategyHandles ModesNotes
7AbstractSearchStrategy BASEAbstract base with Search(PEFile module, CancellationToken ct). Injects SearchRequest + ISearchResultFactory.
8MemberSearchStrategy STRATEGYTypeAndMember, Type, Member, Method, Field, Property, EventMost general; searches metadata entities. Supports OmitGenerics, FullNameSearch.
9LiteralSearchStrategy STRATEGYLiteralSearches constant/literal values in metadata.
10MetadataTokenSearchStrategy STRATEGYTokenParses @0x... prefix, matches raw metadata tokens. High precision.
11ResourceSearchStrategy STRATEGYResourceIterates assembly resources, matches by name.
12AssemblySearchStrategy STRATEGYAssemblySearches assembly names/versions/attributes. Supports AssemblySearchKind sub-filter.
13NamespaceSearchStrategy STRATEGYNamespaceSearches namespace names. Respects InNamespace constraint.

2.3 Result Types

#TypeCreated byKey properties
14SearchResult BASEFitness: float, Name: string, Assembly: string, Reference: object, ComparerByFitness, ComparerByName
15MemberSearchResult RESULTCreate(IEntity)Adds member-specific icon, language name, decompiled display.
16ResourceSearchResult RESULTCreate(MetadataFile, Resource, ...)Carries resource node reference for tree view navigation.
17AssemblySearchResult RESULTCreate(MetadataFile)Represents a matching assembly. Assembly is the assembly itself.
18NamespaceSearchResult RESULTCreate(MetadataFile, INamespace)Wraps namespace node for tree navigation.

2.4 Data Models

#TypeKey Fields
19 SearchRequest MODEL Keywords: string[], RegEx: Regex, Mode: SearchMode, InNamespace: string, InAssembly: string, AssemblySearchKind, MemberSearchKind, FullNameSearch: bool, OmitGenerics: bool, SearchResultFactory: ISearchResultFactory, TreeNodeFactory: ITreeNodeFactory, DecompilerSettings
20 SearchMode ENUM TypeAndMember, Type, Member, Method, Field, Property, Event, Literal, Token, Resource, Assembly, Namespace (12 values)

2.5 Secondary / Supporting Entities

#EntityLocationRole in Search
21AssemblyTreeModel SECONDARYAssemblyTree/Provides AssemblyList, CurrentLanguage; receives NavigateToReferenceEventArgs and calls JumpToReferenceAsync().
22LanguageSettings SECONDARYOptions/ or Settings/Its property-changed events trigger SearchPane.UpdateFilter() → restarts search with new language filter.
23SettingsService SECONDARYServices/Provides DisplaySettings.SortResults, SessionSettings, DecompilerSettings.
24NavigateToReferenceEventArgs SECONDARYMessages/Message bus payload for navigation; carries Reference and inNewTabPage.
25NavigationState SECONDARYNavigation/Navigation history state: TreeNodes, ViewState, TabPage.
26MessageBus<T> SECONDARYMessages/ or Util/Static pub/sub bus. Send() / Subscribers +=. Decouples SearchPane from AssemblyTreeModel.
27EntityReference SECONDARYCommands/Typed reference to an entity in an assembly. Resolved by AssemblyTreeModel.
28Pane (static) SECONDARYViewModels/PaneModel.csProvides IsActiveProperty, IsVisibleProperty (DependencyProperties) for docking integration.
29Language SECONDARYLanguages/Abstraction for IL, C#, VB etc. Affects display name generation in SearchResultFactory.
30DecompilerTextView SECONDARYTextView/Final consumer of navigation events; decompiles and renders the selected entity.

3. Search UI Pane — SearchPane

SearchPane (UserControl)
File: Search/SearchPane.xaml.cs

SearchPane is the outermost orchestrator of the search feature. It is a WPF UserControl with code-behind that manages the full search lifecycle from user input through asynchronous execution to result display and navigation.

3.1 Constants & State

IdentifierTypeValue / Role
MAX_RESULTSconst int1,000. Hard cap on results added to Results. Prevents UI freeze with large result sets.
MAX_REFRESH_TIME_MSconst int10 ms. Per-frame budget for UpdateResults. Drains ResultQueue until budget is spent.
ResultsObservableCollection<SearchResult>Bound to results ListBox. Two-way reactive: adding to it causes WPF DataBinding to update the UI.
currentSearchRunningSearchReference to the active search task. Replaced on each new search. Previous is cancelled before replacement.
resultsComparerIComparer<SearchResult>Set from DisplaySettings.SortResults. Determines insertion order into Results.
runSearchOnNextShowboolDeferred search flag — if the pane is hidden when a search is requested, it runs on next show.

3.2 Event Subscriptions (Constructor)

Event / SourceHandlerEffect
MessageBus<CurrentAssemblyListChangedEventArgs>CurrentAssemblyList_Changed()Restarts search when assembly list changes (load/unload). U
MessageBus<SettingsChangedEventArgs>Settings_PropertyChanged()Calls UpdateFilter()StartSearch() when language or display settings change.
CompositionTarget.RenderingUpdateResults()Runs every WPF render frame. Drains ResultQueue within MAX_REFRESH_TIME_MS. C
searchBox.TextChangedSearchBox_TextChanged()Triggers a new search on every keypress.
searchModeComboBox.SelectionChangedSearchModeComboBox_SelectionChanged()Restarts search with the newly selected mode.
listBox.MouseDoubleClickListBox_MouseDoubleClick()Calls JumpToSelectedItem(), navigating in the same tab.

3.3 Key Methods

StartSearch(string searchTerm)

The central orchestration method. Called whenever the search term, mode, or assembly list changes.

// Pseudocode reconstruction from Abyss evidence
async void StartSearch(string searchTerm)
{
    currentSearch?.Cancel();                             // CRUD: D (cancel old)
    Results.Clear();                                     // CRUD: D (clear UI)

    resultsComparer = settingsService
        .DisplaySettings.SortResults
        ? SearchResult.ComparerByFitness
        : SearchResult.ComparerByName;                   // CRUD: R (read settings)

    var assemblies = assemblyTreeModel.AssemblyList
                         .GetAllAssemblies();            // CRUD: R
    var running = new RunningSearch(                     // CRUD: C (new search)
        assemblies, searchTerm, searchMode,
        language, languageVersion, apiVisibility,
        treeNodeFactory, settingsService);

    currentSearch = running;                             // CRUD: U (replace ref)
    await running.Run();
}

UpdateResults(object sender, EventArgs e)

Called by CompositionTarget.Rendering every frame. It drains currentSearch.ResultQueue until MAX_REFRESH_TIME_MS is exceeded or MAX_RESULTS is reached.

void UpdateResults(object sender, EventArgs e)
{
    var search = currentSearch;
    if (search == null) return;
    var sw = Stopwatch.StartNew();
    while (Results.Count < MAX_RESULTS && sw.ElapsedMilliseconds < MAX_REFRESH_TIME_MS)
    {
        if (!search.ResultQueue.TryTake(out var result)) break;
        // Sorted insertion or append
        Results.InsertSorted(result, resultsComparer);   // CRUD: C
    }
}

JumpToSelectedItem(bool inNewTabPage = false)

Navigates to the selected search result using the MessageBus.

void JumpToSelectedItem(bool inNewTabPage = false)
{
    var result = listBox.SelectedItem as SearchResult;   // CRUD: R
    if (result == null) return;
    MessageBus.Send(this,
        new NavigateToReferenceEventArgs(                // CRUD: C
            result.Reference, inNewTabPage));
}

4. Search Pane Model — SearchPaneModel & SearchModeModel

SearchPaneModel (ViewModel)
File: Search/SearchPaneModel.cs | PaneContentId: "searchPane"

SearchPaneModel is the ViewModel layer for the search pane. It integrates with the docking framework (AvalonDock or equivalent) and provides bindable properties consumed by SearchPane.xaml.

4.1 Bindable Properties

PropertyTypeBinding Pattern
SearchTerm string Two-way via SetProperty(ref _searchTerm, value) (ObservableObject / MVVM Toolkit). Setting it via ShowSearchPageEventArgs auto-populates the search box.
SearchModes SearchModeModel[] One-time bound to searchModeComboBox.ItemsSource. Contains all 12 modes.
SessionSettings SessionSettings Stores selected mode index between sessions. Persisted via ApplySessionSettingsEventArgs.

4.2 The 12 Search Modes

#ModeInput PrefixStrategy
1TypeAndMember(none)MemberSearchStrategy
2Typet: / T / TMMemberSearchStrategy
3Memberm: / MMemberSearchStrategy
4MethodMDMemberSearchStrategy
5FieldFMemberSearchStrategy
6PropertyPMemberSearchStrategy
7EventEMemberSearchStrategy
8LiteralCLiteralSearchStrategy
9Token@0x...MetadataTokenSearchStrategy
10ResourceRResourceSearchStrategy
11AssemblyA / AF / ANAssemblySearchStrategy
12NamespaceNNamespaceSearchStrategy

4.3 Message Bus Subscriptions

Message TypeHandler Effect
ShowSearchPageEventArgs Sets SearchTerm from the event, calls Show() to make the pane visible. Used by "Find in Assemblies" menu commands.
ApplySessionSettingsEventArgs Reads current SessionSettings.SelectedSearchMode and restores the ComboBox selection for the current session.

5. Result Factory, Parsing & Strategy Selection

5.1 SearchResultFactory

SearchResultFactory (implements ISearchResultFactory)
File: Search/SearchResultFactory.cs

The factory is instantiated per-search inside RunningSearch.Parse(), receiving the current Language and settings. This means every search execution gets a factory with a fresh language context — correct for multilingual display.

MethodInputOutputKey operations
Create(IEntity) Any ICSharpCode.Decompiler type (type, method, field, …) MemberSearchResult Calls CalculateFitness(entity, keywords), GetLanguageSpecificName(entity), GetIcon(entity). Sets Assembly from entity's module.
Create(MetadataFile, Resource, ITreeNode, ITreeNode) Module + resource + tree nodes ResourceSearchResult Stores tree node reference for direct navigation.
Create(MetadataFile) Module AssemblySearchResult Module becomes both the result subject and navigation target.
Create(MetadataFile, INamespace) Module + namespace NamespaceSearchResult Namespace node used for tree navigation.

5.2 RunningSearch.Parse(string input) — Prefix Syntax

The Parse() method is the user-facing query language for the search box. It tokenizes the input using CommandLineTools.CommandLineToArgumentArray() and then scans for known prefix tokens to build a structured SearchRequest.

Prefix / PatternEffect on SearchRequestExample
@0x...Sets Mode = Token, parses hex token@0x02000001
INNAMESPACE:xSets InNamespace = "x"INNAMESPACE:System.IO List
INASSEMBLY:xSets InAssembly = "x"INASSEMBLY:mscorlib String
T / TMMode = Type / TypeAndMember (MemberSearchKind filter)T: List
M / MDMode = Member / MethodMD: Dispose
FMode = FieldF: _value
PMode = PropertyP: Count
EMode = EventE: Clicked
CMode = Literal (constant)C: 42
RMode = ResourceR: icon.png
NMode = NamespaceN: System.Linq
A / AF / ANMode = Assembly; AF = file name filter, AN = name filterA: mscorlib
/pattern/Sets RegEx = new Regex(pattern) (Mode stays as selected)/^Get[A-Z]/
Other tokensAppended to Keywords[]IEnumerable Contains

5.3 RunningSearch.GetSearchStrategy(SearchRequest)

A factory method (not a pattern class) inside RunningSearch that maps SearchMode to the appropriate AbstractSearchStrategy subclass:

AbstractSearchStrategy GetSearchStrategy(SearchRequest request) => request.Mode switch
{
    SearchMode.TypeAndMember => new MemberSearchStrategy(request, MemberSearchKind.TypeAndMember),
    SearchMode.Type          => new MemberSearchStrategy(request, MemberSearchKind.Type),
    SearchMode.Member        => new MemberSearchStrategy(request, MemberSearchKind.Member),
    SearchMode.Method        => new MemberSearchStrategy(request, MemberSearchKind.Method),
    SearchMode.Field         => new MemberSearchStrategy(request, MemberSearchKind.Field),
    SearchMode.Property      => new MemberSearchStrategy(request, MemberSearchKind.Property),
    SearchMode.Event         => new MemberSearchStrategy(request, MemberSearchKind.Event),
    SearchMode.Literal       => new LiteralSearchStrategy(request),
    SearchMode.Token         => new MetadataTokenSearchStrategy(request),
    SearchMode.Resource      => new ResourceSearchStrategy(request),
    SearchMode.Assembly      => new AssemblySearchStrategy(request),
    SearchMode.Namespace     => new NamespaceSearchStrategy(request),
    _                        => throw new ArgumentOutOfRangeException()
};

6. Architecture Diagrams

6.1 Class Diagram — Search Subsystem

classDiagram direction TB class SearchPane { +ObservableCollection~SearchResult~ Results -RunningSearch currentSearch -AssemblyTreeModel assemblyTreeModel -ITreeNodeFactory treeNodeFactory -SettingsService settingsService -int MAX_RESULTS -int MAX_REFRESH_TIME_MS +StartSearch(string) void +JumpToSelectedItem(bool) void +FocusSearchBox() void +UpdateFilter() void -UpdateResults(object, EventArgs) void -SearchBox_TextChanged(object, TextChangedEventArgs) void -SearchModeComboBox_SelectionChanged(object, SelectionChangedEventArgs) void -ListBox_MouseDoubleClick(object, MouseButtonEventArgs) void } class RunningSearch { <<sealed>> +IProducerConsumerCollection~SearchResult~ ResultQueue -CancellationTokenSource cts -IList~LoadedAssembly~ assemblies -SearchRequest searchRequest +Cancel() void +Run() Task -Parse(string) SearchRequest -GetSearchStrategy(SearchRequest) AbstractSearchStrategy } class SearchPaneModel { +string SearchTerm +SearchModeModel[] SearchModes +SessionSettings SessionSettings +string PaneContentId } class SearchModeModel { +SearchMode Mode +object Image +string Name } class ISearchResultFactory { <<interface>> +Create(IEntity) MemberSearchResult +Create(MetadataFile, Resource, ITreeNode, ITreeNode) ResourceSearchResult +Create(MetadataFile) AssemblySearchResult +Create(MetadataFile, INamespace) NamespaceSearchResult } class SearchResultFactory { -Language language -DecompilerSettings settings +Create(IEntity) MemberSearchResult +Create(MetadataFile, Resource, ITreeNode, ITreeNode) ResourceSearchResult +Create(MetadataFile) AssemblySearchResult +Create(MetadataFile, INamespace) NamespaceSearchResult -CalculateFitness(IEntity) float -GetLanguageSpecificName(IEntity) string -GetIcon(IEntity) object } class AbstractSearchStrategy { <<abstract>> #SearchRequest request #ISearchResultFactory factory +Search(PEFile, CancellationToken) void } class MemberSearchStrategy { -MemberSearchKind memberKind } class LiteralSearchStrategy class MetadataTokenSearchStrategy class ResourceSearchStrategy class AssemblySearchStrategy class NamespaceSearchStrategy class SearchResult { +float Fitness +string Name +string Assembly +object Reference +IComparer~SearchResult~ ComparerByFitness$ +IComparer~SearchResult~ ComparerByName$ } class MemberSearchResult class ResourceSearchResult class AssemblySearchResult class NamespaceSearchResult class SearchRequest { +string[] Keywords +Regex RegEx +SearchMode Mode +string InNamespace +string InAssembly +bool FullNameSearch +bool OmitGenerics +ISearchResultFactory ResultFactory +DecompilerSettings Settings } class MessageBus~T~ { <<service>> +Send(object, T) void$ +Subscribers EventHandler$ } class AssemblyTreeModel { +AssemblyList AssemblyList +Language CurrentLanguage +JumpToReferenceAsync(object, object, bool) Task } SearchPane "1" --o "0..1" RunningSearch : currentSearch SearchPane --> MessageBus~T~ : publishes SearchPane ..> AssemblyTreeModel : injects SearchPaneModel "1" --o "*" SearchModeModel : SearchModes RunningSearch --> SearchRequest : creates RunningSearch --> AbstractSearchStrategy : creates RunningSearch o-- SearchResult : ResultQueue SearchResultFactory ..|> ISearchResultFactory AbstractSearchStrategy --> ISearchResultFactory : uses AbstractSearchStrategy --> SearchResult : enqueues MemberSearchStrategy --|> AbstractSearchStrategy LiteralSearchStrategy --|> AbstractSearchStrategy MetadataTokenSearchStrategy --|> AbstractSearchStrategy ResourceSearchStrategy --|> AbstractSearchStrategy AssemblySearchStrategy --|> AbstractSearchStrategy NamespaceSearchStrategy --|> AbstractSearchStrategy MemberSearchResult --|> SearchResult ResourceSearchResult --|> SearchResult AssemblySearchResult --|> SearchResult NamespaceSearchResult --|> SearchResult AssemblyTreeModel --> MessageBus~T~ : subscribes
Fig. 1 — Full class diagram of the ILSpy Search subsystem
sequenceDiagram actor User participant SB as searchBox (WPF) participant SP as SearchPane participant RS as RunningSearch participant STRAT as AbstractSearchStrategy participant SRF as SearchResultFactory participant Q as ConcurrentQueue<SearchResult> participant OC as ObservableCollection<SearchResult> User->>SB: types characters SB->>SP: TextChanged event SP->>SP: SearchBox_TextChanged() SP->>RS: currentSearch.Cancel() note right of RS: cts.Cancel() signals CancellationToken SP->>OC: Results.Clear() SP->>RS: new RunningSearch(assemblies, term, mode, …) RS->>RS: Parse(searchTerm) RS->>SRF: new SearchResultFactory(language, settings) RS-->>SP: RunningSearch instance SP->>RS: await Run() activate RS RS->>STRAT: GetSearchStrategy(searchRequest) STRAT-->>RS: concrete strategy loop for each LoadedAssembly RS->>STRAT: Search(module, cancellationToken) loop for each matching entity STRAT->>SRF: Create(entity) SRF-->>STRAT: SearchResult (with Fitness) STRAT->>Q: TryAdd(result) end end deactivate RS loop CompositionTarget.Rendering (every frame) SP->>SP: UpdateResults() SP->>Q: TryTake(out result) Q-->>SP: SearchResult SP->>OC: InsertSorted(result, comparer) note right of OC: WPF binding updates ListBox end
Fig. 2 — WF1: User types a search term → results appear in the ListBox

6.3 Sequence Diagram — WF2: Filter / Mode Change

sequenceDiagram actor User participant CMB as SearchModeComboBox participant SP as SearchPane participant MB as MessageBus<SettingsChangedEventArgs> participant LS as LanguageSettings participant RS as RunningSearch alt User changes mode in ComboBox User->>CMB: select new mode CMB->>SP: SelectionChanged event SP->>SP: SearchModeComboBox_SelectionChanged() SP->>SP: StartSearch(this.SearchTerm) else Language / settings changed LS->>MB: property changed MB->>SP: SettingsChangedEventArgs SP->>SP: Settings_PropertyChanged() SP->>SP: UpdateFilter() SP->>SP: StartSearch(this.SearchTerm) end SP->>RS: currentSearch.Cancel() SP->>RS: new RunningSearch(assemblies, term, NEW_MODE, …) note right of RS: new mode → different AbstractSearchStrategy SP->>RS: await Run() RS->>RS: GetSearchStrategy(searchRequest) note right of RS: Returns different strategy for new SearchMode RS-->>SP: results flow into ResultQueue
Fig. 3 — WF2: User changes search mode or application settings trigger a filter update

6.4 Sequence Diagram — WF3: Result Selection & Navigation

sequenceDiagram actor User participant LB as ListBox (results) participant SP as SearchPane participant MB as MessageBus<NavigateToReferenceEventArgs> participant ATM as AssemblyTreeModel participant TV as DecompilerTextView User->>LB: double-click result LB->>SP: MouseDoubleClick event SP->>SP: ListBox_MouseDoubleClick() SP->>SP: JumpToSelectedItem(inNewTabPage=false) SP->>LB: SelectedItem as SearchResult LB-->>SP: SearchResult (with Reference) SP->>MB: Send(this, new NavigateToReferenceEventArgs(result.Reference, false)) MB->>ATM: NavigateToReferenceEventArgs delivered ATM->>ATM: JumpToReferenceAsync(reference, source, inNewTabPage) ATM->>ATM: Resolve EntityReference → assembly + entity ATM->>ATM: FindTreeNode(entity) ATM->>ATM: SelectNode(treeNode, inNewTabPage) ATM->>TV: decompile and display selected entity TV-->>User: decompiled code shown in text view note over User, TV: Ctrl+click opens in new tab (inNewTabPage=true)
Fig. 4 — WF3: User selects a search result → navigates to decompiled entity

7. CRUD Reference Table

Workflow Step Operation Entity / Resource Code Location
WF1 Search Entry 1D CancelRunningSearch.ctsStartSearch()
2D ClearResults (ObservableCollection)StartSearch()
3R ReadDisplaySettings.SortResultsStartSearch()
4R ReadAssemblyList.GetAllAssemblies()StartSearch()
5C Createnew RunningSearch(…)StartSearch()
6C CreateSearchRequest (via Parse())RunningSearch.Parse()
7C CreateAbstractSearchStrategy subclassGetSearchStrategy()
8C CreateSearchResult objects (via factory)AbstractSearchStrategy.Search()
9C CreateResults in ObservableCollectionUpdateResults()
WF2 Filter Change 1R ReadSelected SearchMode from ComboBoxSelectionChanged handler
2D CancelPrevious RunningSearchStartSearch()
3C CreateNew RunningSearch with new modeStartSearch()
4U UpdateSearchRequest.ModeRunningSearch.Parse()
WF3 Navigation 1R ReadlistBox.SelectedItemJumpToSelectedItem()
2C CreateNavigateToReferenceEventArgsJumpToSelectedItem()
3R ReadResolved EntityReference → assemblyAssemblyTreeModel.JumpToReferenceAsync()
4R ReadTree node lookup (FindTreeNode)AssemblyTreeModel
5U UpdateActive tree selection + text view contentSelectNode() + DecompilerTextView

8. Recommendations

8.1 Quality Recommendations

Q-01 — Single Responsibility Violation: RunningSearch nested in SearchPane
Priority: Medium

RunningSearch is a sealed nested class inside SearchPane.xaml.cs, mixing UI lifecycle concerns with background-task orchestration. This tightly couples the execution engine to the code-behind, making it impossible to unit-test without instantiating a WPF control.

Recommendation: Extract RunningSearch to its own internal file (RunningSearch.cs) in the Search/ folder. Introduce an IRunningSearch interface for testability. This mirrors the existing pattern of ISearchResultFactory.

Q-02 — No Input Debouncing on Search Box
Priority: Medium

Every keystroke fires SearchBox_TextChangedStartSearch() immediately. For large assembly lists this creates unnecessary background tasks killed almost immediately. The current CancellationToken approach mitigates the correctness problem but wastes thread-pool resources and CPU.

Recommendation: Add a DispatcherTimer debounce (e.g. 200 ms delay) before calling StartSearch(). The timer resets on each keypress and only fires when the user pauses.

Q-03 — MAX_RESULTS is a Hard-Coded Constant
Priority: Low

MAX_RESULTS = 1000 is a compile-time constant with no user-configurable override. Power users searching large codebases may need more results; casual users may prefer fewer for performance.

Recommendation: Surface this as a setting in DisplaySettings (alongside SortResults). Default to 1,000; allow range 100–5,000.

Q-04 — CompositionTarget.Rendering Frame-Rate Dependency
Priority: Low

Result insertion runs every WPF render frame (targeting 60 fps). On high-refresh-rate displays (144 Hz) UpdateResults is called 144 times per second instead of 60, spending more time draining the queue but with no additional benefit. On slow machines the 10 ms budget may be insufficient.

Recommendation: Replace with a DispatcherTimer at a fixed interval (e.g. 50 ms) or make MAX_REFRESH_TIME_MS adaptive based on measured frame time.

Q-05 — Parse() Has No Formal Grammar / Unit Tests
Priority: Medium

The prefix language (T:, INNAMESPACE:, @0x..., /regex/) is implemented ad-hoc in Parse(). Adding new prefixes risks silent regressions. There are no visible unit tests for this method.

Recommendation: Extract Parse logic into a standalone SearchQueryParser static class with an xUnit test suite covering: empty input, all prefixes, regex, combined (e.g. INNAMESPACE:Foo /Bar/), and edge cases (unmatched /, empty prefix value).

8.2 Security Recommendations

S-01 — Regex Catastrophic Backtracking (ReDoS)

When the user enters a pattern via /pattern/ syntax, Parse() creates a Regex object from raw user input with no timeout. A malicious or accidental input like /(a+)+$/ can cause exponential backtracking, freezing the background search thread (and consuming 100% CPU on that thread indefinitely, even after cancel if the regex is running).

Fix: Pass RegexOptions.None and a TimeSpan timeout to the Regex constructor:
new Regex(pattern, RegexOptions.Compiled, matchTimeout: TimeSpan.FromSeconds(1))
Catch RegexMatchTimeoutException in the strategy and treat it as no-match. Validate pattern length (< 500 chars) before construction.
S-02 — No Search Term Length Limit

SearchBox_TextChanged passes the full text of the search box to Parse() without any length check. A very long input (e.g. MB-sized paste) will be tokenized by CommandLineTools.CommandLineToArgumentArray() and iterated multiple times, creating large Keywords[] arrays. Combined with the regex issue above, this is a local DoS vector.

Fix: Add a guard at the top of StartSearch():
if (searchTerm?.Length > 500) return;
S-03 — Unvalidated EntityReference Resolution

AssemblyTreeModel.JumpToReferenceAsync() resolves an EntityReference by calling ResolveAssembly(AssemblyList). If a crafted navigation message were injected via MessageBus (possible if ILSpy ever processes external data such as XML workspace files), an attacker-controlled reference could trigger arbitrary file load from disk.

Fix: Validate that the source MetadataFile belongs to the current AssemblyList before resolving. Add a whitelist check on the resolved assembly path (must be under known loaded paths).
S-04 — File Path Exposure in ToolTips

Search results display assembly paths in ToolTips. If ILSpy were embedded in another tool that shows results to external parties, full file-system paths could leak sensitive deployment structure (e.g. internal build server paths on CI-scanned assemblies).

Fix: For untrusted assembly sources, normalize displayed paths relative to a configured base directory and avoid absolute paths in UI-visible strings.
S-05 — Metadata Reflection Without Validation in Token Search

MetadataTokenSearchStrategy accepts a raw hex token (@0x...) and uses it to look up metadata in potentially untrusted assemblies. Negative or out-of-range token values passed to the PEFile reader could trigger unhandled BadImageFormatException or access-violation-level scenarios in native code paths within ICSharpCode.Decompiler.

Fix: Validate the parsed token against valid range (0x01000001–0x7fffffff) and the table type byte before passing to the reader. Wrap the reader call in a try/catch for BadImageFormatException.

9. Evidence Appendix — Abyss Query References

All findings in this report are grounded in Abyss code-intelligence queries against the indexed ILSpy repository (293 documents, 1,746 chunks, 1,729 SCIP-enriched). Each entry below shows the query, file evidence, and the key chunk excerpt used.

Query #Query Text (abbreviated)Key Evidence FileKey Finding
Q1 "SearchPane search pane UI model WPF" Search/SearchPane.xaml.cs Confirmed MAX_RESULTS=1000, Results: ObservableCollection<SearchResult>, constructor subscriptions to MessageBus and CompositionTarget.Rendering.
Q2 "SearchPaneModel search results binding observable SearchTerm" Search/SearchPaneModel.cs Confirmed SearchTerm with SetProperty, SearchModes: SearchModeModel[12], PaneContentId="searchPane", session settings restore.
Q3 "SearchResultFactory factory result creation filtering" Search/SearchResultFactory.cs Confirmed ISearchResultFactory interface and four Create() overloads with their return types. Confirmed CalculateFitness(), GetLanguageSpecificName(), GetIcon().
Q4 "RunningSearch background task cancellation parallel" Search/SearchPane.xaml.cs Confirmed CancellationTokenSource, ConcurrentQueue<SearchResult> as ResultQueue, Task.Factory.StartNew(LongRunning) in Run().
Q5 "ISearchResult SearchResult interface Fitness Name Assembly Reference" Search/SearchResultFactory.cs Confirmed MemberSearchResult, AssemblySearchResult, NamespaceSearchResult as distinct subtypes. ComparerByFitness, ComparerByName on SearchResult.
Q6 "AbstractSearchStrategy MemberSearchStrategy GetSearchStrategy" Search/SearchPane.xaml.cs Confirmed GetSearchStrategy(SearchRequest) factory method with full 12-way switch, strategy class names, and their mode mappings.
Q7 "UpdateResults render results display sort fitness" Search/SearchPane.xaml.cs Confirmed CompositionTarget.Rendering += UpdateResults, MAX_REFRESH_TIME_MS=10, sorted insertion into Results using resultsComparer.
Q8 "NavigateToReferenceEventArgs MessageBus navigation JumpToReferenceAsync" AssemblyTree/AssemblyTreeModel.cs Confirmed JumpToReferenceAsync(object reference, object source, bool inNewTabPage). Confirmed NavigationState class, navigation pipeline to DecompilerTextView.
Q9 "Parse SearchRequest Keywords RegEx Mode INNAMESPACE INASSEMBLY" Search/SearchPane.xaml.cs Confirmed full prefix table: @=Token, INNAMESPACE, INASSEMBLY, T/TM/M/MD/F/P/E/C/R/N/A/AF/AN. Regex via /pattern/. Tokenizer: CommandLineTools.CommandLineToArgumentArray().
Q10 "AssemblySearchStrategy ResourceSearchStrategy NamespaceSearchStrategy" Search/ (multiple files) Confirmed concrete strategy class names, their purpose, and that they each extend AbstractSearchStrategy and receive SearchRequest + ISearchResultFactory.
Reproducibility: All queries were executed against Abyss MCP server with the ILSpy repository indexed at D:\repos\github\ILSpy\ILSpy (293 documents, 1,746 chunks). Filter used: file_path contains "Search" for primary entity queries. Strategy and secondary entity queries used no file filter, relying on semantic relevance ranking.