// Copyright (c) 2018 Daniel Grunwald // // 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 SRM = System.Reflection.Metadata; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; using static ICSharpCode.Decompiler.Metadata.MetadataExtensions; using System.Diagnostics; using System.Collections.Immutable; namespace ICSharpCode.Decompiler.TypeSystem { /// /// Options that control how metadata is represented in the type system. /// [Flags] public enum TypeSystemOptions { /// /// No options enabled; stay as close to the metadata as possible. /// None = 0, /// /// [DynamicAttribute] is used to replace 'object' types with the 'dynamic' type. /// /// If this option is not active, the 'dynamic' type is not used, and the attribute is preserved. /// Dynamic = 1, /// /// Tuple types are represented using the TupleType class. /// [TupleElementNames] is used to name the tuple elements. /// /// If this option is not active, the tuples are represented using their underlying type, and the attribute is preserved. /// Tuple = 2, /// /// If this option is active, [ExtensionAttribute] is removed and methods are marked as IsExtensionMethod. /// Otherwise, the attribute is preserved but the methods are not marked. /// ExtensionMethods = 4, /// /// Default settings: all features enabled. /// Default = Dynamic | Tuple | ExtensionMethods } /// /// Manages the NRefactory type system for the decompiler. /// /// /// This class is thread-safe. /// public class DecompilerTypeSystem : IDecompilerTypeSystem { readonly Metadata.PEFile moduleDefinition; readonly ICompilation compilation; readonly ITypeResolveContext context; readonly TypeSystemOptions typeSystemOptions; readonly MetadataAssembly mainAssembly; Dictionary fieldLookupCache = new Dictionary(); Dictionary propertyLookupCache = new Dictionary(); Dictionary methodLookupCache = new Dictionary(); Dictionary eventLookupCache = new Dictionary(); Dictionary moduleLookup = new Dictionary(); public DecompilerTypeSystem(Metadata.PEFile moduleDefinition) : this(moduleDefinition, new DecompilerSettings()) { } public DecompilerTypeSystem(Metadata.PEFile moduleDefinition, DecompilerSettings settings) { if (settings == null) throw new ArgumentNullException(nameof(settings)); this.moduleDefinition = moduleDefinition; typeSystemOptions = TypeSystemOptions.None; if (settings.Dynamic) typeSystemOptions |= TypeSystemOptions.Dynamic; if (settings.TupleTypes) typeSystemOptions |= TypeSystemOptions.Tuple; if (settings.ExtensionMethods) typeSystemOptions |= TypeSystemOptions.ExtensionMethods; MetadataLoader loader = new MetadataLoader { IncludeInternalMembers = true, ShortenInterfaceImplNames = false, Options = typeSystemOptions, }; IUnresolvedAssembly mainAssembly = loader.LoadModule(moduleDefinition); // Load referenced assemblies and type-forwarder references. // This is necessary to make .NET Core/PCL binaries work better. var referencedAssemblies = new List(); var assemblyReferenceQueue = new Queue(moduleDefinition.AssemblyReferences); var processedAssemblyReferences = new HashSet(KeyComparer.Create((Metadata.AssemblyReference reference) => reference.FullName)); while (assemblyReferenceQueue.Count > 0) { var asmRef = assemblyReferenceQueue.Dequeue(); if (!processedAssemblyReferences.Add(asmRef)) continue; var asm = moduleDefinition.AssemblyResolver.Resolve(asmRef); if (asm != null) { IUnresolvedAssembly unresolvedAsm = loader.LoadModule(asm); referencedAssemblies.Add(unresolvedAsm); moduleLookup.Add(unresolvedAsm, asm); var metadata = asm.Metadata; foreach (var h in metadata.ExportedTypes) { var forwarder = metadata.GetExportedType(h); if (!forwarder.IsForwarder || forwarder.Implementation.Kind != SRM.HandleKind.AssemblyReference) continue; assemblyReferenceQueue.Enqueue(new Metadata.AssemblyReference(asm, (SRM.AssemblyReferenceHandle)forwarder.Implementation)); } } } compilation = new SimpleCompilation(mainAssembly, referencedAssemblies); // Primitive types are necessary to avoid assertions in ILReader. // Fallback to MinimalCorlib to provide the primitive types. if (compilation.FindType(KnownTypeCode.Void).Kind == TypeKind.Unknown || compilation.FindType(KnownTypeCode.Int32).Kind == TypeKind.Unknown) { referencedAssemblies.Add(MinimalCorlib.Instance); compilation = new SimpleCompilation(mainAssembly, referencedAssemblies); } context = new SimpleTypeResolveContext(compilation.MainAssembly); } public ICompilation Compilation { get { return compilation; } } public MetadataAssembly MainAssembly { get { return mainAssembly; } } public Metadata.PEFile ModuleDefinition { get { return moduleDefinition; } } public SRM.MetadataReader GetMetadata() => moduleDefinition.Metadata; public Metadata.PEFile GetModuleDefinition(IAssembly assembly) { if (assembly is MetadataAssembly asm) { return asm.PEFile; } return null; } public IMember ResolveAsMember(SRM.EntityHandle memberReference) { switch (memberReference.Kind) { case SRM.HandleKind.FieldDefinition: return ResolveAsField(memberReference); case SRM.HandleKind.MethodDefinition: return ResolveAsMethod(memberReference); case SRM.HandleKind.MemberReference: var mr = moduleDefinition.Metadata.GetMemberReference((SRM.MemberReferenceHandle)memberReference); switch (mr.GetKind()) { case SRM.MemberReferenceKind.Method: return ResolveAsMethod(memberReference); case SRM.MemberReferenceKind.Field: return ResolveAsField(memberReference); } throw new NotSupportedException(); case SRM.HandleKind.EventDefinition: return ResolveAsEvent(memberReference); case SRM.HandleKind.PropertyDefinition: return ResolveAsProperty(memberReference); case SRM.HandleKind.MethodSpecification: return ResolveAsMethod(memberReference); default: throw new NotSupportedException(); } } #region Resolve Type public IType ResolveAsType(SRM.EntityHandle typeReference) { return MetadataTypeReference.Resolve( typeReference, moduleDefinition.Metadata, mainAssembly.TypeProvider, new GenericContext(), typeSystemOptions ); } IType ResolveDeclaringType(SRM.EntityHandle declaringTypeReference) { // resolve without substituting dynamic/tuple types return MetadataTypeReference.Resolve( declaringTypeReference, moduleDefinition.Metadata, mainAssembly.TypeProvider, new GenericContext(context), typeSystemOptions & ~(TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple) ); } #endregion public SRM.MethodSignature DecodeMethodSignature(SRM.StandaloneSignatureHandle handle) { var standaloneSignature = moduleDefinition.Metadata.GetStandaloneSignature(handle); if (standaloneSignature.GetKind() != SRM.StandaloneSignatureKind.Method) throw new InvalidOperationException("Expected Method signature"); return standaloneSignature.DecodeMethodSignature( mainAssembly.TypeProvider, new GenericContext(context) ); } public ImmutableArray DecodeLocalSignature(SRM.StandaloneSignatureHandle handle) { var standaloneSignature = moduleDefinition.Metadata.GetStandaloneSignature(handle); if (standaloneSignature.GetKind() != SRM.StandaloneSignatureKind.LocalVariables) throw new InvalidOperationException("Expected Local signature"); return standaloneSignature.DecodeLocalSignature( mainAssembly.TypeProvider, new GenericContext(context) ); } #region Resolve Field public IField ResolveAsField(SRM.EntityHandle fieldReference) { if (fieldReference.IsNil) throw new ArgumentNullException(nameof(fieldReference)); if (fieldReference.Kind != SRM.HandleKind.FieldDefinition && fieldReference.Kind != SRM.HandleKind.MemberReference) throw new ArgumentException("HandleKind must be either FieldDefinition or MemberReference", nameof(fieldReference)); lock (fieldLookupCache) { if (!fieldLookupCache.TryGetValue(fieldReference, out IField field)) { var metadata = moduleDefinition.Metadata; IType declaringType; switch (fieldReference.Kind) { case SRM.HandleKind.FieldDefinition: var fieldDefHandle = (SRM.FieldDefinitionHandle)fieldReference; var fieldDef = metadata.GetFieldDefinition(fieldDefHandle); declaringType = ResolveDeclaringType(fieldDef.GetDeclaringType()); var declaringTypeDefinition = declaringType.GetDefinition(); if (declaringTypeDefinition != null) { field = declaringTypeDefinition.GetFields(f => f.MetadataToken == fieldReference, GetMemberOptions.IgnoreInheritedMembers).FirstOrDefault(); } else { field = null; } if (field == null) { field = new FakeField(compilation) { DeclaringType = declaringType, Name = metadata.GetString(fieldDef.Name), ReturnType = FieldTypeReference.Resolve(fieldDefHandle, metadata, context, typeSystemOptions), IsStatic = (fieldDef.Attributes & System.Reflection.FieldAttributes.Static) != 0, }; } break; case SRM.HandleKind.MemberReference: var memberRefHandle = (SRM.MemberReferenceHandle)fieldReference; var memberRef = metadata.GetMemberReference(memberRefHandle); Debug.Assert(memberRef.GetKind() == SRM.MemberReferenceKind.Field); declaringType = ResolveDeclaringType(memberRef.Parent); field = FindNonGenericField(metadata, memberRefHandle, declaringType); break; default: throw new NotSupportedException(); } if (declaringType.TypeArguments.Count > 0) { field = (IField)field.Specialize(new TypeParameterSubstitution(declaringType.TypeArguments, null)); } fieldLookupCache.Add(fieldReference, field); } return field; } } IField FindNonGenericField(SRM.MetadataReader metadata, SRM.MemberReferenceHandle memberRefHandle, IType declaringType) { var memberRef = metadata.GetMemberReference(memberRefHandle); string name = metadata.GetString(memberRef.Name); ITypeDefinition typeDef = declaringType.GetDefinition(); if (typeDef != null) { foreach (IField field in typeDef.Fields) { if (field.Name == name) return field; } } var returnType = memberRef.DecodeFieldSignature(mainAssembly.TypeProvider, new GenericContext(context)); return new FakeField(compilation) { DeclaringType = declaringType, Name = name, ReturnType = returnType }; } #endregion #region Resolve Method public IMethod ResolveAsMethod(SRM.EntityHandle methodReference) { if (methodReference.IsNil) throw new ArgumentNullException(nameof(methodReference)); if (methodReference.Kind != SRM.HandleKind.MethodDefinition && methodReference.Kind != SRM.HandleKind.MemberReference && methodReference.Kind != SRM.HandleKind.MethodSpecification) throw new ArgumentException("HandleKind must be either a MethodDefinition, MemberReference or MethodSpecification", nameof(methodReference)); lock (methodLookupCache) { if (!methodLookupCache.TryGetValue(methodReference, out IMethod method)) { var metadata = moduleDefinition.Metadata; switch (methodReference.Kind) { case SRM.HandleKind.MethodDefinition: method = ResolveMethodDefinition(metadata, (SRM.MethodDefinitionHandle)methodReference); break; case SRM.HandleKind.MemberReference: method = ResolveMethodReference(metadata, (SRM.MemberReferenceHandle)methodReference); break; case SRM.HandleKind.MethodSpecification: var methodSpec = metadata.GetMethodSpecification((SRM.MethodSpecificationHandle)methodReference); var methodTypeArgs = methodSpec.DecodeSignature(mainAssembly.TypeProvider, new GenericContext(context)); if (methodSpec.Method.Kind == SRM.HandleKind.MethodDefinition) { // generic instance of a methoddef (=generic method in non-generic class in current assembly) method = ResolveMethodDefinition(metadata, (SRM.MethodDefinitionHandle)methodSpec.Method); method = method.Specialize(new TypeParameterSubstitution(null, methodTypeArgs)); } else { method = ResolveMethodReference(metadata, (SRM.MemberReferenceHandle)methodSpec.Method, methodTypeArgs); } break; default: throw new NotSupportedException(); } methodLookupCache.Add(methodReference, method); } return method; } } IMethod ResolveMethodDefinition(SRM.MetadataReader metadata, SRM.MethodDefinitionHandle methodDefHandle, bool expandVarArgs = true) { var method = mainAssembly.GetDefinition(methodDefHandle); if (method == null) { throw new NotImplementedException(); } if (expandVarArgs && method.Parameters.LastOrDefault()?.Type.Kind == TypeKind.ArgList) { method = new VarArgInstanceMethod(method, EmptyList.Instance); } return method; } /// /// Resolves a method reference. /// /// /// Class type arguments are provided by the declaring type stored in the memberRef. /// Method type arguments are provided by the caller. /// IMethod ResolveMethodReference(SRM.MetadataReader metadata, SRM.MemberReferenceHandle memberRefHandle, IReadOnlyList methodTypeArguments = null) { var memberRef = metadata.GetMemberReference(memberRefHandle); Debug.Assert(memberRef.GetKind() == SRM.MemberReferenceKind.Method); SRM.MethodSignature signature; IReadOnlyList classTypeArguments = null; IMethod method; if (memberRef.Parent.Kind == SRM.HandleKind.MethodDefinition) { method = ResolveMethodDefinition(metadata, (SRM.MethodDefinitionHandle)memberRef.Parent, expandVarArgs: false); signature = memberRef.DecodeMethodSignature(mainAssembly.TypeProvider, new GenericContext(context)); } else { var declaringType = ResolveDeclaringType(memberRef.Parent); var declaringTypeDefinition = declaringType.GetDefinition(); if (declaringType.TypeArguments.Count > 0) { classTypeArguments = declaringType.TypeArguments; } // Note: declaringType might be parameterized, but the signature is for the original method definition. // We'll have to search the member directly on declaringTypeDefinition. string name = metadata.GetString(memberRef.Name); signature = memberRef.DecodeMethodSignature(mainAssembly.TypeProvider, new GenericContext(declaringTypeDefinition?.TypeParameters)); if (declaringTypeDefinition != null) { // Find the set of overloads to search: IEnumerable methods; if (name == ".ctor") { methods = declaringTypeDefinition.GetConstructors(); } else if (name == ".cctor") { methods = declaringTypeDefinition.Methods.Where(m => m.IsConstructor && m.IsStatic); } else { methods = declaringTypeDefinition.GetMethods(m => m.Name == name, GetMemberOptions.IgnoreInheritedMembers) .Concat(declaringTypeDefinition.GetAccessors(m => m.Name == name, GetMemberOptions.IgnoreInheritedMembers)); } // Determine the expected parameters from the signature: ImmutableArray parameterTypes; if (signature.Header.CallingConvention == SRM.SignatureCallingConvention.VarArgs) { parameterTypes = signature.ParameterTypes .Take(signature.RequiredParameterCount) .Concat(new[] { SpecialType.ArgList }) .ToImmutableArray(); } else { parameterTypes = signature.ParameterTypes; } // Search for the matching method: method = null; foreach (var m in methods) { if (m.TypeParameters.Count != signature.GenericParameterCount) continue; if (CompareSignatures(m.Parameters, parameterTypes) && CompareTypes(m.ReturnType, signature.ReturnType)) { method = m; break; } } } else { method = null; } if (method == null) { method = CreateFakeMethod(declaringType, name, signature); } } if (classTypeArguments != null || methodTypeArguments != null) { method = method.Specialize(new TypeParameterSubstitution(classTypeArguments, methodTypeArguments)); } if (signature.Header.CallingConvention == SRM.SignatureCallingConvention.VarArgs) { method = new VarArgInstanceMethod(method, signature.ParameterTypes.Skip(signature.RequiredParameterCount)); } return method; } static readonly NormalizeTypeVisitor normalizeTypeVisitor = new NormalizeTypeVisitor { ReplaceClassTypeParametersWithDummy = true, ReplaceMethodTypeParametersWithDummy = true, }; static bool CompareTypes(IType a, IType b) { IType type1 = a.AcceptVisitor(normalizeTypeVisitor); IType type2 = b.AcceptVisitor(normalizeTypeVisitor); return type1.Equals(type2); } static bool IsVarArgMethod(IMethod method) { return method.Parameters.Count > 0 && method.Parameters[method.Parameters.Count - 1].Type.Kind == TypeKind.ArgList; } static bool CompareSignatures(IReadOnlyList parameters, ImmutableArray parameterTypes) { if (parameterTypes.Length != parameters.Count) return false; for (int i = 0; i < parameterTypes.Length; i++) { if (!CompareTypes(parameterTypes[i], parameters[i].Type)) return false; } return true; } /// /// Create a dummy IMethod from the specified MethodReference /// IMethod CreateFakeMethod(IType declaringType, string name, SRM.MethodSignature signature) { SymbolKind symbolKind = SymbolKind.Method; if (name == ".ctor" || name == ".cctor") symbolKind = SymbolKind.Constructor; var m = new FakeMethod(compilation, symbolKind); m.DeclaringType = declaringType; m.Name = name; m.ReturnType = signature.ReturnType; m.IsStatic = !signature.Header.IsInstance; var metadata = moduleDefinition.Metadata; TypeParameterSubstitution substitution = null; if (signature.GenericParameterCount > 0) { var typeParameters = new List(); for (int i = 0; i < signature.GenericParameterCount; i++) { typeParameters.Add(new DefaultTypeParameter(m, i)); } m.TypeParameters = typeParameters; substitution = new TypeParameterSubstitution(null, typeParameters); } var parameters = new List(); for (int i = 0; i < signature.RequiredParameterCount; i++) { var type = signature.ParameterTypes[i]; if (substitution != null) { // replace the dummy method type parameters with the owned instances we just created type = type.AcceptVisitor(substitution); } parameters.Add(new DefaultParameter(type, "")); } m.Parameters = parameters; return m; } #endregion #region Resolve Property public IProperty ResolveAsProperty(SRM.EntityHandle propertyReference) { if (propertyReference.IsNil) throw new ArgumentNullException(nameof(propertyReference)); if (propertyReference.Kind != SRM.HandleKind.PropertyDefinition) throw new ArgumentException("HandleKind must be PropertyDefinition", nameof(propertyReference)); lock (propertyLookupCache) { IProperty property; if (!propertyLookupCache.TryGetValue(propertyReference, out property)) { var metadata = moduleDefinition.Metadata; property = FindNonGenericProperty(metadata, (SRM.PropertyDefinitionHandle)propertyReference); /*if (propertyReference.DeclaringType.IsGenericInstance) { var git = (GenericInstanceType)propertyReference.DeclaringType; var typeArguments = git.GenericArguments.SelectArray(Resolve); property = (IProperty)property.Specialize(new TypeParameterSubstitution(typeArguments, null)); }*/ propertyLookupCache.Add(propertyReference, property); } return property; } } IProperty FindNonGenericProperty(SRM.MetadataReader metadata, SRM.PropertyDefinitionHandle handle) { var propertyDefinition = metadata.GetPropertyDefinition(handle); var declaringType = metadata.GetMethodDefinition(propertyDefinition.GetAccessors().GetAny()).GetDeclaringType(); ITypeDefinition typeDef = ResolveDeclaringType(declaringType).GetDefinition(); if (typeDef == null) return null; foreach (IProperty property in typeDef.Properties) { if (property.MetadataToken == handle) return property; } return null; } #endregion #region Resolve Event public IEvent ResolveAsEvent(SRM.EntityHandle eventReference) { if (eventReference.IsNil) throw new ArgumentNullException(nameof(eventReference)); if (eventReference.Kind != SRM.HandleKind.EventDefinition) throw new ArgumentException("HandleKind must be EventDefinition", nameof(eventReference)); lock (eventLookupCache) { IEvent ev; if (!eventLookupCache.TryGetValue(eventReference, out ev)) { var metadata = moduleDefinition.Metadata; ev = FindNonGenericEvent(metadata, (SRM.EventDefinitionHandle)eventReference); /*if (eventReference.DeclaringType.IsGenericInstance) { var git = (GenericInstanceType)eventReference.DeclaringType; var typeArguments = git.GenericArguments.SelectArray(Resolve); ev = (IEvent)ev.Specialize(new TypeParameterSubstitution(typeArguments, null)); }*/ eventLookupCache.Add(eventReference, ev); } return ev; } } IEvent FindNonGenericEvent(SRM.MetadataReader metadata, SRM.EventDefinitionHandle handle) { var eventDefinition = metadata.GetEventDefinition(handle); var declaringType = metadata.GetMethodDefinition(eventDefinition.GetAccessors().GetAny()).GetDeclaringType(); ITypeDefinition typeDef = ResolveDeclaringType(declaringType).GetDefinition(); if (typeDef == null) return null; var returnType = ResolveAsType(eventDefinition.Type); string name = metadata.GetString(eventDefinition.Name); foreach (IEvent ev in typeDef.Events) { if (ev.MetadataToken == handle) return ev; } return null; } #endregion public IDecompilerTypeSystem GetSpecializingTypeSystem(TypeParameterSubstitution substitution) { if (substitution.Equals(TypeParameterSubstitution.Identity)) { return this; } else { return new SpecializingDecompilerTypeSystem(this, substitution); } } } }