From 2cc7bd3ad8cdd46865913aa19b97d15ee34e4fb2 Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Sun, 18 Dec 2011 18:53:00 +1100 Subject: [PATCH] Analyzer: added Attribute 'applied to' analysis. --- ILSpy/ExtensionMethods.cs | 15 + ILSpy/ILSpy.csproj | 2 + .../Analyzer/AnalyzedAssemblyTreeNode.cs | 59 +++ .../AnalyzedAttributeAppliedToTreeNode.cs | 369 ++++++++++++++++++ .../Analyzer/AnalyzedTypeTreeNode.cs | 3 + 5 files changed, 448 insertions(+) create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedAssemblyTreeNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedAttributeAppliedToTreeNode.cs diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 7d14edc55..52cc0d125 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -18,6 +18,8 @@ using System; using System.Collections.Generic; +using ICSharpCode.Decompiler; +using Mono.Cecil; namespace ICSharpCode.ILSpy { @@ -32,5 +34,18 @@ namespace ICSharpCode.ILSpy if (!list.Contains(item)) list.Add(item); } + + public static bool IsCustomAttribute(this TypeDefinition type) + { + while (type.FullName != "System.Object") { + var resolvedBaseType = type.BaseType.Resolve(); + if (resolvedBaseType == null) + return false; + if (resolvedBaseType.FullName == "System.Attribute") + return true; + type = resolvedBaseType; + } + return false; + } } } diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index a79b21d30..bbae163c5 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -175,6 +175,8 @@ + + diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedAssemblyTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedAssemblyTreeNode.cs new file mode 100644 index 000000000..ce3476a29 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedAssemblyTreeNode.cs @@ -0,0 +1,59 @@ +// 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 +{ + internal class AnalyzedAssemblyTreeNode : AnalyzerEntityTreeNode + { + private readonly AssemblyDefinition analyzedAssembly; + + public AnalyzedAssemblyTreeNode(AssemblyDefinition analyzedAssembly) + { + if (analyzedAssembly == null) + throw new ArgumentNullException("analyzedAssembly"); + this.analyzedAssembly = analyzedAssembly; + //this.LazyLoading = true; + } + + public override object Icon + { + get { return Images.Assembly; } + } + + public override object Text + { + get + { + return analyzedAssembly.Name.Name; + } + } + + protected override void LoadChildren() + { + //this.Children.Add(new AnalyzedAssemblyReferencedByTreeNode(analyzedAssembly)); + } + + public override MemberReference Member + { + get { return null; } + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedAttributeAppliedToTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedAttributeAppliedToTreeNode.cs new file mode 100644 index 000000000..2849b1d4e --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedAttributeAppliedToTreeNode.cs @@ -0,0 +1,369 @@ +// 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 System.Threading; +using ICSharpCode.Decompiler.Ast; +using Mono.Cecil; +using Mono.Cecil.Cil; +using ICSharpCode.NRefactory.Utils; +using System.Collections.Concurrent; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + internal sealed class AnalyzedAttributeAppliedToTreeNode : AnalyzerSearchTreeNode + { + private readonly TypeDefinition analyzedType; + private readonly string attributeName; + + private AttributeTargets usage = AttributeTargets.All; + private bool allowMutiple; + private bool inherited = true; + private ConcurrentDictionary foundMethods; + + public static bool CanShow(TypeDefinition type) + { + return type.IsClass && type.IsCustomAttribute(); + } + + public AnalyzedAttributeAppliedToTreeNode(TypeDefinition analyzedType) + { + if (analyzedType == null) + throw new ArgumentNullException("analyzedType"); + + this.analyzedType = analyzedType; + attributeName = this.analyzedType.FullName; + GetAttributeUsage(); + } + + private void GetAttributeUsage() + { + if (analyzedType.HasCustomAttributes) { + foreach (CustomAttribute ca in analyzedType.CustomAttributes) { + TypeReference t = ca.AttributeType; + if (t.Name == "AttributeUsageAttribute" && t.Namespace == "System") { + this.usage = (AttributeTargets)ca.ConstructorArguments[0].Value; + if (ca.ConstructorArguments.Count > 1) { + this.allowMutiple = (bool)ca.ConstructorArguments[1].Value; + this.inherited = (bool)ca.ConstructorArguments[2].Value; + } + if (ca.HasProperties) { + foreach (var namedArgument in ca.Properties) { + switch (namedArgument.Name) { + case "AllowMultiple": + this.allowMutiple = (bool)namedArgument.Argument.Value; + break; + case "Inherited": + this.inherited = (bool)namedArgument.Argument.Value; + break; + } + } + } + } + } + } + } + + public override object Text + { + get { return "Applied To"; } + } + + protected override IEnumerable FetchChildren(CancellationToken ct) + { + foundMethods = new ConcurrentDictionary(); + + //get the assemblies to search + var currentAssembly = analyzedType.Module.Assembly; + var assemblies = analyzedType.IsPublic ? GetReferencingAssemblies(currentAssembly, ct) : GetAssemblyAndAnyFriends(currentAssembly, ct); + + var results = assemblies.AsParallel().WithCancellation(ct).SelectMany(a => FindReferencesInAssembly(a.Item1, a.Item2, ct)); + + foreach (var result in results.OrderBy(n => n.Text)) { + yield return result; + } + + foundMethods = null; + } + + #region standard custom attributes + + private IEnumerable FindReferencesInAssembly(AssemblyDefinition asm, TypeReference tr, CancellationToken ct) + { + //since we do not display modules as separate entities, coalesce the assembly and module searches + bool foundInAssyOrModule = false; + + if ((usage & AttributeTargets.Assembly) != 0) { + if (asm.HasCustomAttributes) { + foreach (var attribute in asm.CustomAttributes) { + if (attribute.AttributeType == tr) { + foundInAssyOrModule = true; + break; + } + } + } + } + + if (!foundInAssyOrModule) { + ct.ThrowIfCancellationRequested(); + + //search module + if ((usage & AttributeTargets.Module) != 0) { + foreach (var module in asm.Modules) { + if (module.HasCustomAttributes) { + foreach (var attribute in module.CustomAttributes) { + if (attribute.AttributeType == tr) { + foundInAssyOrModule = true; + break; + } + } + } + } + } + + } + + if (foundInAssyOrModule) { + yield return new AnalyzedAssemblyTreeNode(asm); + } + + ct.ThrowIfCancellationRequested(); + + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.MainModule.Types, t => t.NestedTypes).OrderBy(t => t.FullName)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in FindReferencesWithinInType(type, tr)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + + private IEnumerable FindReferencesWithinInType(TypeDefinition type, TypeReference attrTypeRef) + { + + bool searchRequired = (type.IsClass && usage.HasFlag(AttributeTargets.Class)) + || (type.IsEnum && usage.HasFlag(AttributeTargets.Enum)) + || (type.IsInterface && usage.HasFlag(AttributeTargets.Interface)) + || (type.IsValueType && usage.HasFlag(AttributeTargets.Struct)); + if (searchRequired) { + if (type.HasCustomAttributes) { + foreach (var attribute in type.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + var node = new AnalyzedTypeTreeNode(type); + node.Language = this.Language; + yield return node; + break; + } + } + } + } + + if ((this.usage & AttributeTargets.GenericParameter) != 0 && type.HasGenericParameters) { + foreach (var parameter in type.GenericParameters) { + if (parameter.HasCustomAttributes) { + foreach (var attribute in parameter.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + var node = new AnalyzedTypeTreeNode(type); + node.Language = this.Language; + yield return node; + break; + } + } + } + } + } + + if ((this.usage & AttributeTargets.Field) != 0 && type.HasFields) { + foreach (var field in type.Fields) { + if (field.HasCustomAttributes) { + foreach (var attribute in field.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + var node = new AnalyzedFieldTreeNode(field); + node.Language = this.Language; + yield return node; + break; + } + } + } + } + } + + if (((usage & AttributeTargets.Property) != 0) && type.HasProperties) { + foreach (var property in type.Properties) { + if (property.HasCustomAttributes) { + foreach (var attribute in property.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + var node = new AnalyzedPropertyTreeNode(property); + node.Language = this.Language; + yield return node; + break; + } + } + } + } + } + if (((usage & AttributeTargets.Event) != 0) && type.HasEvents) { + foreach (var _event in type.Events) { + if (_event.HasCustomAttributes) { + foreach (var attribute in _event.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + var node = new AnalyzedEventTreeNode(_event); + node.Language = this.Language; + yield return node; + break; + } + } + } + } + } + + if (type.HasMethods) { + foreach (var method in type.Methods) { + bool found = false; + if ((usage & (AttributeTargets.Method | AttributeTargets.Constructor)) != 0) { + if (method.HasCustomAttributes) { + foreach (var attribute in method.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + found = true; + break; + } + } + } + } + if (!found && + ((usage & AttributeTargets.ReturnValue) != 0) && + method.MethodReturnType.HasCustomAttributes) { + foreach (var attribute in method.MethodReturnType.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + found = true; + break; + } + } + } + + if (!found && + ((usage & AttributeTargets.Parameter) != 0) && + method.HasParameters) { + foreach (var parameter in method.Parameters) { + foreach (var attribute in parameter.CustomAttributes) { + if (attribute.AttributeType == attrTypeRef) { + found = true; + break; + } + } + } + } + + if (found) { + MethodDefinition codeLocation = this.Language.GetOriginalCodeLocation(method) as MethodDefinition; + if (codeLocation != null && !HasAlreadyBeenFound(codeLocation)) { + var node = new AnalyzedMethodTreeNode(codeLocation); + node.Language = this.Language; + yield return node; + } + } + } + } + } + + private bool HasAlreadyBeenFound(MethodDefinition method) + { + return !foundMethods.TryAdd(method, 0); + } + + #endregion + + #region search scope + + private IEnumerable> GetReferencingAssemblies(AssemblyDefinition asm, CancellationToken ct) + { + yield return new Tuple(asm, this.analyzedType); + + string requiredAssemblyFullName = asm.FullName; + + IEnumerable assemblies = MainWindow.Instance.CurrentAssemblyList.GetAssemblies().Where(assy => assy.AssemblyDefinition != null); + + foreach (var assembly in assemblies) { + ct.ThrowIfCancellationRequested(); + bool found = false; + foreach (var reference in assembly.AssemblyDefinition.MainModule.AssemblyReferences) { + if (requiredAssemblyFullName == reference.FullName) { + found = true; + break; + } + } + if (found) { + var typeref = GetScopeTypeReferenceInAssembly(assembly.AssemblyDefinition); + if (typeref != null) + yield return new Tuple(assembly.AssemblyDefinition, typeref); + } + } + } + + private IEnumerable> GetAssemblyAndAnyFriends(AssemblyDefinition asm, CancellationToken ct) + { + yield return new Tuple(asm, analyzedType); + + if (asm.HasCustomAttributes) { + var attributes = asm.CustomAttributes + .Where(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.InternalsVisibleToAttribute"); + var friendAssemblies = new HashSet(); + foreach (var attribute in attributes) { + string assemblyName = attribute.ConstructorArguments[0].Value as string; + assemblyName = assemblyName.Split(',')[0]; // strip off any public key info + friendAssemblies.Add(assemblyName); + } + + if (friendAssemblies.Count > 0) { + IEnumerable assemblies = MainWindow.Instance.CurrentAssemblyList.GetAssemblies(); + + foreach (var assembly in assemblies) { + ct.ThrowIfCancellationRequested(); + if (friendAssemblies.Contains(assembly.ShortName)) { + var typeref = GetScopeTypeReferenceInAssembly(assembly.AssemblyDefinition); + if (typeref != null) { + yield return new Tuple(assembly.AssemblyDefinition, typeref); + } + } + } + } + } + } + + private TypeReference GetScopeTypeReferenceInAssembly(AssemblyDefinition asm) + { + foreach (var typeref in asm.MainModule.GetTypeReferences()) { + if (typeref.Name == analyzedType.Name && typeref.Namespace == analyzedType.Namespace) { + return typeref; + } + } + return null; + } + + #endregion + } + internal static class ExtensionMethods + { + public static bool HasCustomAttribute(this MemberReference member, string attributeTypeName) + { + return false; + } + } +} \ No newline at end of file diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeTreeNode.cs index 4e3f623f0..ab9b8bd5f 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeTreeNode.cs @@ -48,6 +48,9 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer protected override void LoadChildren() { + if (AnalyzedAttributeAppliedToTreeNode.CanShow(analyzedType)) + this.Children.Add(new AnalyzedAttributeAppliedToTreeNode(analyzedType)); + if (AnalyzedTypeInstantiationsTreeNode.CanShow(analyzedType)) this.Children.Add(new AnalyzedTypeInstantiationsTreeNode(analyzedType));