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;
}