diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 9532d0365..0a02dd876 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -18,6 +18,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -156,5 +157,16 @@ namespace ICSharpCode.Decompiler.TypeSystem { return compilation.FindType(attrType.GetTypeName()); } + + public static KnownAttribute IsKnownAttributeType(this ITypeDefinition attributeType) + { + if (!attributeType.GetNonInterfaceBaseTypes().Any(t => t.IsKnownType(KnownTypeCode.Attribute))) + return KnownAttribute.None; + for (int i = 1; i < typeNames.Length; i++) { + if (typeNames[i] == attributeType.FullTypeName) + return (KnownAttribute)i; + } + return KnownAttribute.None; + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index ba6e4fe47..070081d8f 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -525,5 +525,10 @@ namespace ICSharpCode.Decompiler.TypeSystem { return method.Name == name && method.DeclaringType?.FullName == type; } + + public static KnownAttribute IsBuiltinAttribute(this ITypeDefinition type) + { + return KnownAttributes.IsKnownAttributeType(type); + } } } diff --git a/ILSpy/Analyzers/Builtin/AttributeAppliedToAnalyzer.cs b/ILSpy/Analyzers/Builtin/AttributeAppliedToAnalyzer.cs index 462c741bb..4af25024e 100644 --- a/ILSpy/Analyzers/Builtin/AttributeAppliedToAnalyzer.cs +++ b/ILSpy/Analyzers/Builtin/AttributeAppliedToAnalyzer.cs @@ -18,10 +18,11 @@ using System; using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Reflection.Metadata; +using System.Runtime.InteropServices; +using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.ILSpy.Analyzers.Builtin @@ -31,10 +32,235 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin { public IEnumerable Analyze(ISymbol analyzedSymbol, AnalyzerContext context) { - if (!(analyzedSymbol is IEntity attributeEntity)) - yield break; + if (!(analyzedSymbol is ITypeDefinition attributeType)) + return Array.Empty(); - var scope = context.GetScopeOf(attributeEntity); + var scope = context.GetScopeOf(attributeType); + // TODO: The IndexerNameAttribute needs special support, because it is not available on IL level. Do we want to support it? + // TODO: DeclSecurity attributes are not supported. + if (!IsBuiltinAttribute(attributeType, out var knownAttribute)) { + return HandleCustomAttribute(attributeType, scope); + } else { + return HandleBuiltinAttribute(knownAttribute, scope).SelectMany(s => s); + } + } + + bool IsBuiltinAttribute(ITypeDefinition attributeType, out KnownAttribute knownAttribute) + { + knownAttribute = attributeType.IsBuiltinAttribute(); + switch (knownAttribute) { + case KnownAttribute.Serializable: + case KnownAttribute.ComImport: + case KnownAttribute.StructLayout: + case KnownAttribute.DllImport: + case KnownAttribute.PreserveSig: + case KnownAttribute.MethodImpl: + case KnownAttribute.FieldOffset: + case KnownAttribute.NonSerialized: + case KnownAttribute.MarshalAs: + case KnownAttribute.PermissionSet: + case KnownAttribute.Optional: + case KnownAttribute.In: + case KnownAttribute.Out: + return true; + default: + return false; + } + } + + IEnumerable> HandleBuiltinAttribute(KnownAttribute attributeType, AnalyzerScope scope) + { + // For built-in attributes (i.e. metadata flags) we have to perform the same checks + // as implemented in the GetAttributes methods of MetadataField, MetadataMethod, MetadataParameter, etc. + foreach (Decompiler.Metadata.PEFile module in scope.GetAllModules()) { + var ts = new DecompilerTypeSystem(module, module.GetAssemblyResolver()); + + switch (attributeType) { + case KnownAttribute.Serializable: + yield return ScanDefinitions(ts, TypeAttributes.Serializable); + break; + case KnownAttribute.ComImport: + yield return ScanDefinitions(ts, TypeAttributes.Import); + break; + case KnownAttribute.StructLayout: + yield return ScanDefinitions(ts, matcher: HasStructLayout); + break; + case KnownAttribute.DllImport: + yield return ScanDefinitions(ts, matcher: IsPinvokeImpl); + break; + case KnownAttribute.PreserveSig: + yield return ScanDefinitions(ts, matcher: (mod, m) => !IsPinvokeImpl(mod, m) && (m.ImplAttributes & ~MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.PreserveSig); + break; + case KnownAttribute.MethodImpl: + yield return ScanDefinitions(ts, matcher: HasMethodImplOptions); + break; + case KnownAttribute.FieldOffset: + yield return ScanDefinitions(ts, matcher: (mod, f) => f.GetOffset() != -1); + break; + case KnownAttribute.NonSerialized: + yield return ScanDefinitions(ts, FieldAttributes.NotSerialized); + break; + case KnownAttribute.MarshalAs: + yield return ScanDefinitions(ts, matcher: (mod, f) => !f.GetMarshallingDescriptor().IsNil); + yield return ScanParameters(ts, matcher: (mod, _, p) => !p.GetMarshallingDescriptor().IsNil); + break; + case KnownAttribute.Optional: + yield return ScanParameters(ts, matcher: (mod, m, p) => (p.Attributes & ParameterAttributes.Optional) != 0 && !CheckTSParamFlag(ts.MainModule, m, tsp => tsp.HasConstantValueInSignature)); + break; + case KnownAttribute.In: + yield return ScanParameters(ts, matcher: (mod, m, p) => (p.Attributes & ParameterAttributes.In) == ParameterAttributes.In && CheckTSParamFlag(ts.MainModule, m, tsp => tsp.HasAttribute(KnownAttribute.In))); + break; + case KnownAttribute.Out: + yield return ScanParameters(ts, matcher: (mod, m, p) => (p.Attributes & ParameterAttributes.Out) == ParameterAttributes.Out && CheckTSParamFlag(ts.MainModule, m, tsp => tsp.HasAttribute(KnownAttribute.Out))); + break; + } + } + } + + private bool CheckTSParamFlag(MetadataModule module, MethodDefinitionHandle h, Func matcher) + { + var m = module.GetDefinition(h); + return m.Parameters.Any(matcher); + } + + private bool HasMethodImplOptions(Decompiler.Metadata.PEFile module, MethodDefinition m) + { + var implOptions = m.ImplAttributes & ~MethodImplAttributes.CodeTypeMask; + if (IsPinvokeImpl(module, m)) { + implOptions &= ~MethodImplAttributes.PreserveSig; + } + if (implOptions == MethodImplAttributes.PreserveSig) + return false; + return implOptions != 0; + } + + private bool IsPinvokeImpl(Decompiler.Metadata.PEFile module, MethodDefinition m) + { + var info = m.GetImport(); + return (m.Attributes & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl && !info.Module.IsNil; + } + + private bool HasStructLayout(Decompiler.Metadata.PEFile module, TypeDefinition t) + { + LayoutKind layoutKind = LayoutKind.Auto; + switch (t.Attributes & TypeAttributes.LayoutMask) { + case TypeAttributes.SequentialLayout: + layoutKind = LayoutKind.Sequential; + break; + case TypeAttributes.ExplicitLayout: + layoutKind = LayoutKind.Explicit; + break; + } + CharSet charSet = CharSet.None; + switch (t.Attributes & TypeAttributes.StringFormatMask) { + case TypeAttributes.AnsiClass: + charSet = CharSet.Ansi; + break; + case TypeAttributes.AutoClass: + charSet = CharSet.Auto; + break; + case TypeAttributes.UnicodeClass: + charSet = CharSet.Unicode; + break; + } + var defaultLayoutKind = t.IsValueType(module.Metadata) && !t.IsEnum(module.Metadata) ? LayoutKind.Sequential : LayoutKind.Auto; + var layout = t.GetLayout(); + return layoutKind != defaultLayoutKind || charSet != CharSet.Ansi || layout.PackingSize > 0 || layout.Size > 0; + } + + IEnumerable ScanDefinitions(DecompilerTypeSystem ts, TypeAttributes attribute = 0, Func matcher = null) + { + var module = ts.MainModule.PEFile; + foreach (var h in module.Metadata.TypeDefinitions) { + var t = module.Metadata.GetTypeDefinition(h); + if (matcher != null) { + if (!matcher(module, t)) continue; + } else { + if ((t.Attributes & attribute) == 0) continue; + } + yield return ts.MainModule.GetDefinition(h); + } + } + + IEnumerable ScanDefinitions(DecompilerTypeSystem ts, MethodAttributes attribute = 0, Func matcher = null) + { + var module = ts.MainModule.PEFile; + foreach (var h in module.Metadata.MethodDefinitions) { + var m = module.Metadata.GetMethodDefinition(h); + if (matcher != null) { + if (!matcher(module, m)) continue; + } else { + if ((m.Attributes & attribute) == 0) continue; + } + yield return ts.MainModule.GetDefinition(h); + } + } + + IEnumerable ScanDefinitions(DecompilerTypeSystem ts, FieldAttributes attribute = 0, Func matcher = null) + { + var module = ts.MainModule.PEFile; + foreach (var h in module.Metadata.FieldDefinitions) { + var f = module.Metadata.GetFieldDefinition(h); + if (matcher != null) { + if (!matcher(module, f)) continue; + } else { + if ((f.Attributes & attribute) == 0) continue; + } + yield return ts.MainModule.GetDefinition(h); + } + } + + IEnumerable ScanDefinitions(DecompilerTypeSystem ts, PropertyAttributes attribute = 0, Func matcher = null) + { + var module = ts.MainModule.PEFile; + foreach (var h in module.Metadata.PropertyDefinitions) { + var p = module.Metadata.GetPropertyDefinition(h); + if (matcher != null) { + if (!matcher(module, p)) continue; + } else { + if ((p.Attributes & attribute) == 0) continue; + } + yield return ts.MainModule.GetDefinition(h); + } + } + + IEnumerable ScanDefinitions(DecompilerTypeSystem ts, EventAttributes attribute) + { + var module = ts.MainModule.PEFile; + foreach (var h in module.Metadata.EventDefinitions) { + var e = module.Metadata.GetEventDefinition(h); + if ((e.Attributes & attribute) == 0) continue; + yield return ts.MainModule.GetDefinition(h); + } + } + + IEnumerable ScanParameters(DecompilerTypeSystem ts, ParameterAttributes attribute = 0, Func matcher = null) + { + var module = ts.MainModule.PEFile; + var genericContext = new GenericContext(); + foreach (var h in module.Metadata.MethodDefinitions) { + var m = module.Metadata.GetMethodDefinition(h); + foreach (var ph in m.GetParameters()) { + var p = module.Metadata.GetParameter(ph); + if (matcher != null) { + if (!matcher(module, h, p)) continue; + } else { + if ((p.Attributes & attribute) == 0) continue; + } + var method = ts.MainModule.ResolveMethod(h, genericContext); + if (method != null) { + if (method.IsAccessor) + yield return method.AccessorOwner; + else + yield return method; + } + break; + } + } + } + + IEnumerable HandleCustomAttribute(ITypeDefinition attributeType, AnalyzerScope scope) + { var genericContext = new GenericContext(); // type arguments do not matter for this analyzer. foreach (var module in scope.GetAllModules()) { @@ -44,8 +270,8 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin var customAttribute = module.Metadata.GetCustomAttribute(h); var attributeCtor = ts.MainModule.ResolveMethod(customAttribute.Constructor, genericContext); if (attributeCtor.DeclaringTypeDefinition != null - && attributeCtor.ParentModule.PEFile == attributeEntity.ParentModule.PEFile - && attributeCtor.DeclaringTypeDefinition.MetadataToken == attributeEntity.MetadataToken) { + && attributeCtor.ParentModule.PEFile == attributeType.ParentModule.PEFile + && attributeCtor.DeclaringTypeDefinition.MetadataToken == attributeType.MetadataToken) { if (customAttribute.Parent.Kind == HandleKind.Parameter) { referencedParameters.Add((ParameterHandle)customAttribute.Parent); } else {