mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Language.GetCodeMappingInfo was unconditionally casting the incoming EntityHandle to TypeDefinitionHandle. MethodUsesAnalyzer calls it with the method's own handle and expects the language to walk to the declaring type — the WPF impl does, the Avalonia port didn't. Right-clicking a constructor → Analyze → expanding "Uses" therefore threw at runtime; the error surfaced only as a single-line message in the analyzer pane because AnalyzerErrorNode dropped the stack. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
2 changed files with 157 additions and 3 deletions
@ -0,0 +1,144 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using Avalonia.Headless.NUnit; |
||||||
|
|
||||||
|
using AwesomeAssertions; |
||||||
|
|
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
using ICSharpCode.ILSpyX.Analyzers; |
||||||
|
|
||||||
|
using ILSpy.Analyzers; |
||||||
|
using ILSpy.AppEnv; |
||||||
|
using ILSpy.TreeNodes; |
||||||
|
using ILSpy.ViewModels; |
||||||
|
using ILSpy.Views; |
||||||
|
|
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Tests.Analyzers; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Regression: analysing the "Used By" of a constructor must not throw. The reported
|
||||||
|
/// user-visible failure was an InvalidCastException in the live app; this test exercises
|
||||||
|
/// the same path (analyzer enumeration + the search-node WrapResult switch) but bypasses
|
||||||
|
/// the search node's catch so any exception bubbles up to NUnit with its stack trace.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture] |
||||||
|
public class AnalyzerConstructorUsesTests |
||||||
|
{ |
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Used_By_Analyzer_Runs_Over_A_Constructor_Without_Throwing() |
||||||
|
{ |
||||||
|
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||||
|
window.Show(); |
||||||
|
var vm = (MainWindowViewModel)window.DataContext!; |
||||||
|
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||||
|
|
||||||
|
// Pick any concrete type with at least one public instance ctor; the type itself
|
||||||
|
// doesn't matter — what matters is that the symbol fed to the analyzer is a method
|
||||||
|
// whose IsConstructor is true.
|
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||||
|
"System.Linq", "System.Linq", "System.Linq.Lookup`2"); |
||||||
|
typeNode.EnsureLazyChildren(); |
||||||
|
var ctorTreeNode = typeNode.Children.OfType<MethodTreeNode>() |
||||||
|
.FirstOrDefault(m => m.MethodDefinition.IsConstructor); |
||||||
|
(ctorTreeNode is null).Should().BeFalse( |
||||||
|
"Lookup<TKey,TElement> defines at least one constructor — the test depends on it"); |
||||||
|
var ctor = (IMethod)ctorTreeNode!.Member!; |
||||||
|
ctor.IsConstructor.Should().BeTrue(); |
||||||
|
|
||||||
|
// Resolve the "Used By" analyzer that actually applies to this method.
|
||||||
|
var analyzer = AnalyzerTreeNode.Analyzers |
||||||
|
.Where(a => a.Metadata?.Header == "Used By") |
||||||
|
.Select(a => a.CreateExport().Value) |
||||||
|
.First(a => a.Show(ctor)); |
||||||
|
|
||||||
|
// Drive Analyze directly (no AnalyzerSearchTreeNode → no swallowing catch). Take
|
||||||
|
// a small slice so we don't pay for a full scan; the bug repros on the first call
|
||||||
|
// site if it's going to throw at all.
|
||||||
|
var assemblyList = vm.AssemblyTreeModel.AssemblyList!; |
||||||
|
var context = new AnalyzerContext { |
||||||
|
CancellationToken = CancellationToken.None, |
||||||
|
Language = AppComposition.Current.GetExport<global::ILSpy.Languages.LanguageService>().CurrentLanguage, |
||||||
|
AssemblyList = assemblyList, |
||||||
|
}; |
||||||
|
var results = analyzer.Analyze(ctor, context).Take(20).ToList(); |
||||||
|
results.Should().NotBeEmpty("constructors of public LINQ types must have at least one in-tree caller"); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Uses_Analyzer_Runs_Over_A_Constructor_Without_Throwing() |
||||||
|
{ |
||||||
|
// "Uses" is the forward direction (what does the body of this method touch?).
|
||||||
|
// MethodUsesAnalyzer can return type/method/field/property/event ISymbols — the
|
||||||
|
// path most likely to surface a switch-arm hole for some result shape we missed.
|
||||||
|
|
||||||
|
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||||
|
window.Show(); |
||||||
|
var vm = (MainWindowViewModel)window.DataContext!; |
||||||
|
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||||
|
|
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||||
|
"System.Linq", "System.Linq", "System.Linq.Lookup`2"); |
||||||
|
typeNode.EnsureLazyChildren(); |
||||||
|
var ctor = (IMethod)typeNode.Children.OfType<MethodTreeNode>() |
||||||
|
.First(m => m.MethodDefinition.IsConstructor).Member!; |
||||||
|
|
||||||
|
var analyzer = AnalyzerTreeNode.Analyzers |
||||||
|
.Where(a => a.Metadata?.Header == "Uses") |
||||||
|
.Select(a => a.CreateExport().Value) |
||||||
|
.First(a => a.Show(ctor)); |
||||||
|
|
||||||
|
var assemblyList = vm.AssemblyTreeModel.AssemblyList!; |
||||||
|
var context = new AnalyzerContext { |
||||||
|
CancellationToken = CancellationToken.None, |
||||||
|
Language = AppComposition.Current.GetExport<global::ILSpy.Languages.LanguageService>().CurrentLanguage, |
||||||
|
AssemblyList = assemblyList, |
||||||
|
}; |
||||||
|
var results = analyzer.Analyze(ctor, context).Take(20).ToList(); |
||||||
|
results.Should().NotBeEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Analyze_Pushes_A_Constructor_Onto_The_Pane_Without_Throwing() |
||||||
|
{ |
||||||
|
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||||
|
window.Show(); |
||||||
|
var vm = (MainWindowViewModel)window.DataContext!; |
||||||
|
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||||
|
|
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||||
|
"System.Linq", "System.Linq", "System.Linq.Lookup`2"); |
||||||
|
typeNode.EnsureLazyChildren(); |
||||||
|
var ctor = (IMethod)typeNode.Children.OfType<MethodTreeNode>() |
||||||
|
.First(m => m.MethodDefinition.IsConstructor).Member!; |
||||||
|
|
||||||
|
var analyzerVm = AppComposition.Current.GetExport<AnalyzerTreeViewModel>(); |
||||||
|
var beforeCount = analyzerVm.Root.Children.Count; |
||||||
|
|
||||||
|
// This was the user-reported throw point.
|
||||||
|
analyzerVm.Analyze(ctor); |
||||||
|
|
||||||
|
analyzerVm.Root.Children.Count.Should().Be(beforeCount + 1); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue