internal static DynamicMethod CreateEndMethodDelegate()

in src/profiler/Elastic.Apm.Profiler.Managed/CallTarget/Handlers/IntegrationMapper.cs [373:513]


		internal static DynamicMethod CreateEndMethodDelegate(Type integrationType, Type targetType, Type returnType)
		{
			/*
			 * OnMethodEnd signatures with 3 or 4 parameters with 1 or 2 generics:
			 *      - CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, CallTargetState state);
			 *      - CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TReturn returnValue, Exception exception, CallTargetState state);
			 *      - CallTargetReturn<[Type]> OnMethodEnd<TTarget>([Type] returnValue, Exception exception, CallTargetState state);
			 *
			 */

			Logger.Debug(
				$"Creating EndMethod Dynamic Method for '{integrationType.FullName}' integration. [Target={targetType.FullName}, ReturnType={returnType.FullName}]");
			var onMethodEndMethodInfo = integrationType.GetMethod(EndMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
			if (onMethodEndMethodInfo is null)
			{
				Logger.Debug($"'{EndMethodName}' method was not found in integration type: '{integrationType.FullName}'.");
				return null;
			}

			if (onMethodEndMethodInfo.ReturnType.GetGenericTypeDefinition() != typeof(CallTargetReturn<>))
			{
				throw new ArgumentException(
					$"The return type of the method: {EndMethodName} in type: {integrationType.FullName} is not {nameof(CallTargetReturn)}");
			}

			var genericArgumentsTypes = onMethodEndMethodInfo.GetGenericArguments();
			if (genericArgumentsTypes.Length < 1 || genericArgumentsTypes.Length > 2)
			{
				throw new ArgumentException(
					$"The method: {EndMethodName} in type: {integrationType.FullName} must have the generic type for the instance type.");
			}

			var onMethodEndParameters = onMethodEndMethodInfo.GetParameters();
			if (onMethodEndParameters.Length < 3)
			{
				throw new ArgumentException(
					$"The method: {EndMethodName} with {onMethodEndParameters.Length} parameters in type: {integrationType.FullName} has less parameters than required.");
			}
			else if (onMethodEndParameters.Length > 4)
			{
				throw new ArgumentException(
					$"The method: {EndMethodName} with {onMethodEndParameters.Length} parameters in type: {integrationType.FullName} has more parameters than required.");
			}

			if (onMethodEndParameters[onMethodEndParameters.Length - 2].ParameterType != typeof(Exception))
			{
				throw new ArgumentException(
					$"The Exception type parameter of the method: {EndMethodName} in type: {integrationType.FullName} is missing.");
			}

			if (onMethodEndParameters[onMethodEndParameters.Length - 1].ParameterType != typeof(CallTargetState))
			{
				throw new ArgumentException(
					$"The CallTargetState type parameter of the method: {EndMethodName} in type: {integrationType.FullName} is missing.");
			}

			var callGenericTypes = new List<Type>();

			var mustLoadInstance = onMethodEndParameters.Length == 4;
			var instanceGenericType = genericArgumentsTypes[0];
			var instanceGenericConstraint = instanceGenericType.GetGenericParameterConstraints().FirstOrDefault();
			Type instanceProxyType = null;
			if (instanceGenericConstraint != null)
			{
				var result = DuckType.GetOrCreateProxyType(instanceGenericConstraint, targetType);
				instanceProxyType = result.ProxyType;
				callGenericTypes.Add(instanceProxyType);
			}
			else
				callGenericTypes.Add(targetType);

			var returnParameterIndex = onMethodEndParameters.Length == 4 ? 1 : 0;
			var isAGenericReturnValue = onMethodEndParameters[returnParameterIndex].ParameterType.IsGenericParameter;
			Type returnValueGenericType = null;
			Type returnValueGenericConstraint = null;
			Type returnValueProxyType = null;
			if (isAGenericReturnValue)
			{
				returnValueGenericType = genericArgumentsTypes[1];
				returnValueGenericConstraint = returnValueGenericType.GetGenericParameterConstraints().FirstOrDefault();
				if (returnValueGenericConstraint != null)
				{
					var result = DuckType.GetOrCreateProxyType(returnValueGenericConstraint, returnType);
					returnValueProxyType = result.ProxyType;
					callGenericTypes.Add(returnValueProxyType);
				}
				else
					callGenericTypes.Add(returnType);
			}
			else if (onMethodEndParameters[returnParameterIndex].ParameterType != returnType)
			{
				throw new ArgumentException(
					$"The ReturnValue type parameter of the method: {EndMethodName} in type: {integrationType.FullName} is invalid. [{onMethodEndParameters[returnParameterIndex].ParameterType} != {returnType}]");
			}

			var callMethod = new DynamicMethod(
				$"{onMethodEndMethodInfo.DeclaringType.Name}.{onMethodEndMethodInfo.Name}.{targetType.Name}.{returnType.Name}",
				typeof(CallTargetReturn<>).MakeGenericType(returnType),
				new Type[] { targetType, returnType, typeof(Exception), typeof(CallTargetState) },
				onMethodEndMethodInfo.Module,
				true);

			var ilWriter = callMethod.GetILGenerator();

			// Load the instance if is needed
			if (mustLoadInstance)
			{
				ilWriter.Emit(OpCodes.Ldarg_0);

				if (instanceGenericConstraint != null)
					WriteCreateNewProxyInstance(ilWriter, instanceProxyType, targetType);
			}

			// Load the return value
			ilWriter.Emit(OpCodes.Ldarg_1);
			if (returnValueProxyType != null)
				WriteCreateNewProxyInstance(ilWriter, returnValueProxyType, returnType);

			// Load the exception
			ilWriter.Emit(OpCodes.Ldarg_2);

			// Load the state
			ilWriter.Emit(OpCodes.Ldarg_3);

			// Call Method
			onMethodEndMethodInfo = onMethodEndMethodInfo.MakeGenericMethod(callGenericTypes.ToArray());
			ilWriter.EmitCall(OpCodes.Call, onMethodEndMethodInfo, null);

			// Unwrap return value proxy
			if (returnValueProxyType != null)
			{
				var unwrapReturnValue = UnwrapReturnValueMethodInfo.MakeGenericMethod(returnValueProxyType, returnType);
				ilWriter.EmitCall(OpCodes.Call, unwrapReturnValue, null);
			}

			ilWriter.Emit(OpCodes.Ret);

			Logger.Debug(
				$"Created EndMethod Dynamic Method for '{integrationType.FullName}' integration. [Target={targetType.FullName}, ReturnType={returnType.FullName}]");
			return callMethod;
		}