MethodInfoFunctionInstance.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. using System;
  2. using System.Globalization;
  3. using System.Linq.Expressions;
  4. using System.Reflection;
  5. using Jint.Native;
  6. using Jint.Native.Function;
  7. namespace Jint.Runtime.Interop
  8. {
  9. public sealed class MethodInfoFunctionInstance : FunctionInstance
  10. {
  11. private readonly MethodInfo[] _methods;
  12. public MethodInfoFunctionInstance(Engine engine, MethodInfo[] methods)
  13. : base(engine, "Function", null, null, false)
  14. {
  15. _methods = methods;
  16. Prototype = engine.Function.PrototypeObject;
  17. }
  18. public override JsValue Call(JsValue thisObject, JsValue[] arguments)
  19. {
  20. return Invoke(_methods, thisObject, arguments);
  21. }
  22. public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] jsArguments)
  23. {
  24. var arguments = ProcessParamsArrays(jsArguments, methodInfos);
  25. var converter = Engine.ClrTypeConverter;
  26. foreach (var method in TypeConverter.FindBestMatch(methodInfos, arguments))
  27. {
  28. var parameters = new object[arguments.Length];
  29. var methodParameters = method.GetParameters();
  30. var argumentsMatch = true;
  31. for (var i = 0; i < arguments.Length; i++)
  32. {
  33. var parameterType = methodParameters[i].ParameterType;
  34. if (typeof(JsValue).IsAssignableFrom(parameterType))
  35. {
  36. parameters[i] = arguments[i];
  37. }
  38. else if (parameterType == typeof(JsValue[]) && arguments[i].IsArray())
  39. {
  40. // Handle specific case of F(params JsValue[])
  41. var arrayInstance = arguments[i].AsArray();
  42. var len = TypeConverter.ToInt32(arrayInstance.Get("length"));
  43. var result = new JsValue[len];
  44. for (uint k = 0; k < len; k++)
  45. {
  46. result[k] = arrayInstance.TryGetValue(k, out var value) ? value : JsValue.Undefined;
  47. }
  48. parameters[i] = result;
  49. }
  50. else
  51. {
  52. if (!converter.TryConvert(arguments[i].ToObject(), parameterType, CultureInfo.InvariantCulture, out parameters[i]))
  53. {
  54. argumentsMatch = false;
  55. break;
  56. }
  57. if (parameters[i] is LambdaExpression lambdaExpression)
  58. {
  59. parameters[i] = lambdaExpression.Compile();
  60. }
  61. }
  62. }
  63. if (!argumentsMatch)
  64. {
  65. continue;
  66. }
  67. // todo: cache method info
  68. try
  69. {
  70. return FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters));
  71. }
  72. catch (TargetInvocationException exception)
  73. {
  74. var meaningfulException = exception.InnerException ?? exception;
  75. var handler = Engine.Options._ClrExceptionsHandler;
  76. if (handler != null && handler(meaningfulException))
  77. {
  78. ExceptionHelper.ThrowError(_engine, meaningfulException.Message);
  79. }
  80. throw meaningfulException;
  81. }
  82. }
  83. ExceptionHelper.ThrowTypeError(_engine, "No public methods with the specified arguments were found.");
  84. return null;
  85. }
  86. /// <summary>
  87. /// Reduces a flat list of parameters to a params array
  88. /// </summary>
  89. private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodInfo[] methodInfos)
  90. {
  91. foreach (var methodInfo in methodInfos)
  92. {
  93. var parameters = methodInfo.GetParameters();
  94. bool hasParamArrayAttribute = false;
  95. foreach (var parameter in parameters)
  96. {
  97. if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
  98. {
  99. hasParamArrayAttribute = true;
  100. break;
  101. }
  102. }
  103. if (!hasParamArrayAttribute)
  104. continue;
  105. var nonParamsArgumentsCount = parameters.Length - 1;
  106. if (jsArguments.Length < nonParamsArgumentsCount)
  107. continue;
  108. var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount);
  109. if (argsToTransform.Length == 1 && argsToTransform[0].IsArray())
  110. continue;
  111. var jsArray = Engine.Array.Construct(Arguments.Empty);
  112. Engine.Array.PrototypeObject.Push(jsArray, argsToTransform);
  113. var newArgumentsCollection = new JsValue[nonParamsArgumentsCount + 1];
  114. for (var j = 0; j < nonParamsArgumentsCount; ++j)
  115. {
  116. newArgumentsCollection[j] = jsArguments[j];
  117. }
  118. newArgumentsCollection[nonParamsArgumentsCount] = jsArray;
  119. return newArgumentsCollection;
  120. }
  121. return jsArguments;
  122. }
  123. }
  124. }