in src/Elastic.Apm/Libraries/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs [74:234]
private void GenerateCreateMethodCallIL(MethodBase method, ILGenerator generator, int argsIndex)
{
var args = method.GetParameters();
var argsOk = generator.DefineLabel();
// throw an error if the number of argument values doesn't match method parameters
generator.Emit(OpCodes.Ldarg, argsIndex);
generator.Emit(OpCodes.Ldlen);
generator.Emit(OpCodes.Ldc_I4, args.Length);
generator.Emit(OpCodes.Beq, argsOk);
generator.Emit(OpCodes.Newobj, typeof(TargetParameterCountException).GetConstructor(ReflectionUtils.EmptyTypes));
generator.Emit(OpCodes.Throw);
generator.MarkLabel(argsOk);
if (!method.IsConstructor && !method.IsStatic) generator.PushInstance(method.DeclaringType);
var localConvertible = generator.DeclareLocal(typeof(IConvertible));
var localObject = generator.DeclareLocal(typeof(object));
var variableAddressOpCode = args.Length < 256 ? OpCodes.Ldloca_S : OpCodes.Ldloca;
var variableLoadOpCode = args.Length < 256 ? OpCodes.Ldloc_S : OpCodes.Ldloc;
for (var i = 0; i < args.Length; i++)
{
var parameter = args[i];
var parameterType = parameter.ParameterType;
if (parameterType.IsByRef)
{
parameterType = parameterType.GetElementType();
var localVariable = generator.DeclareLocal(parameterType);
// don't need to set variable for 'out' parameter
if (!parameter.IsOut)
{
generator.PushArrayInstance(argsIndex, i);
if (parameterType.IsValueType())
{
var skipSettingDefault = generator.DefineLabel();
var finishedProcessingParameter = generator.DefineLabel();
// check if parameter is not null
generator.Emit(OpCodes.Brtrue_S, skipSettingDefault);
// parameter has no value, initialize to default
generator.Emit(variableAddressOpCode, localVariable);
generator.Emit(OpCodes.Initobj, parameterType);
generator.Emit(OpCodes.Br_S, finishedProcessingParameter);
// parameter has value, get value from array again and unbox and set to variable
generator.MarkLabel(skipSettingDefault);
generator.PushArrayInstance(argsIndex, i);
generator.UnboxIfNeeded(parameterType);
generator.Emit(OpCodes.Stloc_S, localVariable);
// parameter finished, we out!
generator.MarkLabel(finishedProcessingParameter);
}
else
{
generator.UnboxIfNeeded(parameterType);
generator.Emit(OpCodes.Stloc_S, localVariable);
}
}
generator.Emit(variableAddressOpCode, localVariable);
}
else if (parameterType.IsValueType())
{
generator.PushArrayInstance(argsIndex, i);
generator.Emit(OpCodes.Stloc_S, localObject);
// have to check that value type parameters aren't null
// otherwise they will error when unboxed
var skipSettingDefault = generator.DefineLabel();
var finishedProcessingParameter = generator.DefineLabel();
// check if parameter is not null
generator.Emit(OpCodes.Ldloc_S, localObject);
generator.Emit(OpCodes.Brtrue_S, skipSettingDefault);
// parameter has no value, initialize to default
var localVariable = generator.DeclareLocal(parameterType);
generator.Emit(variableAddressOpCode, localVariable);
generator.Emit(OpCodes.Initobj, parameterType);
generator.Emit(variableLoadOpCode, localVariable);
generator.Emit(OpCodes.Br_S, finishedProcessingParameter);
// argument has value, try to convert it to parameter type
generator.MarkLabel(skipSettingDefault);
if (parameterType.IsPrimitive())
{
// for primitive types we need to handle type widening (e.g. short -> int)
var toParameterTypeMethod = typeof(IConvertible)
.GetMethod("To" + parameterType.Name, new[] { typeof(IFormatProvider) });
if (toParameterTypeMethod != null)
{
var skipConvertible = generator.DefineLabel();
// check if argument type is an exact match for parameter type
// in this case we may use cheap unboxing instead
generator.Emit(OpCodes.Ldloc_S, localObject);
generator.Emit(OpCodes.Isinst, parameterType);
generator.Emit(OpCodes.Brtrue_S, skipConvertible);
// types don't match, check if argument implements IConvertible
generator.Emit(OpCodes.Ldloc_S, localObject);
generator.Emit(OpCodes.Isinst, typeof(IConvertible));
generator.Emit(OpCodes.Stloc_S, localConvertible);
generator.Emit(OpCodes.Ldloc_S, localConvertible);
generator.Emit(OpCodes.Brfalse_S, skipConvertible);
// convert argument to parameter type
generator.Emit(OpCodes.Ldloc_S, localConvertible);
generator.Emit(OpCodes.Ldnull);
generator.Emit(OpCodes.Callvirt, toParameterTypeMethod);
generator.Emit(OpCodes.Br_S, finishedProcessingParameter);
generator.MarkLabel(skipConvertible);
}
}
// we got here because either argument type matches parameter (conversion will succeed),
// or argument type doesn't match parameter, but we're out of options (conversion will fail)
generator.Emit(OpCodes.Ldloc_S, localObject);
generator.UnboxIfNeeded(parameterType);
// parameter finished, we out!
generator.MarkLabel(finishedProcessingParameter);
}
else
{
generator.PushArrayInstance(argsIndex, i);
generator.UnboxIfNeeded(parameterType);
}
}
if (method.IsConstructor)
generator.Emit(OpCodes.Newobj, (ConstructorInfo)method);
else
generator.CallMethod((MethodInfo)method);
var returnType = method.IsConstructor
? method.DeclaringType
: ((MethodInfo)method).ReturnType;
if (returnType != typeof(void))
generator.BoxIfNeeded(returnType);
else
generator.Emit(OpCodes.Ldnull);
generator.Return();
}