using System.Globalization; using System.Reflection; using Jint.Extensions; using Jint.Native; using Jint.Native.Function; #pragma warning disable IL2072 #pragma warning disable IL3050 namespace Jint.Runtime.Interop; /// /// Represents a FunctionInstance wrapper around a CLR method. This is used by user to pass /// custom methods to the engine. /// internal sealed class DelegateWrapper : Function { private static readonly JsString _name = new JsString("delegate"); private readonly Delegate _d; private readonly bool _delegateContainsParamsArgument; public DelegateWrapper( Engine engine, Delegate d) : base(engine, engine.Realm, _name, FunctionThisMode.Global) { _d = d; _prototype = engine.Realm.Intrinsics.Function.PrototypeObject; var parameterInfos = _d.Method.GetParameters(); _delegateContainsParamsArgument = false; foreach (var p in parameterInfos) { if (Attribute.IsDefined(p, typeof(ParamArrayAttribute))) { _delegateContainsParamsArgument = true; break; } } } protected internal override JsValue Call(JsValue thisObject, JsCallArguments arguments) { var parameterInfos = _d.Method.GetParameters(); #if NETFRAMEWORK if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(System.Runtime.CompilerServices.Closure)) { var reducedLength = parameterInfos.Length - 1; var reducedParameterInfos = new ParameterInfo[reducedLength]; Array.Copy(parameterInfos, 1, reducedParameterInfos, 0, reducedLength); parameterInfos = reducedParameterInfos; } #endif int delegateArgumentsCount = parameterInfos.Length; int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount; int jsArgumentsCount = arguments.Length; int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount); var clrTypeConverter = Engine.TypeConverter; var valueCoercionType = Engine.Options.Interop.ValueCoercion; var parameters = new object?[delegateArgumentsCount]; // convert non params parameter to expected types for (var i = 0; i < jsArgumentsWithoutParamsCount; i++) { var parameterType = parameterInfos[i].ParameterType; var value = arguments[i]; object? converted; if (typeof(JsValue).IsAssignableFrom(parameterType)) { converted = value; } else if (!ReflectionExtensions.TryConvertViaTypeCoercion(parameterType, valueCoercionType, value, out converted)) { converted = clrTypeConverter.Convert( value.ToObject(), parameterType, CultureInfo.InvariantCulture); } parameters[i] = converted; } // assign null to parameters not provided for (var i = jsArgumentsWithoutParamsCount; i < delegateNonParamsArgumentsCount; i++) { if (parameterInfos[i].ParameterType.IsValueType) { parameters[i] = Activator.CreateInstance(parameterInfos[i].ParameterType); } else { parameters[i] = null; } } // assign params to array and converts each object to expected type if (_delegateContainsParamsArgument) { int paramsArgumentIndex = delegateArgumentsCount - 1; int paramsCount = Math.Max(0, jsArgumentsCount - delegateNonParamsArgumentsCount); var paramsParameterType = parameterInfos[paramsArgumentIndex].ParameterType.GetElementType(); var paramsParameter = Array.CreateInstance(paramsParameterType!, paramsCount); for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++) { var paramsIndex = i - paramsArgumentIndex; var value = arguments[i]; object? converted; if (paramsParameterType == typeof(JsValue)) { converted = value; } else if (!ReflectionExtensions.TryConvertViaTypeCoercion(paramsParameterType, valueCoercionType, value, out converted)) { converted = Engine.TypeConverter.Convert( value.ToObject(), paramsParameterType!, CultureInfo.InvariantCulture); } paramsParameter.SetValue(converted, paramsIndex); } parameters[paramsArgumentIndex] = paramsParameter; } try { var result = _d.DynamicInvoke(parameters); if (!IsAwaitable(result)) { return FromObject(Engine, result); } return ConvertAwaitableToPromise(Engine, result!); } catch (TargetInvocationException exception) { Throw.MeaningfulException(Engine, exception); throw; } } private static bool IsAwaitable(object? obj) { if (obj is null) { return false; } if (obj is Task) { return true; } #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP if (obj is ValueTask) { return true; } // ValueTask is not derived from ValueTask, so we need to check for it explicitly var type = obj.GetType(); if (!type.IsGenericType) { return false; } return type.GetGenericTypeDefinition() == typeof(ValueTask<>); #else return false; #endif } }