using System; using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; using Mono.Cecil; namespace ICSharpCode.Decompiler.TypeSystem { /// /// Manages the NRefactory type system for the decompiler. /// /// /// This class is thread-safe. /// public class DecompilerTypeSystem : IDecompilerTypeSystem { readonly ModuleDefinition moduleDefinition; readonly ICompilation compilation; readonly ITypeResolveContext context; /// /// CecilLoader used for converting cecil type references to ITypeReference. /// May only be accessed within lock(typeReferenceCecilLoader). /// readonly CecilLoader typeReferenceCecilLoader; /// /// Dictionary for NRefactory->Cecil lookup. /// May only be accessed within lock(entityDict) /// Dictionary entityDict = new Dictionary(); Dictionary fieldLookupCache = new Dictionary(); Dictionary propertyLookupCache = new Dictionary(); Dictionary methodLookupCache = new Dictionary(); Dictionary eventLookupCache = new Dictionary(); public DecompilerTypeSystem(ModuleDefinition moduleDefinition) : this(moduleDefinition, new DecompilerSettings()) { } public DecompilerTypeSystem(ModuleDefinition moduleDefinition, DecompilerSettings settings) { if (moduleDefinition == null) throw new ArgumentNullException(nameof(moduleDefinition)); if (settings == null) throw new ArgumentNullException(nameof(settings)); this.moduleDefinition = moduleDefinition; typeReferenceCecilLoader = new CecilLoader { UseDynamicType = settings.Dynamic, UseTupleTypes = settings.TupleTypes, }; CecilLoader cecilLoader = new CecilLoader { IncludeInternalMembers = true, LazyLoad = true, OnEntityLoaded = StoreMemberReference, ShortenInterfaceImplNames = false, UseDynamicType = settings.Dynamic, UseTupleTypes = settings.TupleTypes, }; typeReferenceCecilLoader.SetCurrentModule(moduleDefinition); IUnresolvedAssembly mainAssembly = cecilLoader.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((AssemblyNameReference 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) { referencedAssemblies.Add(cecilLoader.LoadAssembly(asm)); foreach (var forwarder in asm.MainModule.ExportedTypes) { if (!forwarder.IsForwarder || !(forwarder.Scope is AssemblyNameReference forwarderRef)) continue; assemblyReferenceQueue.Enqueue(forwarderRef); } } } 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 IAssembly MainAssembly { get { return compilation.MainAssembly; } } public ModuleDefinition ModuleDefinition { get { return moduleDefinition; } } void StoreMemberReference(IUnresolvedEntity entity, MemberReference mr) { // This is a callback from the type system, which is multi-threaded and may be accessed externally lock (entityDict) entityDict[entity] = mr; } /// /// Retrieves the Cecil member definition for the specified member. /// /// /// Returns null if the member is not defined in the module being decompiled. /// public MemberReference GetCecil(IUnresolvedEntity member) { if (member == null) return null; lock (entityDict) { MemberReference mr; if (entityDict.TryGetValue(member, out mr)) return mr; return null; } } /// /// Retrieves the Cecil member definition for the specified member. /// /// /// Returns null if the member is not defined in the module being decompiled. /// public MemberReference GetCecil(IMember member) { if (member == null) return null; return GetCecil(member.UnresolvedMember); } /// /// Retrieves the Cecil type definition. /// /// /// Returns null if the type is not defined in the module being decompiled. /// public TypeDefinition GetCecil(ITypeDefinition typeDefinition) { if (typeDefinition == null) return null; return GetCecil(typeDefinition.Parts[0]) as TypeDefinition; } #region Resolve Type public IType Resolve(TypeReference typeReference, bool isFromSignature = false) { if (typeReference == null) return SpecialType.UnknownType; // We need to skip SentinelType and PinnedType. // But PinnedType can be nested within modopt, so we'll also skip those. while (typeReference is OptionalModifierType || typeReference is RequiredModifierType) { typeReference = ((TypeSpecification)typeReference).ElementType; isFromSignature = true; } if (typeReference is SentinelType || typeReference is PinnedType) { typeReference = ((TypeSpecification)typeReference).ElementType; isFromSignature = true; } ITypeReference typeRef; lock (typeReferenceCecilLoader) typeRef = typeReferenceCecilLoader.ReadTypeReference(typeReference, isFromSignature: isFromSignature); return typeRef.Resolve(context); } IType ResolveInSignature(TypeReference typeReference) { return Resolve(typeReference, isFromSignature: true); } #endregion #region Resolve Field public IField Resolve(FieldReference fieldReference) { if (fieldReference == null) throw new ArgumentNullException(nameof(fieldReference)); lock (fieldLookupCache) { IField field; if (!fieldLookupCache.TryGetValue(fieldReference, out field)) { field = FindNonGenericField(fieldReference); if (fieldReference.DeclaringType.IsGenericInstance) { var git = (GenericInstanceType)fieldReference.DeclaringType; var typeArguments = git.GenericArguments.SelectArray(ResolveInSignature); field = (IField)field.Specialize(new TypeParameterSubstitution(typeArguments, null)); } fieldLookupCache.Add(fieldReference, field); } return field; } } IField FindNonGenericField(FieldReference fieldReference) { ITypeDefinition typeDef = Resolve(fieldReference.DeclaringType).GetDefinition(); if (typeDef == null) return CreateFakeField(fieldReference); foreach (IField field in typeDef.Fields) if (field.Name == fieldReference.Name) return field; return CreateFakeField(fieldReference); } IField CreateFakeField(FieldReference fieldReference) { var declaringType = Resolve(fieldReference.DeclaringType); var f = new DefaultUnresolvedField(); f.Name = fieldReference.Name; lock (typeReferenceCecilLoader) { f.ReturnType = typeReferenceCecilLoader.ReadTypeReference(fieldReference.FieldType); } return new ResolvedFakeField(f, context.WithCurrentTypeDefinition(declaringType.GetDefinition()), declaringType); } class ResolvedFakeField : DefaultResolvedField { readonly IType declaringType; public ResolvedFakeField(DefaultUnresolvedField unresolved, ITypeResolveContext parentContext, IType declaringType) : base(unresolved, parentContext) { this.declaringType = declaringType; } public override IType DeclaringType { get { return declaringType; } } } #endregion #region Resolve Method public IMethod Resolve(MethodReference methodReference) { if (methodReference == null) throw new ArgumentNullException(nameof(methodReference)); lock (methodLookupCache) { IMethod method; if (!methodLookupCache.TryGetValue(methodReference, out method)) { method = FindNonGenericMethod(methodReference.GetElementMethod()); if (methodReference.CallingConvention == MethodCallingConvention.VarArg) { method = new VarArgInstanceMethod( method, methodReference.Parameters.SkipWhile(p => !p.ParameterType.IsSentinel).Select(p => ResolveInSignature(p.ParameterType)) ); } else if (methodReference.IsGenericInstance || methodReference.DeclaringType.IsGenericInstance) { IReadOnlyList classTypeArguments = null; IReadOnlyList methodTypeArguments = null; if (methodReference.IsGenericInstance) { var gim = ((GenericInstanceMethod)methodReference); methodTypeArguments = gim.GenericArguments.SelectArray(ResolveInSignature); } if (methodReference.DeclaringType.IsGenericInstance) { var git = (GenericInstanceType)methodReference.DeclaringType; classTypeArguments = git.GenericArguments.SelectArray(ResolveInSignature); } method = method.Specialize(new TypeParameterSubstitution(classTypeArguments, methodTypeArguments)); } methodLookupCache.Add(methodReference, method); } return method; } } IMethod FindNonGenericMethod(MethodReference methodReference) { ITypeDefinition typeDef = Resolve(methodReference.DeclaringType).GetDefinition(); if (typeDef == null) return CreateFakeMethod(methodReference); IEnumerable methods; if (methodReference.Name == ".ctor") { methods = typeDef.GetConstructors(); } else if (methodReference.Name == ".cctor") { return typeDef.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic); } else { methods = typeDef.GetMethods(m => m.Name == methodReference.Name, GetMemberOptions.IgnoreInheritedMembers) .Concat(typeDef.GetAccessors(m => m.Name == methodReference.Name, GetMemberOptions.IgnoreInheritedMembers)); } if (methodReference.MetadataToken.TokenType == TokenType.Method) { foreach (var method in methods) { if (method.MetadataToken == methodReference.MetadataToken) return method; } } IType[] parameterTypes; if (methodReference.CallingConvention == MethodCallingConvention.VarArg) { parameterTypes = methodReference.Parameters .TakeWhile(p => !p.ParameterType.IsSentinel) .Select(p => ResolveInSignature(p.ParameterType)) .Concat(new[] { SpecialType.ArgList }) .ToArray(); } else { parameterTypes = methodReference.Parameters.SelectArray(p => ResolveInSignature(p.ParameterType)); } var returnType = ResolveInSignature(methodReference.ReturnType); foreach (var method in methods) { if (method.TypeParameters.Count != methodReference.GenericParameters.Count) continue; if (!CompareSignatures(method.Parameters, parameterTypes) || !CompareTypes(method.ReturnType, returnType)) continue; return method; } return CreateFakeMethod(methodReference); } static bool CompareTypes(IType a, IType b) { IType type1 = DummyTypeParameter.NormalizeAllTypeParameters(a); IType type2 = DummyTypeParameter.NormalizeAllTypeParameters(b); 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, IType[] 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(MethodReference methodReference) { var m = new DefaultUnresolvedMethod(); ITypeReference declaringTypeReference; lock (typeReferenceCecilLoader) { declaringTypeReference = typeReferenceCecilLoader.ReadTypeReference(methodReference.DeclaringType); if (methodReference.Name == ".ctor" || methodReference.Name == ".cctor") m.SymbolKind = SymbolKind.Constructor; m.Name = methodReference.Name; m.ReturnType = typeReferenceCecilLoader.ReadTypeReference(methodReference.ReturnType); m.IsStatic = !methodReference.HasThis; for (int i = 0; i < methodReference.GenericParameters.Count; i++) { m.TypeParameters.Add(new DefaultUnresolvedTypeParameter(SymbolKind.Method, i, methodReference.GenericParameters[i].Name)); } foreach (var p in methodReference.Parameters) { m.Parameters.Add(new DefaultUnresolvedParameter(typeReferenceCecilLoader.ReadTypeReference(p.ParameterType), p.Name)); } } var type = declaringTypeReference.Resolve(context); return new ResolvedFakeMethod(m, context.WithCurrentTypeDefinition(type.GetDefinition()), type); } class ResolvedFakeMethod : DefaultResolvedMethod { readonly IType declaringType; public ResolvedFakeMethod(DefaultUnresolvedMethod unresolved, ITypeResolveContext parentContext, IType declaringType) : base(unresolved, parentContext) { this.declaringType = declaringType; } public override IType DeclaringType { get { return declaringType; } } } #endregion #region Resolve Property public IProperty Resolve(PropertyReference propertyReference) { if (propertyReference == null) throw new ArgumentNullException(nameof(propertyReference)); lock (propertyLookupCache) { IProperty property; if (!propertyLookupCache.TryGetValue(propertyReference, out property)) { property = FindNonGenericProperty(propertyReference); if (propertyReference.DeclaringType.IsGenericInstance) { var git = (GenericInstanceType)propertyReference.DeclaringType; var typeArguments = git.GenericArguments.SelectArray(ResolveInSignature); property = (IProperty)property.Specialize(new TypeParameterSubstitution(typeArguments, null)); } propertyLookupCache.Add(propertyReference, property); } return property; } } IProperty FindNonGenericProperty(PropertyReference propertyReference) { ITypeDefinition typeDef = Resolve(propertyReference.DeclaringType).GetDefinition(); if (typeDef == null) return null; var parameterTypes = propertyReference.Parameters.SelectArray(p => ResolveInSignature(p.ParameterType)); var returnType = Resolve(propertyReference.PropertyType); foreach (IProperty property in typeDef.Properties) { if (property.Name == propertyReference.Name && CompareTypes(property.ReturnType, returnType) && CompareSignatures(property.Parameters, parameterTypes)) return property; } return null; } #endregion #region Resolve Event public IEvent Resolve(EventReference eventReference) { if (eventReference == null) throw new ArgumentNullException("propertyReference"); lock (eventLookupCache) { IEvent ev; if (!eventLookupCache.TryGetValue(eventReference, out ev)) { ev = FindNonGenericEvent(eventReference); if (eventReference.DeclaringType.IsGenericInstance) { var git = (GenericInstanceType)eventReference.DeclaringType; var typeArguments = git.GenericArguments.SelectArray(ResolveInSignature); ev = (IEvent)ev.Specialize(new TypeParameterSubstitution(typeArguments, null)); } eventLookupCache.Add(eventReference, ev); } return ev; } } IEvent FindNonGenericEvent(EventReference eventReference) { ITypeDefinition typeDef = Resolve(eventReference.DeclaringType).GetDefinition(); if (typeDef == null) return null; var returnType = Resolve(eventReference.EventType); foreach (IEvent ev in typeDef.Events) { if (ev.Name == eventReference.Name && CompareTypes(ev.ReturnType, returnType)) return ev; } return null; } #endregion public IDecompilerTypeSystem GetSpecializingTypeSystem(TypeParameterSubstitution substitution) { if (substitution.Equals(TypeParameterSubstitution.Identity)) { return this; } else { return new SpecializingDecompilerTypeSystem(this, substitution); } } } }