From 34fa07cbb88498d156b7c4325ca3d5d67f50bc9e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 24 Feb 2011 21:06:32 +0100 Subject: [PATCH 01/11] add ILSpy analyzer Conflicts: ILSpy/MainWindow.xaml.cs --- ILSpy/ILSpy.csproj | 6 +- ILSpy/Images/Images.cs | 1 + ILSpy/MainWindow.xaml | 14 ++- ILSpy/MainWindow.xaml.cs | 30 ++++++ .../Analyzer/AnalyzedMethodTreeNode.cs | 69 ++++++++++++++ .../Analyzer/AnalyzedMethodUsedByTreeNode.cs | 92 +++++++++++++++++++ ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs | 28 ++++++ ILSpy/TreeNodes/DerivedTypesTreeNode.cs | 28 +++++- ILSpy/TreeNodes/ILSpyTreeNode.cs | 2 +- ILSpy/TreeNodes/MethodTreeNode.cs | 58 ++++++++---- ...hreadedTreeNode.cs => ThreadingSupport.cs} | 34 ++----- ILSpy/themes/generic.xaml | 4 + 12 files changed, 314 insertions(+), 52 deletions(-) create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs rename ILSpy/TreeNodes/{ThreadedTreeNode.cs => ThreadingSupport.cs} (70%) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index df6244777..0060256bd 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -133,6 +133,9 @@ + + + @@ -148,7 +151,7 @@ - + @@ -233,6 +236,7 @@ + \ No newline at end of file diff --git a/ILSpy/Images/Images.cs b/ILSpy/Images/Images.cs index 701ca051f..b144b6ef9 100644 --- a/ILSpy/Images/Images.cs +++ b/ILSpy/Images/Images.cs @@ -67,5 +67,6 @@ namespace ICSharpCode.ILSpy public static readonly BitmapImage ProtectedStruct = LoadBitmap("ProtectedStruct"); public static readonly BitmapImage Delete = LoadBitmap("Delete"); + public static readonly BitmapImage Search = LoadBitmap("Search"); } } diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index bc4db2307..adaffe58e 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -13,11 +13,7 @@ FocusManager.FocusedElement="{Binding ElementName=treeView}" > - - - - - + + @@ -160,6 +157,13 @@ + + + + + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 4d5de03c7..e7e766f64 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -27,7 +27,10 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Imaging; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.TreeNodes.Analyzer; using ICSharpCode.TreeView; using Microsoft.Win32; @@ -45,8 +48,15 @@ namespace ICSharpCode.ILSpy AssemblyList assemblyList; AssemblyListTreeNode assemblyListTreeNode; + static MainWindow instance; + + public static MainWindow Instance { + get { return instance; } + } + public MainWindow() { + instance = this; spySettings = ILSpySettings.Load(); this.sessionSettings = new SessionSettings(spySettings); this.assemblyListManager = new AssemblyListManager(spySettings); @@ -369,6 +379,26 @@ namespace ICSharpCode.ILSpy } #endregion + #region Analyzer + public void Analyze(MethodTreeNode method) + { + if (analyzerTree.Root == null) + analyzerTree.Root = new SharpTreeNode(); + + if (analyzerPanel.Visibility != Visibility.Visible) + analyzerPanel.Visibility = Visibility.Visible; + + var newNode = new AnalyzedMethodTreeNode(method.MethodDefinition) { + Language = sessionSettings.FilterSettings.Language + }; + + if (analyzerTree.Root.Children.Contains(newNode)) + analyzerTree.Root.Children.Remove(newNode); + + analyzerTree.Root.Children.Add(newNode); + } + #endregion + protected override void OnStateChanged(EventArgs e) { base.OnStateChanged(e); diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs new file mode 100644 index 000000000..809698ac5 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2011 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; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzedMethodTreeNode : AnalyzerTreeNode + { + MethodDefinition analyzedMethod; + + public AnalyzedMethodTreeNode(MethodDefinition analyzedMethod) + { + if (analyzedMethod == null) + throw new ArgumentNullException("analyzedMethod"); + this.analyzedMethod = analyzedMethod; + this.LazyLoading = true; + } + + public override object Icon { + get { return MethodTreeNode.GetIcon(analyzedMethod); } + } + + public override object Text { + get { return MethodTreeNode.GetText(analyzedMethod, Language); } + } + + protected override void LoadChildren() + { + this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); + } + + #region Equals and GetHashCode implementation + public override bool Equals(object obj) + { + AnalyzedMethodTreeNode other = obj as AnalyzedMethodTreeNode; + if (other == null) + return false; + return object.Equals(this.analyzedMethod, other.analyzedMethod); + } + + public override int GetHashCode() + { + int hashCode = 0; + unchecked { + if (analyzedMethod != null) + hashCode += 1000000007 * analyzedMethod.GetHashCode(); + } + return hashCode; + } + #endregion + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs new file mode 100644 index 000000000..41cf720ba --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2011 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; +using System.Collections.Generic; +using System.Threading; + +using ICSharpCode.NRefactory.Utils; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzedMethodUsedByTreeNode : ILSpyTreeNode + { + MethodDefinition analyzedMethod; + ThreadingSupport threading; + + public AnalyzedMethodUsedByTreeNode(MethodDefinition analyzedMethod) + { + if (analyzedMethod == null) + throw new ArgumentNullException("analyzedMethod"); + + this.analyzedMethod = analyzedMethod; + this.threading = new ThreadingSupport(); + this.LazyLoading = true; + } + + public override object Text { + get { return "Used By"; } + } + + public override object Icon { + get { return Images.Search; } + } + + protected override void LoadChildren() + { + threading.LoadChildren(this, FetchChildren); + } + + IEnumerable FetchChildren(CancellationToken ct) + { + return FindReferences(MainWindow.Instance.AssemblyList.GetAssemblies(), ct); + } + + IEnumerable FindReferences(LoadedAssembly[] assemblies, CancellationToken ct) + { + foreach (LoadedAssembly asm in assemblies) { + ct.ThrowIfCancellationRequested(); + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (MethodDefinition method in type.Methods) { + ct.ThrowIfCancellationRequested(); + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + if (instr.Operand is MethodReference + && ((MethodReference)instr.Operand).Resolve() == analyzedMethod) { + found = true; + break; + } + } + if (found) + yield return new MethodTreeNode(method); + } + } + } + } + + public override void Decompile(Language language, ICSharpCode.Decompiler.ITextOutput output, DecompilationOptions options) + { + throw new NotImplementedException(); + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs new file mode 100644 index 000000000..0a91760a4 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2011 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; +using ICSharpCode.TreeView; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzerTreeNode : SharpTreeNode + { + public Language Language { get; set; } + } +} diff --git a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs index 079b3ceae..74344a4d0 100644 --- a/ILSpy/TreeNodes/DerivedTypesTreeNode.cs +++ b/ILSpy/TreeNodes/DerivedTypesTreeNode.cs @@ -15,15 +15,18 @@ namespace ICSharpCode.ILSpy.TreeNodes /// /// Lists the super types of a class. /// - sealed class DerivedTypesTreeNode : ThreadedTreeNode + sealed class DerivedTypesTreeNode : ILSpyTreeNode { readonly AssemblyList list; readonly TypeDefinition type; + ThreadingSupport threading; public DerivedTypesTreeNode(AssemblyList list, TypeDefinition type) { this.list = list; this.type = type; + this.LazyLoading = true; + this.threading = new ThreadingSupport(); } public override object Text { @@ -34,7 +37,12 @@ namespace ICSharpCode.ILSpy.TreeNodes get { return Images.SubTypes; } } - protected override IEnumerable FetchChildren(CancellationToken cancellationToken) + protected override void LoadChildren() + { + threading.LoadChildren(this, FetchChildren); + } + + IEnumerable FetchChildren(CancellationToken cancellationToken) { // FetchChildren() runs on the main thread; but the enumerator will be consumed on a background thread var assemblies = list.GetAssemblies().Select(node => node.AssemblyDefinition).Where(asm => asm != null).ToArray(); @@ -62,17 +70,24 @@ namespace ICSharpCode.ILSpy.TreeNodes { return typeRef.FullName == type.FullName; } + + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + { + threading.Decompile(language, output, options, EnsureLazyChildren); + } } - class DerivedTypesEntryNode : ThreadedTreeNode + class DerivedTypesEntryNode : ILSpyTreeNode { TypeDefinition def; AssemblyDefinition[] assemblies; + ThreadingSupport threading; public DerivedTypesEntryNode(TypeDefinition def, AssemblyDefinition[] assemblies) { this.def = def; this.assemblies = assemblies; + threading = new ThreadingSupport(); } public override bool ShowExpander { @@ -91,7 +106,12 @@ namespace ICSharpCode.ILSpy.TreeNodes } } - protected override IEnumerable FetchChildren(CancellationToken ct) + protected override void LoadChildren() + { + threading.LoadChildren(this, FetchChildren); + } + + IEnumerable FetchChildren(CancellationToken ct) { // FetchChildren() runs on the main thread; but the enumerator will be consumed on a background thread return DerivedTypesTreeNode.FindDerivedTypes(def, assemblies, ct); diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 3aef3bfc5..960e32203 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -56,7 +56,7 @@ namespace ICSharpCode.ILSpy.TreeNodes return FilterResult.Hidden; } - protected object HighlightSearchMatch(string text, string suffix = null) + protected static object HighlightSearchMatch(string text, string suffix = null) { // TODO: implement highlighting the search match return text + suffix; diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs index fd1802957..3e28884b9 100644 --- a/ILSpy/TreeNodes/MethodTreeNode.cs +++ b/ILSpy/TreeNodes/MethodTreeNode.cs @@ -18,6 +18,8 @@ using System; using System.Text; +using System.Windows.Controls; +using System.Windows.Media.Imaging; using ICSharpCode.Decompiler; using Mono.Cecil; @@ -43,30 +45,41 @@ namespace ICSharpCode.ILSpy.TreeNodes public override object Text { get { - StringBuilder b = new StringBuilder(); - b.Append('('); - for (int i = 0; i < method.Parameters.Count; i++) { - if (i > 0) b.Append(", "); - b.Append(this.Language.TypeToString(method.Parameters[i].ParameterType, false, method.Parameters[i])); - } - b.Append(") : "); - b.Append(this.Language.TypeToString(method.ReturnType, false, method.MethodReturnType)); - return HighlightSearchMatch(method.Name, b.ToString()); + return GetText(method, Language); + } + } + + public static object GetText(MethodDefinition method, Language language) + { + StringBuilder b = new StringBuilder(); + b.Append('('); + for (int i = 0; i < method.Parameters.Count; i++) { + if (i > 0) + b.Append(", "); + b.Append(language.TypeToString(method.Parameters[i].ParameterType, false, method.Parameters[i])); } + b.Append(") : "); + b.Append(language.TypeToString(method.ReturnType, false, method.MethodReturnType)); + return HighlightSearchMatch(method.Name, b.ToString()); } public override object Icon { get { - if (method.IsSpecialName && method.Name.StartsWith("op_", StringComparison.Ordinal)) - return Images.Operator; - if (method.IsStatic && method.HasCustomAttributes) { - foreach (var ca in method.CustomAttributes) { - if (ca.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute") - return Images.ExtensionMethod; - } + return GetIcon(method); + } + } + + public static BitmapImage GetIcon(MethodDefinition method) + { + if (method.IsSpecialName && method.Name.StartsWith("op_", StringComparison.Ordinal)) + return Images.Operator; + if (method.IsStatic && method.HasCustomAttributes) { + foreach (var ca in method.CustomAttributes) { + if (ca.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute") + return Images.ExtensionMethod; } - return Images.Method; } + return Images.Method; } public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) @@ -81,5 +94,16 @@ namespace ICSharpCode.ILSpy.TreeNodes else return FilterResult.Hidden; } + + public override System.Windows.Controls.ContextMenu GetContextMenu() + { + ContextMenu menu = new ContextMenu(); + MenuItem item = new MenuItem() { Header = "Analyze", Icon = new Image() { Source = Images.Search } }; + item.Click += delegate { MainWindow.Instance.Analyze(this); }; + + menu.Items.Add(item); + + return menu; + } } } diff --git a/ILSpy/TreeNodes/ThreadedTreeNode.cs b/ILSpy/TreeNodes/ThreadingSupport.cs similarity index 70% rename from ILSpy/TreeNodes/ThreadedTreeNode.cs rename to ILSpy/TreeNodes/ThreadingSupport.cs index fca99f5af..1d0dd1f71 100644 --- a/ILSpy/TreeNodes/ThreadedTreeNode.cs +++ b/ILSpy/TreeNodes/ThreadingSupport.cs @@ -11,36 +11,22 @@ using ICSharpCode.Decompiler; namespace ICSharpCode.ILSpy.TreeNodes { /// - /// Node that is lazy-loaded and loads its children on a background thread. + /// Adds threading support to nodes /// - abstract class ThreadedTreeNode : ILSpyTreeNode + class ThreadingSupport { Task> loadChildrenTask; - public ThreadedTreeNode() - { - this.LazyLoading = true; - } - - public void Invalidate() - { - this.LazyLoading = true; - this.Children.Clear(); - loadChildrenTask = null; - } - /// - /// FetchChildren() runs on the main thread; but the enumerator is consumed on a background thread + /// /// - protected abstract IEnumerable FetchChildren(CancellationToken ct); - - protected override sealed void LoadChildren() + public void LoadChildren(ILSpyTreeNode node, Func> fetchChildren) { - this.Children.Add(new LoadingTreeNode()); + node.Children.Add(new LoadingTreeNode()); CancellationToken ct = CancellationToken.None; - var fetchChildrenEnumerable = FetchChildren(ct); + var fetchChildrenEnumerable = fetchChildren(ct); Task> thisTask = null; thisTask = new Task>( delegate { @@ -53,14 +39,14 @@ namespace ICSharpCode.ILSpy.TreeNodes // don't access "child" here the // background thread might already be running the next loop iteration if (loadChildrenTask == thisTask) { - this.Children.Insert(this.Children.Count - 1, newChild); + node.Children.Insert(node.Children.Count - 1, newChild); } }), child); } App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( delegate { if (loadChildrenTask == thisTask) { - this.Children.RemoveAt(this.Children.Count - 1); // remove 'Loading...' + node.Children.RemoveAt(node.Children.Count - 1); // remove 'Loading...' } })); return result; @@ -72,11 +58,11 @@ namespace ICSharpCode.ILSpy.TreeNodes thisTask.Wait(TimeSpan.FromMilliseconds(200)); } - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + public void Decompile(Language language, ITextOutput output, DecompilationOptions options, Action ensureLazyChildren) { var loadChildrenTask = this.loadChildrenTask; if (loadChildrenTask == null) { - App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren)); + App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, ensureLazyChildren); loadChildrenTask = this.loadChildrenTask; } if (loadChildrenTask != null) { diff --git a/ILSpy/themes/generic.xaml b/ILSpy/themes/generic.xaml index 7d6202224..5af30034d 100644 --- a/ILSpy/themes/generic.xaml +++ b/ILSpy/themes/generic.xaml @@ -22,4 +22,8 @@ Data = "M 5,5 L 10,10 L 15,5 L 5,5"/> + + + + \ No newline at end of file From ead42305f86a44257f4f4e6a7b7b5e2aabd321f9 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 24 Feb 2011 20:29:34 +0100 Subject: [PATCH 02/11] replace DockPanel with Grid and add a GridSplitter to allow Analyzer to be resized + other cleanup --- ILSpy/MainWindow.xaml | 29 ++++++++++++++----- ILSpy/MainWindow.xaml.cs | 6 ++-- .../Analyzer/AnalyzedMethodTreeNode.cs | 20 ------------- 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index adaffe58e..4c7f34411 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -144,10 +144,13 @@ BorderBrush="Transparent" HorizontalAlignment="Stretch" /> - - - + + + + + + + @@ -157,15 +160,25 @@ - + + + + + - - - + \ No newline at end of file diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index e7e766f64..a97756e74 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -389,12 +389,10 @@ namespace ICSharpCode.ILSpy analyzerPanel.Visibility = Visibility.Visible; var newNode = new AnalyzedMethodTreeNode(method.MethodDefinition) { - Language = sessionSettings.FilterSettings.Language + Language = sessionSettings.FilterSettings.Language, + IsExpanded = true }; - if (analyzerTree.Root.Children.Contains(newNode)) - analyzerTree.Root.Children.Remove(newNode); - analyzerTree.Root.Children.Add(newNode); } #endregion diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs index 809698ac5..0974d12bd 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -45,25 +45,5 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); } - - #region Equals and GetHashCode implementation - public override bool Equals(object obj) - { - AnalyzedMethodTreeNode other = obj as AnalyzedMethodTreeNode; - if (other == null) - return false; - return object.Equals(this.analyzedMethod, other.analyzedMethod); - } - - public override int GetHashCode() - { - int hashCode = 0; - unchecked { - if (analyzedMethod != null) - hashCode += 1000000007 * analyzedMethod.GetHashCode(); - } - return hashCode; - } - #endregion } } From 3302748c58610ca720bf2285f36964a50dcb30e6 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 24 Feb 2011 22:24:21 +0100 Subject: [PATCH 03/11] Bugfix SharpTreeNodeCollection.Clear(). --- SharpTreeView/SharpTreeNodeCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharpTreeView/SharpTreeNodeCollection.cs b/SharpTreeView/SharpTreeNodeCollection.cs index 57d13124e..b49881304 100644 --- a/SharpTreeView/SharpTreeNodeCollection.cs +++ b/SharpTreeView/SharpTreeNodeCollection.cs @@ -140,7 +140,7 @@ namespace ICSharpCode.TreeView ThrowOnReentrancy(); var oldList = list; list = new List(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list, 0)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldList, 0)); } public bool Contains(SharpTreeNode node) From e9ad53a607ea99f9cf14584bfea23e55f9add1ac Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 24 Feb 2011 22:33:05 +0100 Subject: [PATCH 04/11] Improvements for the analyzer. --- ILSpy/MainWindow.xaml | 41 +++++----- ILSpy/MainWindow.xaml.cs | 54 ++++++++++--- ILSpy/SessionSettings.cs | 3 + ILSpy/TextView/DecompilerTextView.cs | 15 +--- .../Analyzer/AnalyzedMethodTreeNode.cs | 6 ++ .../Analyzer/AnalyzedMethodUsedByTreeNode.cs | 57 ++++++++------ ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs | 38 ++++++++- ILSpy/TreeNodes/MethodTreeNode.cs | 3 +- ILSpy/TreeNodes/ThreadingSupport.cs | 78 +++++++++++++++---- 9 files changed, 212 insertions(+), 83 deletions(-) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 4c7f34411..4406128e3 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -61,7 +61,7 @@ - + @@ -139,16 +139,22 @@ + Margin="-5,0" + BorderThickness="5,0" + HorizontalAlignment="Center" + VerticalAlignment="Stretch" + BorderBrush="Transparent" /> + + + - - + + + @@ -163,21 +169,20 @@ - - - - - - + + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index a97756e74..002edb75d 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -33,6 +33,7 @@ using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes.Analyzer; using ICSharpCode.TreeView; using Microsoft.Win32; +using Mono.Cecil; namespace ICSharpCode.ILSpy { @@ -260,6 +261,23 @@ namespace ICSharpCode.ILSpy path.Reverse(); return path.ToArray(); } + + public void JumpToReference(object reference) + { + if (reference is TypeReference) { + SelectNode(assemblyListTreeNode.FindTypeNode(((TypeReference)reference).Resolve())); + } else if (reference is MethodReference) { + SelectNode(assemblyListTreeNode.FindMethodNode(((MethodReference)reference).Resolve())); + } else if (reference is FieldReference) { + SelectNode(assemblyListTreeNode.FindFieldNode(((FieldReference)reference).Resolve())); + } else if (reference is PropertyReference) { + SelectNode(assemblyListTreeNode.FindPropertyNode(((PropertyReference)reference).Resolve())); + } else if (reference is EventReference) { + SelectNode(assemblyListTreeNode.FindEventNode(((EventReference)reference).Resolve())); + } else if (reference is AssemblyDefinition) { + SelectNode(assemblyListTreeNode.FindAssemblyNode((AssemblyDefinition)reference)); + } + } #endregion #region Open/Refresh @@ -380,20 +398,18 @@ namespace ICSharpCode.ILSpy #endregion #region Analyzer - public void Analyze(MethodTreeNode method) + public void AddToAnalyzer(AnalyzerTreeNode node) { if (analyzerTree.Root == null) - analyzerTree.Root = new SharpTreeNode(); + analyzerTree.Root = new AnalyzerTreeNode { Language = sessionSettings.FilterSettings.Language }; - if (analyzerPanel.Visibility != Visibility.Visible) - analyzerPanel.Visibility = Visibility.Visible; + if (!showAnalyzer.IsChecked) + showAnalyzer.IsChecked = true; - var newNode = new AnalyzedMethodTreeNode(method.MethodDefinition) { - Language = sessionSettings.FilterSettings.Language, - IsExpanded = true - }; - - analyzerTree.Root.Children.Add(newNode); + node.IsExpanded = true; + analyzerTree.Root.Children.Add(node); + analyzerTree.SelectedItem = node; + analyzerTree.FocusNode(node); } #endregion @@ -412,7 +428,25 @@ namespace ICSharpCode.ILSpy sessionSettings.ActiveTreeViewPath = GetPathForNode(treeView.SelectedItem as SharpTreeNode); sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.SplitterPosition = leftColumn.Width.Value / (leftColumn.Width.Value + rightColumn.Width.Value); + if (showAnalyzer.IsChecked) + sessionSettings.AnalyzerSplitterPosition = analyzerRow.Height.Value / (analyzerRow.Height.Value + textViewRow.Height.Value); sessionSettings.Save(); } + + void ShowAnalyzer_Checked(object sender, RoutedEventArgs e) + { + analyzerRow.MinHeight = 100; + if (sessionSettings.AnalyzerSplitterPosition > 0 && sessionSettings.AnalyzerSplitterPosition < 1) { + textViewRow.Height = new GridLength(1 - sessionSettings.AnalyzerSplitterPosition, GridUnitType.Star); + analyzerRow.Height = new GridLength(sessionSettings.AnalyzerSplitterPosition, GridUnitType.Star); + } + } + + void ShowAnalyzer_Unchecked(object sender, RoutedEventArgs e) + { + sessionSettings.AnalyzerSplitterPosition = analyzerRow.Height.Value / (analyzerRow.Height.Value + textViewRow.Height.Value); + analyzerRow.MinHeight = 0; + analyzerRow.Height = new GridLength(0); + } } } \ No newline at end of file diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index f3c50e2c0..11d8346d3 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -49,6 +49,7 @@ namespace ICSharpCode.ILSpy this.WindowState = FromString((string)doc.Element("WindowState"), WindowState.Normal); this.WindowBounds = FromString((string)doc.Element("WindowBounds"), new Rect(10, 10, 750, 550)); this.SplitterPosition = FromString((string)doc.Element("SplitterPosition"), 0.4); + this.AnalyzerSplitterPosition = FromString((string)doc.Element("AnalyzerSplitterPosition"), 0.3); } public event PropertyChangedEventHandler PropertyChanged; @@ -68,6 +69,7 @@ namespace ICSharpCode.ILSpy public WindowState WindowState = WindowState.Normal; public Rect WindowBounds; public double SplitterPosition; + public double AnalyzerSplitterPosition; public void Save() { @@ -82,6 +84,7 @@ namespace ICSharpCode.ILSpy doc.Add(new XElement("WindowState", ToString(this.WindowState))); doc.Add(new XElement("WindowBounds", ToString(this.WindowBounds))); doc.Add(new XElement("SplitterPosition", ToString(this.SplitterPosition))); + doc.Add(new XElement("AnalyzerSplitterPosition", ToString(this.AnalyzerSplitterPosition))); ILSpySettings.SaveSettings(doc); } diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index e4355a1ca..f3d0ccb0f 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -359,20 +359,7 @@ namespace ICSharpCode.ILSpy.TextView return; } } - var assemblyListTreeNode = mainWindow.AssemblyListTreeNode; - if (reference is TypeReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindTypeNode(((TypeReference)reference).Resolve())); - } else if (reference is MethodReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindMethodNode(((MethodReference)reference).Resolve())); - } else if (reference is FieldReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindFieldNode(((FieldReference)reference).Resolve())); - } else if (reference is PropertyReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindPropertyNode(((PropertyReference)reference).Resolve())); - } else if (reference is EventReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindEventNode(((EventReference)reference).Resolve())); - } else if (reference is AssemblyDefinition) { - mainWindow.SelectNode(assemblyListTreeNode.FindAssemblyNode((AssemblyDefinition)reference)); - } + mainWindow.JumpToReference(reference); } #endregion diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs index 0974d12bd..1d0308b84 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -41,6 +41,12 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer get { return MethodTreeNode.GetText(analyzedMethod, Language); } } + public override void ActivateItem(System.Windows.RoutedEventArgs e) + { + e.Handled = true; + MainWindow.Instance.JumpToReference(analyzedMethod); + } + protected override void LoadChildren() { this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs index 41cf720ba..72f43f6ba 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs @@ -18,15 +18,17 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; - +using System.Threading.Tasks; using ICSharpCode.NRefactory.Utils; +using ICSharpCode.TreeView; using Mono.Cecil; using Mono.Cecil.Cil; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { - class AnalyzedMethodUsedByTreeNode : ILSpyTreeNode + class AnalyzedMethodUsedByTreeNode : AnalyzerTreeNode { MethodDefinition analyzedMethod; ThreadingSupport threading; @@ -54,39 +56,46 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer threading.LoadChildren(this, FetchChildren); } - IEnumerable FetchChildren(CancellationToken ct) + protected override void OnCollapsing() + { + if (threading.IsRunning) { + this.LazyLoading = true; + threading.Cancel(); + this.Children.Clear(); + } + } + + IEnumerable FetchChildren(CancellationToken ct) { return FindReferences(MainWindow.Instance.AssemblyList.GetAssemblies(), ct); } - IEnumerable FindReferences(LoadedAssembly[] assemblies, CancellationToken ct) + IEnumerable FindReferences(LoadedAssembly[] assemblies, CancellationToken ct) { - foreach (LoadedAssembly asm in assemblies) { + // use parallelism only on the assembly level (avoid locks within Cecil) + return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + } + + IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + { + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { ct.ThrowIfCancellationRequested(); - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { + foreach (MethodDefinition method in type.Methods) { ct.ThrowIfCancellationRequested(); - foreach (MethodDefinition method in type.Methods) { - ct.ThrowIfCancellationRequested(); - bool found = false; - if (!method.HasBody) - continue; - foreach (Instruction instr in method.Body.Instructions) { - if (instr.Operand is MethodReference - && ((MethodReference)instr.Operand).Resolve() == analyzedMethod) { - found = true; - break; - } + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + if (instr.Operand is MethodReference + && ((MethodReference)instr.Operand).Resolve() == analyzedMethod) { + found = true; + break; } - if (found) - yield return new MethodTreeNode(method); } + if (found) + yield return new AnalyzedMethodTreeNode(method); } } } - - public override void Decompile(Language language, ICSharpCode.Decompiler.ITextOutput output, DecompilationOptions options) - { - throw new NotImplementedException(); - } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs index 0a91760a4..e91b048d8 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs @@ -17,12 +17,48 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Linq; using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { class AnalyzerTreeNode : SharpTreeNode { - public Language Language { get; set; } + Language language; + + public Language Language { + get { return language; } + set { + if (language != value) { + language = value; + foreach (var child in this.Children.OfType()) + child.Language = value; + } + } + } + + public override bool CanDelete() + { + return Parent != null && Parent.IsRoot; + } + + public override void DeleteCore() + { + Parent.Children.Remove(this); + } + + public override void Delete() + { + DeleteCore(); + } + + protected override void OnChildrenChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) { + foreach (AnalyzerTreeNode a in e.NewItems.OfType()) + a.Language = this.Language; + } + base.OnChildrenChanged(e); + } } } diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs index 3e28884b9..7c015d5fd 100644 --- a/ILSpy/TreeNodes/MethodTreeNode.cs +++ b/ILSpy/TreeNodes/MethodTreeNode.cs @@ -21,6 +21,7 @@ using System.Text; using System.Windows.Controls; using System.Windows.Media.Imaging; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.TreeNodes.Analyzer; using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes @@ -99,7 +100,7 @@ namespace ICSharpCode.ILSpy.TreeNodes { ContextMenu menu = new ContextMenu(); MenuItem item = new MenuItem() { Header = "Analyze", Icon = new Image() { Source = Images.Search } }; - item.Click += delegate { MainWindow.Instance.Analyze(this); }; + item.Click += delegate { MainWindow.Instance.AddToAnalyzer(new AnalyzedMethodTreeNode(method)); }; menu.Items.Add(item); diff --git a/ILSpy/TreeNodes/ThreadingSupport.cs b/ILSpy/TreeNodes/ThreadingSupport.cs index 1d0dd1f71..a375ad978 100644 --- a/ILSpy/TreeNodes/ThreadingSupport.cs +++ b/ILSpy/TreeNodes/ThreadingSupport.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; using ICSharpCode.Decompiler; +using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.TreeNodes { @@ -15,27 +17,39 @@ namespace ICSharpCode.ILSpy.TreeNodes /// class ThreadingSupport { - Task> loadChildrenTask; + Task> loadChildrenTask; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + public bool IsRunning { + get { return !loadChildrenTask.IsCompleted; } + } + + public void Cancel() + { + cancellationTokenSource.Cancel(); + loadChildrenTask = null; + cancellationTokenSource = new CancellationTokenSource(); + } /// - /// + /// Starts loading the children of the specified node. /// - public void LoadChildren(ILSpyTreeNode node, Func> fetchChildren) + public void LoadChildren(SharpTreeNode node, Func> fetchChildren) { node.Children.Add(new LoadingTreeNode()); - CancellationToken ct = CancellationToken.None; + CancellationToken ct = cancellationTokenSource.Token; var fetchChildrenEnumerable = fetchChildren(ct); - Task> thisTask = null; - thisTask = new Task>( + Task> thisTask = null; + thisTask = new Task>( delegate { - List result = new List(); - foreach (ILSpyTreeNode child in fetchChildrenEnumerable) { + List result = new List(); + foreach (SharpTreeNode child in fetchChildrenEnumerable) { ct.ThrowIfCancellationRequested(); result.Add(child); - App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( - delegate (ILSpyTreeNode newChild) { + App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( + delegate (SharpTreeNode newChild) { // don't access "child" here the // background thread might already be running the next loop iteration if (loadChildrenTask == thisTask) { @@ -43,16 +57,27 @@ namespace ICSharpCode.ILSpy.TreeNodes } }), child); } + return result; + }, ct); + loadChildrenTask = thisTask; + thisTask.Start(); + thisTask.ContinueWith( + delegate (Task continuation) { App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( delegate { if (loadChildrenTask == thisTask) { node.Children.RemoveAt(node.Children.Count - 1); // remove 'Loading...' } + if (continuation.Exception != null) { // observe exception even when task isn't current + if (loadChildrenTask == thisTask) { + foreach (Exception ex in continuation.Exception.InnerExceptions) { + node.Children.Add(new ErrorTreeNode(ex.ToString())); + } + } + } })); - return result; - }, ct); - loadChildrenTask = thisTask; - thisTask.Start(); + }); + // Give the task a bit time to complete before we return to WPF - this keeps "Loading..." // from showing up for very short waits. thisTask.Wait(TimeSpan.FromMilliseconds(200)); @@ -66,7 +91,7 @@ namespace ICSharpCode.ILSpy.TreeNodes loadChildrenTask = this.loadChildrenTask; } if (loadChildrenTask != null) { - foreach (var child in loadChildrenTask.Result) { + foreach (ILSpyTreeNode child in loadChildrenTask.Result.Cast()) { child.Decompile(language, output, options); } } @@ -87,5 +112,28 @@ namespace ICSharpCode.ILSpy.TreeNodes { } } + + sealed class ErrorTreeNode : ILSpyTreeNode + { + string text; + + public override object Text { + get { return text; } + } + + public ErrorTreeNode(string text) + { + this.text = text; + } + + public override FilterResult Filter(FilterSettings settings) + { + return FilterResult.Match; + } + + public override void Decompile(Language language, ICSharpCode.Decompiler.ITextOutput output, DecompilationOptions options) + { + } + } } } From 2d4de37ac23bf579f7f206ed2df7b2432d9c2528 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 24 Feb 2011 22:38:32 +0100 Subject: [PATCH 05/11] Fix NullReferenceException in AssemblyListTreeNode.Find*Node --- ILSpy/TreeNodes/AssemblyListTreeNode.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs index 0530055c3..12a4da03c 100644 --- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -186,6 +186,8 @@ namespace ICSharpCode.ILSpy.TreeNodes if (def == null) return null; TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + if (typeNode == null) + return null; typeNode.EnsureLazyChildren(); MethodTreeNode methodNode = typeNode.Children.OfType().FirstOrDefault(m => m.MethodDefinition == def && !m.IsHidden); if (methodNode != null) @@ -212,6 +214,8 @@ namespace ICSharpCode.ILSpy.TreeNodes if (def == null) return null; TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + if (typeNode == null) + return null; typeNode.EnsureLazyChildren(); return typeNode.Children.OfType().FirstOrDefault(m => m.FieldDefinition == def && !m.IsHidden); } @@ -225,6 +229,8 @@ namespace ICSharpCode.ILSpy.TreeNodes if (def == null) return null; TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + if (typeNode == null) + return null; typeNode.EnsureLazyChildren(); return typeNode.Children.OfType().FirstOrDefault(m => m.PropertyDefinition == def && !m.IsHidden); } @@ -238,6 +244,8 @@ namespace ICSharpCode.ILSpy.TreeNodes if (def == null) return null; TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + if (typeNode == null) + return null; typeNode.EnsureLazyChildren(); return typeNode.Children.OfType().FirstOrDefault(m => m.EventDefinition == def && !m.IsHidden); } From 4e0d9ddefee435cf1143e221aef219fd74971b39 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 24 Feb 2011 22:39:22 +0100 Subject: [PATCH 06/11] Analyzer: Show type name; avoid resolving (and potentially loading assemblies) where possible. --- ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs | 2 +- ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs index 1d0308b84..5287fee95 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer } public override object Text { - get { return MethodTreeNode.GetText(analyzedMethod, Language); } + get { return Language.TypeToString(analyzedMethod.DeclaringType, true) + "." + MethodTreeNode.GetText(analyzedMethod, Language); } } public override void ActivateItem(System.Windows.RoutedEventArgs e) diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs index 72f43f6ba..28bdce99f 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs @@ -78,6 +78,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) { + string name = analyzedMethod.Name; + string declTypeName = analyzedMethod.DeclaringType.FullName; foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { ct.ThrowIfCancellationRequested(); foreach (MethodDefinition method in type.Methods) { @@ -86,8 +88,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer if (!method.HasBody) continue; foreach (Instruction instr in method.Body.Instructions) { - if (instr.Operand is MethodReference - && ((MethodReference)instr.Operand).Resolve() == analyzedMethod) { + MethodReference mr = instr.Operand as MethodReference; + if (mr != null && mr.Name == name && mr.DeclaringType.FullName == declTypeName && mr.Resolve() == analyzedMethod) { found = true; break; } From e2291940fb153a53160ef6ce6226cab368db83a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Thu, 24 Feb 2011 21:52:51 +0000 Subject: [PATCH 07/11] Improve control flow of conditions --- .../Ast/AstMethodBodyBuilder.cs | 19 ++----- .../ILAst/ILAstOptimizer.cs | 51 ++++++++++++++++++- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 8bd3385c6..6ea1a92d6 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -116,20 +116,11 @@ namespace Decompiler yield return (Statement)TransformExpression(ilLoop.PostLoopGoto); } else if (node is ILCondition) { ILCondition conditionalNode = (ILCondition)node; - if (conditionalNode.FalseBlock.Body.Any()) { - // Swap bodies - yield return new Ast.IfElseStatement { - Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, MakeBranchCondition(conditionalNode.Condition)), - TrueStatement = TransformBlock(conditionalNode.FalseBlock), - FalseStatement = TransformBlock(conditionalNode.TrueBlock) - }; - } else { - yield return new Ast.IfElseStatement { - Condition = MakeBranchCondition(conditionalNode.Condition), - TrueStatement = TransformBlock(conditionalNode.TrueBlock), - FalseStatement = TransformBlock(conditionalNode.FalseBlock) - }; - } + yield return new Ast.IfElseStatement { + Condition = MakeBranchCondition(conditionalNode.Condition), + TrueStatement = TransformBlock(conditionalNode.TrueBlock), + FalseStatement = TransformBlock(conditionalNode.FalseBlock) + }; } else if (node is ILSwitch) { ILSwitch ilSwitch = (ILSwitch)node; SwitchStatement switchStmt = new SwitchStatement() { Expression = (Expression)TransformExpression(ilSwitch.Condition.Arguments[0]) }; diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index e3d400770..a5b4f836e 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -314,7 +314,7 @@ namespace Decompiler.ControlFlow && node.DominanceFrontier.Contains(node) && (node != entryPoint || !excludeEntryPoint)) { - HashSet loopContents = FindDominatedNodes(scope, node); + HashSet loopContents = FindLoopContent(scope, node); ILWhileLoop loop = new ILWhileLoop(); @@ -484,6 +484,35 @@ namespace Decompiler.ControlFlow scope.ExceptWith(content); ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget)); } + + if (scope.Count == 0) { + // We have removed the whole scope - eliminte one of the condition bodies + int trueSize = ilCond.TrueBlock.GetSelfAndChildrenRecursive().Count(); + int falseSize = ilCond.FalseBlock.GetSelfAndChildrenRecursive().Count(); + + // The block are protected + Debug.Assert(ilCond.TrueBlock.EntryGoto != null); + Debug.Assert(ilCond.FalseBlock.EntryGoto != null); + + if (falseSize > trueSize) { + // Move the false body out + result.AddRange(ilCond.FalseBlock.Body); + ilCond.FalseBlock.Body.Clear(); + } else { + // Move the true body out + result.AddRange(ilCond.TrueBlock.Body); + ilCond.TrueBlock.Body.Clear(); + } + } + + // If true body is empty, swap bodies. + // Might happend because there was not any to start with or we moved it out. + if (ilCond.TrueBlock.Body.Count == 0) { + ILBlock tmp = ilCond.TrueBlock; + ilCond.TrueBlock = ilCond.FalseBlock; + ilCond.FalseBlock = tmp; + ilCond.Condition = new ILExpression(ILCode.LogicNot, null, ilCond.Condition); + } } } @@ -509,6 +538,26 @@ namespace Decompiler.ControlFlow } static HashSet FindDominatedNodes(HashSet scope, ControlFlowNode head) + { + HashSet agenda = new HashSet(); + HashSet result = new HashSet(); + agenda.Add(head); + + while(agenda.Count > 0) { + ControlFlowNode addNode = agenda.First(); + agenda.Remove(addNode); + + if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { + foreach (var successor in addNode.Successors) { + agenda.Add(successor); + } + } + } + + return result; + } + + static HashSet FindLoopContent(HashSet scope, ControlFlowNode head) { var exitNodes = head.DominanceFrontier.SelectMany(n => n.Predecessors); HashSet agenda = new HashSet(exitNodes); From 164691cf3352cb1b4350bd72b24c21e4e265fb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Thu, 24 Feb 2011 22:31:18 +0000 Subject: [PATCH 08/11] Further improvement in conditional control flow. --- ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index a5b4f836e..f2e64e438 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -507,7 +507,7 @@ namespace Decompiler.ControlFlow // If true body is empty, swap bodies. // Might happend because there was not any to start with or we moved it out. - if (ilCond.TrueBlock.Body.Count == 0) { + if (ilCond.TrueBlock.Body.Count == 0 && ilCond.FalseBlock.Body.Count > 0) { ILBlock tmp = ilCond.TrueBlock; ilCond.TrueBlock = ilCond.FalseBlock; ilCond.FalseBlock = tmp; From 9fab4e4ac0a5fddd52d051b00b0dd16b9fa1eac6 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 25 Feb 2011 16:37:56 +0100 Subject: [PATCH 09/11] Fix distinction between 'this' and 'base' in generic classes. --- .../Ast/AstMethodBodyBuilder.cs | 2 +- ICSharpCode.Decompiler/Tests/Generics.cs | 30 +++++++++++++++++++ .../Tests/ICSharpCode.Decompiler.Tests.csproj | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.Decompiler/Tests/Generics.cs diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 6ea1a92d6..8e996e3ea 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -638,7 +638,7 @@ namespace Decompiler } if (target is ThisReferenceExpression && !isVirtual) { // a non-virtual call on "this" might be a "base"-call. - if (cecilMethod.DeclaringType != methodDef.DeclaringType) { + if ((cecilMethod.DeclaringType.IsGenericInstance ? cecilMethod.DeclaringType.GetElementType() : cecilMethod.DeclaringType) != methodDef.DeclaringType) { // If we're not calling a method in the current class; we must be calling one in the base class. target = new BaseReferenceExpression(); } diff --git a/ICSharpCode.Decompiler/Tests/Generics.cs b/ICSharpCode.Decompiler/Tests/Generics.cs new file mode 100644 index 000000000..636eb708c --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/Generics.cs @@ -0,0 +1,30 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; + +public static class Generics +{ + class MyArray + { + private T[] arr; + + public MyArray(int capacity) + { + this.arr = new T[capacity]; + } + + public void Size(int capacity) + { + Array.Resize(ref this.arr, capacity); + } + + public void Grow(int capacity) + { + if (capacity >= this.arr.Length) + { + this.Size(capacity); + } + } + } +} diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index ba85e099c..e210d308b 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -51,6 +51,7 @@ + From db44cd1d3ba78672fa8db1088f11fe3290b74281 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 25 Feb 2011 16:39:39 +0100 Subject: [PATCH 10/11] Handle ldelem.any. Closes #47. --- ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs | 3 +-- ICSharpCode.Decompiler/ILAst/ILInlining.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 8e996e3ea..d08c4972a 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -349,9 +349,8 @@ namespace Decompiler case Code.Ldelem_R4: case Code.Ldelem_R8: case Code.Ldelem_Ref: - return arg1.Indexer(arg2); case Code.Ldelem_Any: - return InlineAssembly(byteCode, args); + return arg1.Indexer(arg2); case Code.Ldelema: return MakeRef(arg1.Indexer(arg2)); diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index 6337eb032..6ca8d90db 100644 --- a/ICSharpCode.Decompiler/ILAst/ILInlining.cs +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -50,7 +50,7 @@ namespace Decompiler { if (expr.Code != ILCode.Stloc) throw new ArgumentException("expr must be stloc"); - // ensure the variable is only accessed only a single time + // ensure the variable is accessed only a single time if (method.GetSelfAndChildrenRecursive().Count(e => e != expr && e.Operand == expr.Operand) != 1) return false; ILExpression parent; From 6b7e88e1227dc6c38f56cbd73fa6c5574c20137d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 25 Feb 2011 18:55:08 +0100 Subject: [PATCH 11/11] Add analyzer for fields. --- ILSpy/ILSpy.csproj | 3 + .../Analyzer/AnalyzedFieldAccessNode.cs | 109 ++++++++++++++++++ ILSpy/TreeNodes/Analyzer/AnalyzedFieldNode.cs | 45 ++++++++ .../Analyzer/AnalyzedMethodTreeNode.cs | 2 + .../Analyzer/AnalyzedMethodUsesNode.cs | 85 ++++++++++++++ ILSpy/TreeNodes/FieldTreeNode.cs | 28 ++++- 6 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedFieldNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsesNode.cs diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 0060256bd..8b3406c3a 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -133,8 +133,11 @@ + + + diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs new file mode 100644 index 000000000..cfad11418 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs @@ -0,0 +1,109 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +using ICSharpCode.NRefactory.Utils; +using ICSharpCode.TreeView; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzedFieldAccessNode : AnalyzerTreeNode + { + readonly bool showWrites; // true: show writes; false: show read access + readonly FieldDefinition analyzedField; + readonly ThreadingSupport threading; + + public AnalyzedFieldAccessNode(FieldDefinition analyzedField, bool showWrites) + { + if (analyzedField == null) + throw new ArgumentNullException("analyzedField"); + + this.analyzedField = analyzedField; + this.showWrites = showWrites; + this.threading = new ThreadingSupport(); + this.LazyLoading = true; + } + + public override object Text { + get { return showWrites ? "Assigned By" : "Read By"; } + } + + public override object Icon { + get { return Images.Search; } + } + + protected override void LoadChildren() + { + threading.LoadChildren(this, FetchChildren); + } + + protected override void OnCollapsing() + { + if (threading.IsRunning) { + this.LazyLoading = true; + threading.Cancel(); + this.Children.Clear(); + } + } + + IEnumerable FetchChildren(CancellationToken ct) + { + return FindReferences(MainWindow.Instance.AssemblyList.GetAssemblies(), ct); + } + + IEnumerable FindReferences(LoadedAssembly[] assemblies, CancellationToken ct) + { + // use parallelism only on the assembly level (avoid locks within Cecil) + return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + } + + IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + { + string name = analyzedField.Name; + string declTypeName = analyzedField.DeclaringType.FullName; + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (MethodDefinition method in type.Methods) { + ct.ThrowIfCancellationRequested(); + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + if (CanBeReference(instr.OpCode.Code)) { + FieldReference fr = instr.Operand as FieldReference; + if (fr != null && fr.Name == name && fr.DeclaringType.FullName == declTypeName && fr.Resolve() == analyzedField) { + found = true; + break; + } + } + } + if (found) + yield return new AnalyzedMethodTreeNode(method); + } + } + } + + bool CanBeReference(Code code) + { + switch (code) { + case Code.Ldfld: + case Code.Ldsfld: + return !showWrites; + case Code.Stfld: + case Code.Stsfld: + return showWrites; + case Code.Ldflda: + case Code.Ldsflda: + return true; // always show address-loading + default: + return false; + } + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedFieldNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldNode.cs new file mode 100644 index 000000000..ce15a16fa --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldNode.cs @@ -0,0 +1,45 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzedFieldNode : AnalyzerTreeNode + { + FieldDefinition analyzedField; + + public AnalyzedFieldNode(FieldDefinition analyzedField) + { + if (analyzedField == null) + throw new ArgumentNullException("analyzedField"); + this.analyzedField = analyzedField; + this.LazyLoading = true; + } + + public override object Icon { + get { return FieldTreeNode.GetIcon(analyzedField); } + } + + public override object Text { + get { + return Language.TypeToString(analyzedField.DeclaringType, true) + + "." + analyzedField.Name + " : " + this.Language.TypeToString(analyzedField.FieldType, false, analyzedField); + } + } + + public override void ActivateItem(System.Windows.RoutedEventArgs e) + { + e.Handled = true; + MainWindow.Instance.JumpToReference(analyzedField); + } + + protected override void LoadChildren() + { + this.Children.Add(new AnalyzedFieldAccessNode(analyzedField, false)); + if (!analyzedField.IsLiteral) + this.Children.Add(new AnalyzedFieldAccessNode(analyzedField, true)); + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs index 5287fee95..f577e4a85 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -49,6 +49,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer protected override void LoadChildren() { + if (analyzedMethod.HasBody) + this.Children.Add(new AnalyzedMethodUsesNode(analyzedMethod)); this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsesNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsesNode.cs new file mode 100644 index 000000000..65b1706c4 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsesNode.cs @@ -0,0 +1,85 @@ +// Copyright (c) 2011 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; +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + /// + /// Shows the methods that are used by this method. + /// + sealed class AnalyzedMethodUsesNode : AnalyzerTreeNode + { + MethodDefinition analyzedMethod; + + public AnalyzedMethodUsesNode(MethodDefinition analyzedMethod) + { + if (analyzedMethod == null) + throw new ArgumentNullException("analyzedMethod"); + + this.analyzedMethod = analyzedMethod; + this.LazyLoading = true; + } + + public override object Text { + get { return "Uses"; } + } + + public override object Icon { + get { return Images.Search; } + } + + protected override void LoadChildren() + { + foreach (var f in GetUsedFields().Distinct()) { + this.Children.Add(new AnalyzedFieldNode(f)); + } + foreach (var m in GetUsedMethods().Distinct()) { + this.Children.Add(new AnalyzedMethodTreeNode(m)); + } + } + + IEnumerable GetUsedMethods() + { + foreach (Instruction instr in analyzedMethod.Body.Instructions) { + MethodReference mr = instr.Operand as MethodReference; + if (mr != null) { + MethodDefinition def = mr.Resolve(); + if (def != null) + yield return def; + } + } + } + + IEnumerable GetUsedFields() + { + foreach (Instruction instr in analyzedMethod.Body.Instructions) { + FieldReference fr = instr.Operand as FieldReference; + if (fr != null) { + FieldDefinition def = fr.Resolve(); + if (def != null) + yield return def; + } + } + } + } +} diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs index 3838c0522..d3becb0ce 100644 --- a/ILSpy/TreeNodes/FieldTreeNode.cs +++ b/ILSpy/TreeNodes/FieldTreeNode.cs @@ -17,7 +17,9 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Windows.Controls; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.TreeNodes.Analyzer; using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes @@ -45,12 +47,15 @@ namespace ICSharpCode.ILSpy.TreeNodes } public override object Icon { - get { - if (field.IsLiteral) - return Images.Literal; - else - return Images.Field; - } + get { return GetIcon(field); } + } + + public static object GetIcon(FieldDefinition field) + { + if (field.IsLiteral) + return Images.Literal; + else + return Images.Field; } public override FilterResult Filter(FilterSettings settings) @@ -65,5 +70,16 @@ namespace ICSharpCode.ILSpy.TreeNodes { language.DecompileField(field, output, options); } + + public override System.Windows.Controls.ContextMenu GetContextMenu() + { + ContextMenu menu = new ContextMenu(); + MenuItem item = new MenuItem() { Header = "Analyze", Icon = new Image() { Source = Images.Search } }; + item.Click += delegate { MainWindow.Instance.AddToAnalyzer(new AnalyzedFieldNode(field)); }; + + menu.Items.Add(item); + + return menu; + } } }