123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- using System.Globalization;
- using System.Linq.Expressions;
- using System.Reflection;
- using Jint.Extensions;
- using Jint.Native;
- using Jint.Native.Function;
- #pragma warning disable IL2067
- #pragma warning disable IL2072
- #pragma warning disable IL3050
- namespace Jint.Runtime.Interop;
- internal sealed class MethodInfoFunction : Function
- {
- private readonly Type _targetType;
- private readonly object? _target;
- private readonly string _name;
- private readonly MethodDescriptor[] _methods;
- private readonly ClrFunction? _fallbackClrFunctionInstance;
- public MethodInfoFunction(
- Engine engine,
- Type targetType,
- object? target,
- string name,
- MethodDescriptor[] methods,
- ClrFunction? fallbackClrFunctionInstance = null)
- : base(engine, engine.Realm, new JsString(name))
- {
- _targetType = targetType;
- _target = target;
- _name = name;
- _methods = methods;
- _fallbackClrFunctionInstance = fallbackClrFunctionInstance;
- _prototype = engine.Realm.Intrinsics.Function.PrototypeObject;
- }
- private static bool IsGenericParameter(object? argObj, Type parameterType)
- {
- if (argObj is null)
- {
- return false;
- }
- var result = InteropHelper.IsAssignableToGenericType(argObj.GetType(), parameterType);
- if (result.Score < 0)
- {
- return false;
- }
- if (parameterType.IsGenericParameter || parameterType.IsGenericType)
- {
- return true;
- }
- return false;
- }
- private static void HandleGenericParameter(object? argObj, Type parameterType, Type[] genericArgTypes)
- {
- if (argObj is null)
- {
- return;
- }
- var result = InteropHelper.IsAssignableToGenericType(argObj.GetType(), parameterType);
- if (result.Score < 0)
- {
- return;
- }
- if (parameterType.IsGenericParameter)
- {
- var genericParamPosition = parameterType.GenericParameterPosition;
- if (genericParamPosition >= 0)
- {
- genericArgTypes[genericParamPosition] = argObj.GetType();
- }
- }
- else if (parameterType.IsGenericType)
- {
- // TPC: maybe we can pull the generic parameters from the arguments?
- var genericArgs = parameterType.GetGenericArguments();
- for (int j = 0; j < genericArgs.Length; ++j)
- {
- var genericArg = genericArgs[j];
- if (genericArg.IsGenericParameter)
- {
- var genericParamPosition = genericArg.GenericParameterPosition;
- if (genericParamPosition >= 0)
- {
- var givenTypeGenericArgs = result.MatchingGivenType.GetGenericArguments();
- genericArgTypes[genericParamPosition] = givenTypeGenericArgs[j];
- }
- }
- }
- }
- else
- {
- return;
- }
- }
- private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, JsCallArguments arguments)
- {
- if (!method.IsGenericMethod)
- {
- return method;
- }
- if (!method.IsGenericMethodDefinition)
- {
- return method;
- }
- var methodInfo = method as MethodInfo;
- if (methodInfo == null)
- {
- // probably should issue at least a warning here
- return method;
- }
- // TPC: we could also && "(method.Method.IsGenericMethodDefinition)" because we won't create a generic method if that isn't the case
- var methodGenericArgs = method.GetGenericArguments();
- var genericArgTypes = new Type[methodGenericArgs.Length];
- for (var i = 0; i < methodParameters.Length; ++i)
- {
- var methodParameter = methodParameters[i];
- var parameterType = methodParameter.ParameterType;
- var argObj = i < arguments.Length ? arguments[i].ToObject() : typeof(object);
- HandleGenericParameter(argObj, parameterType, genericArgTypes);
- }
- for (int i = 0; i < genericArgTypes.Length; ++i)
- {
- if (genericArgTypes[i] == null)
- {
- // this is how we're dealing with things like "void" return types - you can't use "void" as a type:
- genericArgTypes[i] = typeof(object);
- }
- }
- var genericMethodInfo = methodInfo.MakeGenericMethod(genericArgTypes);
- return genericMethodInfo;
- }
- private readonly record struct MethodResolverState(Engine Engine, JsValue This, JsCallArguments Arguments);
- protected internal override JsValue Call(JsValue thisObject, JsCallArguments jsArguments)
- {
- static JsCallArguments ArgumentProvider(MethodDescriptor method, MethodResolverState state)
- {
- if (method.IsExtensionMethod)
- {
- var jsArgumentsTemp = new JsValue[1 + state.Arguments.Length];
- jsArgumentsTemp[0] = state.This;
- Array.Copy(state.Arguments, 0, jsArgumentsTemp, 1, state.Arguments.Length);
- return method.HasParams
- ? ProcessParamsArrays(state.Engine, method, jsArgumentsTemp)
- : jsArgumentsTemp;
- }
- return method.HasParams
- ? ProcessParamsArrays(state.Engine, method, state.Arguments)
- : state.Arguments;
- }
- var converter = Engine.TypeConverter;
- var thisObj = thisObject.ToObject() ?? _target;
- object?[]? parameters = null;
- var state = new MethodResolverState(_engine, thisObject, jsArguments);
- foreach (var (method, arguments, _) in InteropHelper.FindBestMatch(_engine, _methods, ArgumentProvider, state))
- {
- var methodParameters = method.Parameters;
- if (parameters == null || parameters.Length != methodParameters.Length)
- {
- parameters = new object[methodParameters.Length];
- }
- var argumentsMatch = true;
- var resolvedMethod = ResolveMethod(method.Method, methodParameters, arguments);
- // TPC: if we're concerned about cost of MethodInfo.GetParameters() - we could only invoke it if this ends up being a generic method (i.e. they will be different in that scenario)
- methodParameters = resolvedMethod.GetParameters();
- for (var i = 0; i < parameters.Length; i++)
- {
- var methodParameter = methodParameters[i];
- var parameterType = methodParameter.ParameterType;
- var argument = arguments.Length > i ? arguments[i] : null;
- if (typeof(JsValue).IsAssignableFrom(parameterType))
- {
- parameters[i] = argument;
- }
- else if (argument is null)
- {
- // optional
- parameters[i] = System.Type.Missing;
- }
- else if (IsGenericParameter(argument.ToObject(), parameterType)) // don't think we need the condition preface of (argument == null) because of earlier condition
- {
- parameters[i] = argument.ToObject();
- }
- else if (parameterType == typeof(JsValue[]) && argument.IsArray())
- {
- // Handle specific case of F(params JsValue[])
- var arrayInstance = argument.AsArray();
- var len = TypeConverter.ToInt32(arrayInstance.Get(CommonProperties.Length, this));
- var result = new JsValue[len];
- for (uint k = 0; k < len; k++)
- {
- result[k] = arrayInstance.TryGetValue(k, out var value) ? value : Undefined;
- }
- parameters[i] = result;
- }
- else
- {
- if (!ReflectionExtensions.TryConvertViaTypeCoercion(parameterType, _engine.Options.Interop.ValueCoercion, argument, out parameters[i])
- && !converter.TryConvert(argument.ToObject(), parameterType, CultureInfo.InvariantCulture, out parameters[i]))
- {
- argumentsMatch = false;
- break;
- }
- if (parameters[i] is LambdaExpression lambdaExpression)
- {
- parameters[i] = lambdaExpression.Compile();
- }
- }
- }
- if (!argumentsMatch)
- {
- continue;
- }
- // todo: cache method info
- try
- {
- if (method.Method is MethodInfo { IsGenericMethodDefinition: true })
- {
- var result = resolvedMethod.Invoke(thisObj, parameters);
- return FromObjectWithType(Engine, result, type: (resolvedMethod as MethodInfo)?.ReturnType);
- }
- return FromObjectWithType(Engine, method.Method.Invoke(thisObj, parameters), type: (method.Method as MethodInfo)?.ReturnType);
- }
- catch (TargetInvocationException exception)
- {
- Throw.MeaningfulException(_engine, exception);
- }
- }
- if (_fallbackClrFunctionInstance is not null)
- {
- return _fallbackClrFunctionInstance.Call(thisObject, jsArguments);
- }
- Throw.TypeError(_engine.Realm, "No public methods with the specified arguments were found.");
- return null;
- }
- /// <summary>
- /// Reduces a flat list of parameters to a params array, if needed
- /// </summary>
- private static JsCallArguments ProcessParamsArrays(Engine engine, MethodDescriptor methodInfo, JsCallArguments arguments)
- {
- var parameters = methodInfo.Parameters;
- var nonParamsArgumentsCount = parameters.Length - 1;
- if (arguments.Length < nonParamsArgumentsCount)
- {
- return arguments;
- }
- var argsToTransform = arguments.Skip(nonParamsArgumentsCount);
- if (argsToTransform.Length == 1 && argsToTransform[0].IsArray())
- {
- return arguments;
- }
- var array = new JsArray(engine, argsToTransform);
- var newArguments = new JsValue[nonParamsArgumentsCount + 1];
- for (var j = 0; j < nonParamsArgumentsCount; ++j)
- {
- newArguments[j] = arguments[j];
- }
- newArguments[nonParamsArgumentsCount] = array;
- return newArguments;
- }
- public override string ToString()
- {
- return $"function {_targetType}.{_name}() {{ [native code] }}";
- }
- }
|