internal static CreateAsyncEndMethodResult CreateAsyncEndMethodDelegate()

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


		internal static CreateAsyncEndMethodResult CreateAsyncEndMethodDelegate(Type integrationType, Type targetType, Type returnType)
		{
			/*
			 * OnAsyncMethodEnd signatures with 3 or 4 parameters with 1 or 2 generics:
			 *      - TReturn OnAsyncMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, CallTargetState state);
			 *      - TReturn OnAsyncMethodEnd<TTarget, TReturn>(TReturn returnValue, Exception exception, CallTargetState state);
			 *      - [Type] OnAsyncMethodEnd<TTarget>([Type] returnValue, Exception exception, CallTargetState state);
			 *
			 *      In case the continuation is for a Task/ValueTask, the returnValue type will be an object and the value null.
			 *      In case the continuation is for a Task<T>/ValueTask<T>, the returnValue type will be T with the instance value after the task completes.
			 *
			 */

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

			if (!onAsyncMethodEndMethodInfo.ReturnType.IsGenericParameter && onAsyncMethodEndMethodInfo.ReturnType != returnType)
			{
				throw new ArgumentException(
					$"The return type of the method: {EndAsyncMethodName} in type: {integrationType.FullName} is not {returnType}");
			}

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

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

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

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

			var preserveContext = onAsyncMethodEndMethodInfo.GetCustomAttribute<PreserveContextAttribute>() != null;

			var callGenericTypes = new List<Type>();

			var mustLoadInstance = onAsyncMethodEndParameters.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 = onAsyncMethodEndParameters.Length == 4 ? 1 : 0;
			var isAGenericReturnValue = onAsyncMethodEndParameters[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 (onAsyncMethodEndParameters[returnParameterIndex].ParameterType != returnType)
			{
				throw new ArgumentException(
					$"The ReturnValue type parameter of the method: {EndAsyncMethodName} in type: {integrationType.FullName} is invalid. [{onAsyncMethodEndParameters[returnParameterIndex].ParameterType} != {returnType}]");
			}

			var callMethod = new DynamicMethod(
				$"{onAsyncMethodEndMethodInfo.DeclaringType.Name}.{onAsyncMethodEndMethodInfo.Name}.{targetType.Name}.{returnType.Name}",
				returnType,
				new Type[] { targetType, returnType, typeof(Exception), typeof(CallTargetState) },
				onAsyncMethodEndMethodInfo.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
			onAsyncMethodEndMethodInfo = onAsyncMethodEndMethodInfo.MakeGenericMethod(callGenericTypes.ToArray());
			ilWriter.EmitCall(OpCodes.Call, onAsyncMethodEndMethodInfo, 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 AsyncEndMethod Dynamic Method for '{integrationType.FullName}' integration. [Target={targetType.FullName}, ReturnType={returnType.FullName}]");
			return new CreateAsyncEndMethodResult(callMethod, preserveContext);
		}