private static void CreateMethods()

in src/profiler/Elastic.Apm.Profiler.Managed/DuckTyping/DuckType.Methods.cs [89:490]


		private static void CreateMethods(TypeBuilder proxyTypeBuilder, Type proxyType, Type targetType, FieldInfo instanceField)
		{
			var proxyMethodsDefinitions = GetMethods(proxyType);

			var targetMethodsDefinitions = GetMethods(targetType);

			foreach (var method in targetMethodsDefinitions)
			{
				if (method.GetCustomAttribute<DuckIncludeAttribute>(true) is not null)
					proxyMethodsDefinitions.Add(method);
			}

			foreach (var proxyMethodDefinition in proxyMethodsDefinitions)
			{
				// Ignore the method marked with `DuckIgnore` attribute
				if (proxyMethodDefinition.GetCustomAttribute<DuckIgnoreAttribute>(true) is not null)
					continue;

				// Extract the method parameters types
				var proxyMethodDefinitionParameters = proxyMethodDefinition.GetParameters();
				var proxyMethodDefinitionParametersTypes = proxyMethodDefinitionParameters.Select(p => p.ParameterType).ToArray();

				// We select the target method to call
				var targetMethod = SelectTargetMethod(targetType, proxyMethodDefinition, proxyMethodDefinitionParameters,
					proxyMethodDefinitionParametersTypes);

				// If the target method couldn't be found and the proxy method doesn't have an implementation already (ex: abstract and virtual classes) we throw.
				if (targetMethod is null && proxyMethodDefinition.IsVirtual)
					DuckTypeTargetMethodNotFoundException.Throw(proxyMethodDefinition);

				// Check if target method is a reverse method
				var isReverse = targetMethod.GetCustomAttribute<DuckReverseMethodAttribute>(true) is not null;

				// Gets the proxy method definition generic arguments
				var proxyMethodDefinitionGenericArguments = proxyMethodDefinition.GetGenericArguments();
				var proxyMethodDefinitionGenericArgumentsNames = proxyMethodDefinitionGenericArguments.Select(a => a.Name).ToArray();

				// Checks if the target method is a generic method while the proxy method is non generic (checks if the Duck attribute contains the generic parameters)
				var targetMethodGenericArguments = targetMethod.GetGenericArguments();
				if (proxyMethodDefinitionGenericArguments.Length == 0 && targetMethodGenericArguments.Length > 0)
				{
					var proxyDuckAttribute = proxyMethodDefinition.GetCustomAttribute<DuckAttribute>();
					if (proxyDuckAttribute is null)
						DuckTypeTargetMethodNotFoundException.Throw(proxyMethodDefinition);

					if (proxyDuckAttribute.GenericParameterTypeNames?.Length != targetMethodGenericArguments.Length)
						DuckTypeTargetMethodNotFoundException.Throw(proxyMethodDefinition);

					targetMethod = targetMethod.MakeGenericMethod(proxyDuckAttribute.GenericParameterTypeNames.Select(name => Type.GetType(name))
						.ToArray());
				}

				// Gets target method parameters
				var targetMethodParameters = targetMethod.GetParameters();
				var targetMethodParametersTypes = targetMethodParameters.Select(p => p.ParameterType).ToArray();

				// Make sure we have the right methods attributes.
				var proxyMethodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig;

				// Create the proxy method implementation
				var proxyMethodParametersBuilders = new ParameterBuilder[proxyMethodDefinitionParameters.Length];
				var proxyMethod = proxyTypeBuilder.DefineMethod(proxyMethodDefinition.Name, proxyMethodAttributes, proxyMethodDefinition.ReturnType,
					proxyMethodDefinitionParametersTypes);
				if (proxyMethodDefinitionGenericArgumentsNames.Length > 0)
					_ = proxyMethod.DefineGenericParameters(proxyMethodDefinitionGenericArgumentsNames);

				// Define the proxy method implementation parameters for optional parameters with default values
				for (var j = 0; j < proxyMethodDefinitionParameters.Length; j++)
				{
					var pmDefParameter = proxyMethodDefinitionParameters[j];
					var pmImpParameter = proxyMethod.DefineParameter(j, pmDefParameter.Attributes, pmDefParameter.Name);
					if (pmDefParameter.HasDefaultValue)
						pmImpParameter.SetConstant(pmDefParameter.RawDefaultValue);

					proxyMethodParametersBuilders[j] = pmImpParameter;
				}

				var il = new LazyILGenerator(proxyMethod.GetILGenerator());
				var returnType = targetMethod.ReturnType;
				List<OutputAndRefParameterData> outputAndRefParameters = null;

				// Load the instance if needed
				if (!targetMethod.IsStatic)
				{
					il.Emit(OpCodes.Ldarg_0);
					il.Emit(instanceField.FieldType.IsValueType ? OpCodes.Ldflda : OpCodes.Ldfld, instanceField);
				}

				// Load all the arguments / parameters
				var maxParamLength = Math.Max(proxyMethodDefinitionParameters.Length, targetMethodParameters.Length);
				for (var idx = 0; idx < maxParamLength; idx++)
				{
					var proxyParamInfo = idx < proxyMethodDefinitionParameters.Length ? proxyMethodDefinitionParameters[idx] : null;
					var targetParamInfo = targetMethodParameters[idx];

					if (proxyParamInfo is null)
					{
						// The proxy method is missing parameters, we check if the target parameter is optional
						if (!targetParamInfo.IsOptional)
						{
							// The target method parameter is not optional.
							DuckTypeProxyMethodParameterIsMissingException.Throw(proxyMethodDefinition, targetParamInfo);
						}
					}
					else
					{
						if (proxyParamInfo.IsOut != targetParamInfo.IsOut || proxyParamInfo.IsIn != targetParamInfo.IsIn)
						{
							// the proxy and target parameters doesn't have the same signature
							DuckTypeProxyAndTargetMethodParameterSignatureMismatchException.Throw(proxyMethodDefinition, targetMethod);
						}

						var proxyParamType = proxyParamInfo.ParameterType;
						var targetParamType = targetParamInfo.ParameterType;

						if (proxyParamType.IsByRef != targetParamType.IsByRef)
						{
							// the proxy and target parameters doesn't have the same signature
							DuckTypeProxyAndTargetMethodParameterSignatureMismatchException.Throw(proxyMethodDefinition, targetMethod);
						}

						// We check if we have to handle an output parameter, by ref parameter or a normal parameter
						if (proxyParamInfo.IsOut)
						{
							// If is an output parameter with diferent types we need to handle differently
							// by creating a local var first to store the target parameter out value
							// and then try to set the output parameter of the proxy method by converting the value (a base class or a duck typing)
							if (proxyParamType != targetParamType)
							{
								var localTargetArg = il.DeclareLocal(targetParamType.GetElementType());

								// We need to store the output parameter data to set the proxy parameter value after we call the target method
								if (outputAndRefParameters is null)
									outputAndRefParameters = new List<OutputAndRefParameterData>();

								outputAndRefParameters.Add(new OutputAndRefParameterData(localTargetArg.LocalIndex, targetParamType, idx,
									proxyParamType));

								// Load the local var ref (to be used in the target method param as output)
								il.Emit(OpCodes.Ldloca_S, localTargetArg.LocalIndex);
							}
							else
								il.WriteLoadArgument(idx, false);
						}
						else if (proxyParamType.IsByRef)
						{
							// If is a ref parameter with diferent types we need to handle differently
							// by creating a local var first to store the initial proxy parameter ref value casted to the target parameter type ( this cast may fail at runtime )
							// later pass this local var ref to the target method, and then, modify the proxy parameter ref with the new reference from the target method
							// by converting the value (a base class or a duck typing)
							if (proxyParamType != targetParamType)
							{
								var proxyParamTypeElementType = proxyParamType.GetElementType();
								var targetParamTypeElementType = targetParamType.GetElementType();

								if (!UseDirectAccessTo(proxyTypeBuilder, targetParamTypeElementType))
								{
									targetParamType = typeof(object).MakeByRefType();
									targetParamTypeElementType = typeof(object);
								}

								var localTargetArg = il.DeclareLocal(targetParamTypeElementType);

								// We need to store the ref parameter data to set the proxy parameter value after we call the target method
								if (outputAndRefParameters is null)
									outputAndRefParameters = new List<OutputAndRefParameterData>();

								outputAndRefParameters.Add(new OutputAndRefParameterData(localTargetArg.LocalIndex, targetParamType, idx,
									proxyParamType));

								// Load the argument (ref)
								il.WriteLoadArgument(idx, false);

								// Load the value inside the ref
								il.Emit(OpCodes.Ldind_Ref);

								// Check if the type can be converted of if we need to enable duck chaining
								if (NeedsDuckChaining(targetParamTypeElementType, proxyParamTypeElementType))
								{
									// First we check if the value is null before trying to get the instance value
									var lblCallGetInstance = il.DefineLabel();
									var lblAfterGetInstance = il.DefineLabel();

									il.Emit(OpCodes.Dup);
									il.Emit(OpCodes.Brtrue_S, lblCallGetInstance);

									il.Emit(OpCodes.Pop);
									il.Emit(OpCodes.Ldnull);
									il.Emit(OpCodes.Br_S, lblAfterGetInstance);

									// Call IDuckType.Instance property to get the actual value
									il.MarkLabel(lblCallGetInstance);
									il.Emit(OpCodes.Castclass, typeof(IDuckType));
									il.EmitCall(OpCodes.Callvirt, DuckTypeInstancePropertyInfo.GetMethod, null);
									il.MarkLabel(lblAfterGetInstance);
								}

								// Cast the value to the target type
								il.WriteSafeTypeConversion(proxyParamTypeElementType, targetParamTypeElementType);

								// Store the casted value to the local var
								il.WriteStoreLocal(localTargetArg.LocalIndex);

								// Load the local var ref (to be used in the target method param)
								il.Emit(OpCodes.Ldloca_S, localTargetArg.LocalIndex);
							}
							else
								il.WriteLoadArgument(idx, false);
						}
						else if (!isReverse)
						{
							// Check if the type can be converted of if we need to enable duck chaining
							if (NeedsDuckChaining(targetParamType, proxyParamType))
							{
								// Load the argument and cast it as Duck type
								il.WriteLoadArgument(idx, false);
								il.Emit(OpCodes.Castclass, typeof(IDuckType));

								// Call IDuckType.Instance property to get the actual value
								il.EmitCall(OpCodes.Callvirt, DuckTypeInstancePropertyInfo.GetMethod, null);
							}
							else
								il.WriteLoadArgument(idx, false);

							// If the target parameter type is public or if it's by ref we have to actually use the original target type.
							targetParamType = UseDirectAccessTo(proxyTypeBuilder, targetParamType) ? targetParamType : typeof(object);
							il.WriteSafeTypeConversion(proxyParamType, targetParamType);

							targetMethodParametersTypes[idx] = targetParamType;
						}
						else
						{
							if (NeedsDuckChaining(proxyParamType, targetParamType))
							{
								// Load the argument (our proxy type) and cast it as Duck type (the original type)
								il.WriteLoadArgument(idx, false);
								if (UseDirectAccessTo(proxyTypeBuilder, proxyParamType) && proxyParamType.IsValueType)
									il.Emit(OpCodes.Box, proxyParamType);

								// We call DuckType.CreateCache<>.Create(object instance)
								var getProxyMethodInfo = typeof(CreateCache<>)
									.MakeGenericType(targetParamType)
									.GetMethod("Create");

								il.Emit(OpCodes.Call, getProxyMethodInfo);
							}
							else
								il.WriteLoadArgument(idx, false);

							// If the target parameter type is public or if it's by ref we have to actually use the original target type.
							targetParamType = UseDirectAccessTo(proxyTypeBuilder, targetParamType) ? targetParamType : typeof(object);
							il.WriteSafeTypeConversion(proxyParamType, targetParamType);

							targetMethodParametersTypes[idx] = targetParamType;
						}
					}
				}

				// Call the target method
				if (UseDirectAccessTo(proxyTypeBuilder, targetType))
				{
					// If the instance is public we can emit directly without any dynamic method

					// Create generic method call
					if (proxyMethodDefinitionGenericArguments.Length > 0)
						targetMethod = targetMethod.MakeGenericMethod(proxyMethodDefinitionGenericArguments);

					// Method call
					// A generic method cannot be called using calli (throws System.InvalidOperationException)
					if (targetMethod.IsPublic || targetMethod.IsGenericMethod)
					{
						// We can emit a normal call if we have a public instance with a public target method.
						il.EmitCall(targetMethod.IsStatic || targetMethod.DeclaringType.IsValueType ? OpCodes.Call : OpCodes.Callvirt, targetMethod,
							null);
					}
					else
					{
						// In case we have a public instance and a non public target method we can use [Calli] with the function pointer
						il.WriteMethodCalli(targetMethod);
					}
				}
				else
				{
					// A generic method call can't be made from a DynamicMethod
					if (proxyMethodDefinitionGenericArguments.Length > 0)
						DuckTypeProxyMethodsWithGenericParametersNotSupportedInNonPublicInstancesException.Throw(proxyMethod);

					// If the instance is not public we need to create a Dynamic method to overpass the visibility checks
					// we can't access non public types so we have to cast to object type (in the instance object and the return type).

					var dynMethodName = $"_callMethod_{targetMethod.DeclaringType.Name}_{targetMethod.Name}";
					returnType = UseDirectAccessTo(proxyTypeBuilder, targetMethod.ReturnType) && !targetMethod.ReturnType.IsGenericParameter
						? targetMethod.ReturnType
						: typeof(object);

					// We create the dynamic method
					var originalTargetParameters = targetMethod.GetParameters().Select(p => p.ParameterType).ToArray();
					var targetParameters = targetMethod.IsStatic
						? originalTargetParameters
						: (new[] { typeof(object) }).Concat(originalTargetParameters).ToArray();
					var dynParameters = targetMethod.IsStatic
						? targetMethodParametersTypes
						: (new[] { typeof(object) }).Concat(targetMethodParametersTypes).ToArray();
					var dynMethod = new DynamicMethod(dynMethodName, returnType, dynParameters, proxyTypeBuilder.Module, true);

					// Emit the dynamic method body
					var dynIL = new LazyILGenerator(dynMethod.GetILGenerator());

					if (!targetMethod.IsStatic)
						dynIL.LoadInstanceArgument(typeof(object), targetMethod.DeclaringType);

					for (var idx = targetMethod.IsStatic ? 0 : 1; idx < dynParameters.Length; idx++)
					{
						dynIL.WriteLoadArgument(idx, true);
						dynIL.WriteSafeTypeConversion(dynParameters[idx], targetParameters[idx]);
					}

					// Check if we can emit a normal Call/CallVirt to the target method
					if (!targetMethod.ContainsGenericParameters)
					{
						dynIL.EmitCall(targetMethod.IsStatic || targetMethod.DeclaringType.IsValueType ? OpCodes.Call : OpCodes.Callvirt,
							targetMethod, null);
					}
					else
					{
						// We can't emit a call to a method with generics from a DynamicMethod
						// Instead we emit a Calli with the function pointer.
						dynIL.WriteMethodCalli(targetMethod);
					}

					dynIL.WriteSafeTypeConversion(targetMethod.ReturnType, returnType);
					dynIL.Emit(OpCodes.Ret);
					dynIL.Flush();

					// Emit the call to the dynamic method
					il.WriteDynamicMethodCall(dynMethod, proxyTypeBuilder);
				}

				// We check if we have output or ref parameters to set in the proxy method
				if (outputAndRefParameters != null)
				{
					foreach (var outOrRefParameter in outputAndRefParameters)
					{
						var proxyArgumentType = outOrRefParameter.ProxyArgumentType.GetElementType();
						var localType = outOrRefParameter.LocalType.GetElementType();

						// We load the argument to be set
						il.WriteLoadArgument(outOrRefParameter.ProxyArgumentIndex, false);

						// We load the value from the local
						il.WriteLoadLocal(outOrRefParameter.LocalIndex);

						// If we detect duck chaining we create a new proxy instance with the output of the original target method
						if (NeedsDuckChaining(localType, proxyArgumentType))
						{
							if (localType.IsValueType)
								il.Emit(OpCodes.Box, localType);

							// We call DuckType.CreateCache<>.Create()
							var getProxyMethodInfo = typeof(CreateCache<>)
								.MakeGenericType(proxyArgumentType)
								.GetMethod("Create");

							il.Emit(OpCodes.Call, getProxyMethodInfo);
						}
						else
							il.WriteSafeTypeConversion(localType, proxyArgumentType);

						// We store the value
						il.Emit(OpCodes.Stind_Ref);
					}
				}

				// Check if the target method returns something
				if (targetMethod.ReturnType != typeof(void))
				{
					// Handle the return value
					// Check if the type can be converted or if we need to enable duck chaining
					if (NeedsDuckChaining(targetMethod.ReturnType, proxyMethodDefinition.ReturnType))
					{
						if (UseDirectAccessTo(proxyTypeBuilder, targetMethod.ReturnType) && targetMethod.ReturnType.IsValueType)
							il.Emit(OpCodes.Box, targetMethod.ReturnType);

						// We call DuckType.CreateCache<>.Create()
						var getProxyMethodInfo = typeof(CreateCache<>)
							.MakeGenericType(proxyMethodDefinition.ReturnType)
							.GetMethod("Create");

						il.Emit(OpCodes.Call, getProxyMethodInfo);
					}
					else if (returnType != proxyMethodDefinition.ReturnType)
					{
						// If the type is not the expected type we try a conversion.
						il.WriteSafeTypeConversion(returnType, proxyMethodDefinition.ReturnType);
					}
				}

				il.Emit(OpCodes.Ret);
				il.Flush();
				_methodBuilderGetToken.Invoke(proxyMethod, null);
			}
		}