diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 3eaca83ce..4e99f674b 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -148,6 +148,7 @@ + diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventFiredByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventFiredByTreeNode.cs new file mode 100644 index 000000000..02320cc09 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventFiredByTreeNode.cs @@ -0,0 +1,153 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using ICSharpCode.TreeView; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + internal sealed class AnalyzedEventFiredByTreeNode : AnalyzerTreeNode + { + private readonly EventDefinition analyzedEvent; + private readonly FieldDefinition eventBackingField; + private readonly MethodDefinition eventFiringMethod; + + private readonly ThreadingSupport threading; + private ConcurrentDictionary foundMethods; + + public AnalyzedEventFiredByTreeNode(EventDefinition analyzedEvent) + { + if (analyzedEvent == null) + throw new ArgumentNullException("analyzedEvent"); + + this.analyzedEvent = analyzedEvent; + this.threading = new ThreadingSupport(); + this.LazyLoading = true; + + this.eventBackingField = GetBackingField(analyzedEvent); + this.eventFiringMethod = analyzedEvent.EventType.Resolve().Methods.First(md => md.Name == "Invoke"); + } + + public override object Text + { + get { return "Raised 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(); + } + } + + private IEnumerable FetchChildren(CancellationToken ct) + { + foundMethods = new ConcurrentDictionary(); + + foreach (var child in FindReferencesInType(analyzedEvent.DeclaringType)) { + yield return child; + } + + foundMethods = null; + } + + private IEnumerable FindReferencesInType(TypeDefinition type) + { + // HACK: in lieu of proper flow analysis, I'm going to use a simple heuristic + // If the method accesses the event's backing field, and calls invoke on a delegate + // with the same signature, then it is (most likely) raise the given event. + + foreach (MethodDefinition method in type.Methods) { + bool readBackingField = false; + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + Code code = instr.OpCode.Code; + if (code == Code.Ldfld || code == Code.Ldflda) { + FieldReference fr = instr.Operand as FieldReference; + if (fr != null && fr.Name == eventBackingField.Name && fr == eventBackingField) { + readBackingField = true; + } + } + if (readBackingField && (code == Code.Callvirt || code == Code.Call)) { + MethodReference mr = instr.Operand as MethodReference; + if (mr != null && mr.Name == eventFiringMethod.Name && mr.Resolve() == eventFiringMethod) { + found = true; + break; + } + } + } + + method.Body = null; + + if (found) { + MethodDefinition codeLocation = this.Language.GetOriginalCodeLocation(method) as MethodDefinition; + if (codeLocation != null && !HasAlreadyBeenFound(codeLocation)) { + yield return new AnalyzedMethodTreeNode(codeLocation); + } + } + } + } + + private bool HasAlreadyBeenFound(MethodDefinition method) + { + return !foundMethods.TryAdd(method, 0); + } + + // HACK: we should probably examine add/remove methods to determine this + private static FieldDefinition GetBackingField(EventDefinition ev) + { + var fieldName = ev.Name; + var vbStyleFieldName = fieldName + "Event"; + var fieldType = ev.EventType; + + foreach (var fd in ev.DeclaringType.Fields) { + if (fd.Name == fieldName || fd.Name == vbStyleFieldName) + if (fd.FieldType.FullName == fieldType.FullName) + return fd; + } + + return null; + } + + + public static bool CanShow(EventDefinition ev) + { + return GetBackingField(ev) != null; + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs index 89903abd4..84a937942 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs @@ -59,13 +59,19 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { if (analyzedEvent.AddMethod != null) this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.AddMethod, "add")); + if (analyzedEvent.RemoveMethod != null) this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.RemoveMethod, "remove")); + foreach (var accessor in analyzedEvent.OtherMethods) this.Children.Add(new AnalyzedEventAccessorTreeNode(accessor, null)); + if (AnalyzedEventFiredByTreeNode.CanShow(analyzedEvent)) + this.Children.Add(new AnalyzedEventFiredByTreeNode(analyzedEvent)); + if (AnalyzedEventOverridesTreeNode.CanShow(analyzedEvent)) this.Children.Add(new AnalyzedEventOverridesTreeNode(analyzedEvent)); + if (AnalyzedInterfaceEventImplementedByTreeNode.CanShow(analyzedEvent)) this.Children.Add(new AnalyzedInterfaceEventImplementedByTreeNode(analyzedEvent)); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs index 993ef36d7..26f5e4c65 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs @@ -89,7 +89,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer var BaseMethods = TypesHierarchyHelpers.FindBaseMethods(analyzedMethod).ToArray(); if (BaseMethods.Length > 0) { baseMethod = BaseMethods[BaseMethods.Length - 1]; - } + } else + baseMethod = analyzedMethod; possibleTypes = new List(); @@ -119,15 +120,24 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer MethodReference mr = instr.Operand as MethodReference; if (mr != null && mr.Name == name) { // explicit call to the requested method - if (Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == analyzedMethod) { + if (instr.OpCode.Code == Code.Call + && Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) + && mr.Resolve() == analyzedMethod) { found = true; prefix = "(as base) "; break; } // virtual call to base method - if (instr.OpCode.Code == Code.Callvirt && Helpers.IsReferencedBy(baseMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == baseMethod) { - found = true; - break; + if (instr.OpCode.Code == Code.Callvirt) { + MethodDefinition md = mr.Resolve(); + if (md == null) { + // cannot resolve the operand, so ignore this method + break; + } + if (md == baseMethod) { + found = true; + break; + } } } } diff --git a/ILSpy/TreeNodes/Analyzer/Helpers.cs b/ILSpy/TreeNodes/Analyzer/Helpers.cs index bef4e486a..b773ad875 100644 --- a/ILSpy/TreeNodes/Analyzer/Helpers.cs +++ b/ILSpy/TreeNodes/Analyzer/Helpers.cs @@ -67,11 +67,11 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer return FindMethodUsageInType(method.DeclaringType, method) ?? method; } - var typeUsage = GetOriginalCodeLocation(method.DeclaringType, method); + var typeUsage = GetOriginalCodeLocation(method.DeclaringType); return typeUsage ?? method; } - public static MethodDefinition GetOriginalCodeLocation(TypeDefinition type, MethodDefinition method) + public static MethodDefinition GetOriginalCodeLocation(TypeDefinition type) { if (type != null && type.DeclaringType != null && type.IsCompilerGenerated()) { MethodDefinition constructor = GetTypeConstructor(type);