context: /*
      Copyright (C) 2014-2019 de4dot@gmail.com
  
      This file is part of dnSpy
  
      dnSpy is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      dnSpy is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System.Collections.Generic;
  using System.Runtime.InteropServices;
  using dnlib.DotNet;
  
  namespace dnSpy.Contracts.Decompiler {
  	/// <summary>
  	/// Custom attributes utils
  	/// </summary>
  	public static class CustomAttributesUtils {
  		static bool IsType(TypeDef type, (UTF8String @namespace, UTF8String name)[] typeNames) {
  			if (!(type.DeclaringType is null))
  				return false;
  			var name = type.Name;
  			var @namespace = type.Namespace;
  			foreach (var info in typeNames) {
  				if (name == info.name && @namespace == info.@namespace)
  					return true;
  			}
  			return false;
  		}
  
  		/// <summary>
  		/// Checks whether <paramref name="type"/> is a pseudo custom attribute type
  		/// </summary>
  		/// <param name="type">Type to check</param>
  		/// <returns></returns>
  		public static bool IsPseudoCustomAttributeType(TypeDef type) {
  			if (!(type.BaseType is ITypeDefOrRef baseType))
  				return false;
  			bool canCheck = false;
  			if (baseType.Name == attributeName && baseType.Namespace == systemName)
  				canCheck = true;
  			else if (IsSecurityAttribute(type))
  				return true;
  
  			return canCheck && IsType(type, pseudoCANames);
  		}
  
  		static bool IsSecurityAttribute(TypeDef type) {
  			for (int i = 0; i < 1000; i++) {
  				if (type.Name == securityAttributeName && type.Namespace == systemSecurityPermissionsName)
  					return true;
  				if (!(type.BaseType is ITypeDefOrRef baseType))
  					break;
  				if (!(baseType.ResolveTypeDef() is TypeDef bt))
  					break;
  				type = bt;
  			}
  			return false;
  		}
  
  		/// <summary>
  		/// Checks whether <paramref name="type"/> is a pseudo custom attribute related type
  		/// </summary>
  		/// <param name="type">Type to check</param>
  		/// <returns></returns>
  		public static bool IsPseudoCustomAttributeOtherType(TypeDef type) {
  			var nonNestedType = type;
  			while (nonNestedType.DeclaringType is TypeDef declType)
  				nonNestedType = declType;
  			if (nonNestedType.Namespace == systemSecurityPermissionsName && IsPublic(type))
  				return true;
  
  			return IsType(type, pseudoCAOtherTypeNames);
  		}
  
  		static bool IsPublic(TypeDef type) {
  			for (;;) {
  				if (type.DeclaringType is TypeDef declType) {
  					if (!(type.IsNestedFamily || type.IsNestedFamilyOrAssembly || type.IsNestedPublic))
  						return false;
  					type = declType;
  				}
  				else
  					return type.IsPublic;
  			}
  		}
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="hca">Object with custom attributes</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this IHasCustomAttribute hca) {
  			switch (hca) {
  			case AssemblyDef asm:		return asm.GetCustomAttributes();
  			case ModuleDef mod:			return mod.GetCustomAttributes();
  			case TypeDef type:			return type.GetCustomAttributes();
  			case GenericParam gp:		return gp.GetCustomAttributes();
  			case FieldDef field:		return field.GetCustomAttributes();
  			case PropertyDef property:	return property.GetCustomAttributes();
  			case EventDef @event:		return @event.GetCustomAttributes();
  			case MethodDef method:		return method.GetCustomAttributes();
  			case ParamDef parameter:	return parameter.GetCustomAttributes();
  			default:					return hca.CustomAttributes;
  			}
  		}
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="gp">Generic parameter</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this GenericParam gp) => gp.CustomAttributes;
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="event">Event</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this EventDef @event) => @event.CustomAttributes;
  
  		static IEnumerable<CustomAttribute> GetSecurityDeclarations(ModuleDef module, IHasDeclSecurity hds) {
  			TypeSig? securityActionType = null;
  			foreach (var ds in hds.DeclSecurities) {
  				if (securityActionType is null)
  					securityActionType = new ValueTypeSig(new TypeRefUser(module, systemSecurityPermissionsName, securityActionName, module.CorLibTypes.AssemblyRef));
  				foreach (var secAttr in ds.SecurityAttributes) {
  					var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, securityActionType), secAttr.AttributeType));
  					ca.ConstructorArguments.Add(new CAArgument(securityActionType, (int)ds.Action));
  					foreach (var namedArg in secAttr.NamedArguments)
  						ca.NamedArguments.Add(namedArg);
  					yield return ca;
  				}
  			}
  		}
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="assembly">Assembly</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this AssemblyDef assembly) {
  			var module = (ModuleDef?)assembly.ManifestModule;
  			if (!(module is null)) {
  				if (assembly.HashAlgorithm != AssemblyHashAlgorithm.SHA1) {
  					var declType = new TypeRefUser(module, systemReflectionName, assemblyAlgorithmIdAttributeName, module.CorLibTypes.AssemblyRef);
  					var enumDeclType = new ValueTypeSig(new TypeRefUser(module, systemConfigurationAssembliesName, assemblyHashAlgorithmName, module.CorLibTypes.AssemblyRef));
  					var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, enumDeclType), declType));
  					ca.ConstructorArguments.Add(new CAArgument(enumDeclType, (int)assembly.HashAlgorithm));
  					yield return ca;
  				}
  				if (!UTF8String.IsNullOrEmpty(assembly.Culture)) {
  					var declType = new TypeRefUser(module, systemReflectionName, assemblyCultureAttributeName, module.CorLibTypes.AssemblyRef);
  					var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.String), declType));
  					ca.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, assembly.Culture));
  					yield return ca;
  				}
  				var asmAttrs = assembly.Attributes & ~AssemblyAttributes.PublicKey;
  				if (asmAttrs != AssemblyAttributes.None) {
  					var declType = new TypeRefUser(module, systemReflectionName, assemblyFlagsAttributeName, module.CorLibTypes.AssemblyRef);
  					var enumDeclType = new ValueTypeSig(new TypeRefUser(module, systemReflectionName, assemblyNameFlagsName, module.CorLibTypes.AssemblyRef));
  					var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, enumDeclType), declType));
  					ca.ConstructorArguments.Add(new CAArgument(enumDeclType, (int)asmAttrs));
  					yield return ca;
  				}
  				{
  					var declType = new TypeRefUser(module, systemReflectionName, assemblyVersionAttributeName, module.CorLibTypes.AssemblyRef);
  					var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.String), declType));
  					ca.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, new UTF8String(assembly.Version.ToString())));
  					yield return ca;
  				}
  			}
  			foreach (var ca in assembly.CustomAttributes)
  				yield return ca;
  			if (!(module is null)) {
  				foreach (var ca in GetSecurityDeclarations(module, assembly))
  					yield return ca;
  			}
  
  			foreach (var asmModule in assembly.Modules) {
  				TypeSig? typeType = null;
  				TypeRefUser? typeForwardedToAttributeType = null;
  				MemberRefUser? ctor = null;
  				foreach (var exportedType in asmModule.ExportedTypes) {
  					if (!exportedType.MovedToAnotherAssembly)
  						continue;
  					if (typeForwardedToAttributeType is null)
  						typeForwardedToAttributeType = new TypeRefUser(asmModule, systemRuntimeCompilerServicesName, typeForwardedToAttributeName, asmModule.CorLibTypes.AssemblyRef);
  					if (typeType is null)
  						typeType = new ClassSig(new TypeRefUser(asmModule, systemName, typeName, asmModule.CorLibTypes.AssemblyRef));
  					if (ctor is null)
  						ctor = new MemberRefUser(asmModule, ctorName, MethodSig.CreateInstance(asmModule.CorLibTypes.Void, typeType), typeForwardedToAttributeType);
  					var ca = new CustomAttribute(ctor);
  					ca.ConstructorArguments.Add(new CAArgument(typeType, exportedType.ToTypeRef().ToTypeSig()));
  					yield return ca;
  				}
  			}
  		}
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="module">Module</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this ModuleDef module) => module.CustomAttributes;
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="type">Type</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this TypeDef type) {
  			foreach (var ca in type.CustomAttributes)
  				yield return ca;
  			var module = type.Module;
  			foreach (var ca in GetSecurityDeclarations(module, type))
  				yield return ca;
  			if (type.IsSerializable) {
  				var declType = new TypeRefUser(module, systemName, serializableAttributeName, GetSystemRuntimeSerializationFormattersAssemblyRef(module));
  				yield return new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void), declType));
  			}
  			if (type.IsImport) {
  				var declType = new TypeRefUser(module, systemRuntimeInteropServicesName, comImportAttributeName, GetSystemRuntimeInteropServicesAssemblyRef(module));
  				yield return new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void), declType));
  			}
  			{
  				var layoutKind = LayoutKind.Auto;
  				switch (type.Layout) {
  				case TypeAttributes.SequentialLayout:
  					layoutKind = LayoutKind.Sequential;
  					break;
  				case TypeAttributes.ExplicitLayout:
  					layoutKind = LayoutKind.Explicit;
  					break;
  				}
  				var charSet = CharSet.None;
  				switch (type.StringFormat) {
  				case TypeAttributes.AnsiClass:
  					charSet = CharSet.Ansi;
  					break;
  				case TypeAttributes.AutoClass:
  					charSet = CharSet.Auto;
  					break;
  				case TypeAttributes.UnicodeClass:
  					charSet = CharSet.Unicode;
  					break;
  				}
  				bool isValueType = type.IsValueType;
  				var defaultLayoutKind = isValueType && !type.IsEnum ? LayoutKind.Sequential : LayoutKind.Auto;
  				if (layoutKind != defaultLayoutKind || charSet != CharSet.Ansi || ShowClassLayout(type, isValueType)) {
  					var declType = new TypeRefUser(module, systemRuntimeInteropServicesName, structLayoutAttributeName, module.CorLibTypes.AssemblyRef);
  					var layoutKindType = new ValueTypeSig(new TypeRefUser(module, systemRuntimeInteropServicesName, layoutKindName, module.CorLibTypes.AssemblyRef));
  					var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, layoutKindType), declType));
  					ca.ConstructorArguments.Add(new CAArgument(layoutKindType, (int)layoutKind));
  					if (charSet != CharSet.Ansi) {
  						var charSetType = new ValueTypeSig(new TypeRefUser(module, systemRuntimeInteropServicesName, charSetName, module.CorLibTypes.AssemblyRef));
  						ca.NamedArguments.Add(new CANamedArgument(isField: true, charSetType, charSetName, new CAArgument(charSetType, (int)charSet)));
  					}
  					if (type.PackingSize != ushort.MaxValue && type.PackingSize > 0)
  						ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Int32, packName, new CAArgument(module.CorLibTypes.Int32, (int)type.PackingSize)));
  					if (type.ClassSize != uint.MaxValue && type.ClassSize > 0)
  						ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Int32, sizeName, new CAArgument(module.CorLibTypes.Int32, (int)type.ClassSize)));
  					yield return ca;
  				}
  			}
  		}
  
  		static bool ShowClassLayout(TypeDef type, bool isValueType) {
  			if (!isValueType)
  				return type.HasClassLayout;
  			if (type.HasClassLayout) {
  				foreach (var field in type.Fields) {
  					if (!field.IsStatic)
  						return true;
  				}
  			}
  			return false;
  		}
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="field">Field</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this FieldDef field) {
  			foreach (var ca in field.CustomAttributes)
  				yield return ca;
  			var module = field.Module;
  			if (field.IsNotSerialized) {
  				var declType = new TypeRefUser(module, systemName, nonSerializedAttributeName, GetSystemRuntimeSerializationFormattersAssemblyRef(module));
  				yield return new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void), declType));
  			}
  			if (field.FieldOffset != null) {
  				var declType = new TypeRefUser(module, systemRuntimeInteropServicesName, fieldOffsetAttributeName, module.CorLibTypes.AssemblyRef);
  				var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.Int32), declType));
  				ca.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.Int32, (int)field.FieldOffset.Value));
  				yield return ca;
  			}
  			if (field.MarshalType is MarshalType mt)
  				yield return CreateMarshalTypeCustomAttribute(module, mt);
  		}
  
  		/// <summary>
  		/// Gets custom attributes and pseudo custom attributes
  		/// </summary>
  		/// <param name="method">Method</param>
  		/// <returns></returns>
  		public static IEnumerable<CustomAttribute> GetCustomAttributes(this MethodDef method) {
  			foreach (var ca in method.CustomAttributes)
  				yield return ca;
  			var module = method.Module;
  			foreach (var ca in GetSecurityDeclarations(module, method))
  				yield return ca;
  			var implAttr = method.ImplAttributes & ~MethodImplAttributes.CodeTypeMask;
  			if (method.ImplMap is ImplMap implMap) {
  				var declType = new TypeRefUser(module, systemRuntimeInteropServicesName, dllImportAttributeName, GetSystemRuntimeInteropServicesAssemblyRef(module));
  				var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.String), declType));
  				ca.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, implMap.Module?.Name ?? UTF8String.Empty));
  
  				if (implMap.IsBestFitDisabled)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, bestFitMappingName, new CAArgument(module.CorLibTypes.Boolean, false)));
  				else if (implMap.IsBestFitEnabled)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, bestFitMappingName, new CAArgument(module.CorLibTypes.Boolean, true)));
  
  				System.Runtime.InteropServices.CallingConvention callingConvention;
  				switch (implMap.CallConv) {
  				case PInvokeAttributes.CallConvCdecl:
  					callingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl;
  					break;
  				case PInvokeAttributes.CallConvFastcall:
  					callingConvention = System.Runtime.InteropServices.CallingConvention.FastCall;
  					break;
  				case PInvokeAttributes.CallConvStdCall:
  					callingConvention = System.Runtime.InteropServices.CallingConvention.StdCall;
  					break;
  				case PInvokeAttributes.CallConvThiscall:
  					callingConvention = System.Runtime.InteropServices.CallingConvention.ThisCall;
  					break;
  				case PInvokeAttributes.CallConvWinapi:
  					callingConvention = System.Runtime.InteropServices.CallingConvention.Winapi;
  					break;
  				default:
  					callingConvention = 0;
  					break;
  				}
  				if (callingConvention != System.Runtime.InteropServices.CallingConvention.Winapi) {
  					var callingConventionType = new ValueTypeSig(new TypeRefUser(module, systemRuntimeInteropServicesName, callingConventionName, module.CorLibTypes.AssemblyRef));
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, callingConventionType, callingConventionName, new CAArgument(callingConventionType, (int)callingConvention)));
  				}
  
  				var charSet = CharSet.None;
  				switch (implMap.Attributes & PInvokeAttributes.CharSetMask) {
  				case PInvokeAttributes.CharSetAnsi:
  					charSet = CharSet.Ansi;
  					break;
  				case PInvokeAttributes.CharSetAuto:
  					charSet = CharSet.Auto;
  					break;
  				case PInvokeAttributes.CharSetUnicode:
  					charSet = CharSet.Unicode;
  					break;
  				}
  				if (charSet != CharSet.None) {
  					var charSetType = new ValueTypeSig(new TypeRefUser(module, systemRuntimeInteropServicesName, charSetName, module.CorLibTypes.AssemblyRef));
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, charSetType, charSetName, new CAArgument(charSetType, (int)charSet)));
  				}
  
  				if (!UTF8String.IsNullOrEmpty(implMap.Name) && implMap.Name != method.Name)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.String, entryPointName, new CAArgument(module.CorLibTypes.String, implMap.Name)));
  
  				if (implMap.IsNoMangle)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, exactSpellingName, new CAArgument(module.CorLibTypes.Boolean, true)));
  
  				if ((implAttr & MethodImplAttributes.PreserveSig) == 0)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, preserveSigName, new CAArgument(module.CorLibTypes.Boolean, false)));
  				implAttr &= ~MethodImplAttributes.PreserveSig;
  
  				if (implMap.SupportsLastError)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, setLastErrorName, new CAArgument(module.CorLibTypes.Boolean, true)));
  
  				if (implMap.IsThrowOnUnmappableCharDisabled)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, throwOnUnmappableCharName, new CAArgument(module.CorLibTypes.Boolean, false)));
  				else if (implMap.IsThrowOnUnmappableCharEnabled)
  					ca.NamedArguments.Add(new CANamedArgument(isField: true, module.CorLibTypes.Boolean, throwOnUnmappableCharName, new CAArgument(module.CorLibTypes.Boolean, true)));
  				yield return ca;
  			}
  			if (implAttr == MethodImplAttributes.PreserveSig) {
  				implAttr = 0;
  				var declType = new TypeRefUser(module, systemRuntimeInteropServicesName, preserveSigAttributeName, GetSystemRuntimeInteropServicesAssemblyRef(module));
  				yield return new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void), declType));
  			}
  			if (implAttr != 0) {
  				var declType = new TypeRefUser(module, systemRuntimeCompilerServicesName, methodImplAttributeName, module.CorLibTypes.AssemblyRef);
  				var enumDeclType = new ValueTypeSig(new TypeRefUser(module, systemRuntimeCompilerServicesName, methodImplOptionsName, module.CorLibTypes.AssemblyRef));
  				var ca = new CustomAttribute(new MemberRefUser(module, ctorName, MethodSig.CreateInstance(module.CorLibTypes.Void, enumDeclType), declType));
  				ca.ConstructorArguments.Add(new CAArgument(enumDeclType, (int)implAttr));
  				yield return ca;
  			}
  		}
  
  		static Parameter? GetParameter(MethodDef method, int sequence) {
  
gt: 			if (sequence == 0)