DelegateWrapper.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using System.Globalization;
  2. using System.Reflection;
  3. using Jint.Extensions;
  4. using Jint.Native;
  5. using Jint.Native.Function;
  6. #pragma warning disable IL2072
  7. #pragma warning disable IL3050
  8. namespace Jint.Runtime.Interop
  9. {
  10. /// <summary>
  11. /// Represents a FunctionInstance wrapper around a CLR method. This is used by user to pass
  12. /// custom methods to the engine.
  13. /// </summary>
  14. internal sealed class DelegateWrapper : Function
  15. {
  16. private static readonly JsString _name = new JsString("delegate");
  17. private readonly Delegate _d;
  18. private readonly bool _delegateContainsParamsArgument;
  19. public DelegateWrapper(
  20. Engine engine, Delegate d)
  21. : base(engine, engine.Realm, _name, FunctionThisMode.Global)
  22. {
  23. _d = d;
  24. _prototype = engine.Realm.Intrinsics.Function.PrototypeObject;
  25. var parameterInfos = _d.Method.GetParameters();
  26. _delegateContainsParamsArgument = false;
  27. foreach (var p in parameterInfos)
  28. {
  29. if (Attribute.IsDefined(p, typeof(ParamArrayAttribute)))
  30. {
  31. _delegateContainsParamsArgument = true;
  32. break;
  33. }
  34. }
  35. }
  36. protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
  37. {
  38. var parameterInfos = _d.Method.GetParameters();
  39. #if NETFRAMEWORK
  40. if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(System.Runtime.CompilerServices.Closure))
  41. {
  42. var reducedLength = parameterInfos.Length - 1;
  43. var reducedParameterInfos = new ParameterInfo[reducedLength];
  44. Array.Copy(parameterInfos, 1, reducedParameterInfos, 0, reducedLength);
  45. parameterInfos = reducedParameterInfos;
  46. }
  47. #endif
  48. int delegateArgumentsCount = parameterInfos.Length;
  49. int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount;
  50. int jsArgumentsCount = arguments.Length;
  51. int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount);
  52. var clrTypeConverter = Engine.TypeConverter;
  53. var valueCoercionType = Engine.Options.Interop.ValueCoercion;
  54. var parameters = new object?[delegateArgumentsCount];
  55. // convert non params parameter to expected types
  56. for (var i = 0; i < jsArgumentsWithoutParamsCount; i++)
  57. {
  58. var parameterType = parameterInfos[i].ParameterType;
  59. var value = arguments[i];
  60. object? converted;
  61. if (parameterType == typeof(JsValue))
  62. {
  63. converted = value;
  64. }
  65. else if (!ReflectionExtensions.TryConvertViaTypeCoercion(parameterType, valueCoercionType, value, out converted))
  66. {
  67. converted = clrTypeConverter.Convert(
  68. value.ToObject(),
  69. parameterType,
  70. CultureInfo.InvariantCulture);
  71. }
  72. parameters[i] = converted;
  73. }
  74. // assign null to parameters not provided
  75. for (var i = jsArgumentsWithoutParamsCount; i < delegateNonParamsArgumentsCount; i++)
  76. {
  77. if (parameterInfos[i].ParameterType.IsValueType)
  78. {
  79. parameters[i] = Activator.CreateInstance(parameterInfos[i].ParameterType);
  80. }
  81. else
  82. {
  83. parameters[i] = null;
  84. }
  85. }
  86. // assign params to array and converts each object to expected type
  87. if (_delegateContainsParamsArgument)
  88. {
  89. int paramsArgumentIndex = delegateArgumentsCount - 1;
  90. int paramsCount = Math.Max(0, jsArgumentsCount - delegateNonParamsArgumentsCount);
  91. var paramsParameterType = parameterInfos[paramsArgumentIndex].ParameterType.GetElementType();
  92. var paramsParameter = Array.CreateInstance(paramsParameterType!, paramsCount);
  93. for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++)
  94. {
  95. var paramsIndex = i - paramsArgumentIndex;
  96. var value = arguments[i];
  97. object? converted;
  98. if (paramsParameterType == typeof(JsValue))
  99. {
  100. converted = value;
  101. }
  102. else if (!ReflectionExtensions.TryConvertViaTypeCoercion(paramsParameterType, valueCoercionType, value, out converted))
  103. {
  104. converted = Engine.TypeConverter.Convert(
  105. value.ToObject(),
  106. paramsParameterType!,
  107. CultureInfo.InvariantCulture);
  108. }
  109. paramsParameter.SetValue(converted, paramsIndex);
  110. }
  111. parameters[paramsArgumentIndex] = paramsParameter;
  112. }
  113. try
  114. {
  115. var result = _d.DynamicInvoke(parameters);
  116. if (!IsAwaitable(result))
  117. {
  118. return FromObject(Engine, result);
  119. }
  120. return ConvertAwaitableToPromise(Engine, result!);
  121. }
  122. catch (TargetInvocationException exception)
  123. {
  124. ExceptionHelper.ThrowMeaningfulException(Engine, exception);
  125. throw;
  126. }
  127. }
  128. private static bool IsAwaitable(object? obj)
  129. {
  130. if (obj is null)
  131. {
  132. return false;
  133. }
  134. if (obj is Task)
  135. {
  136. return true;
  137. }
  138. #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
  139. if (obj is ValueTask)
  140. {
  141. return true;
  142. }
  143. // ValueTask<T> is not derived from ValueTask, so we need to check for it explicitly
  144. var type = obj.GetType();
  145. if (!type.IsGenericType)
  146. {
  147. return false;
  148. }
  149. return type.GetGenericTypeDefinition() == typeof(ValueTask<>);
  150. #else
  151. return false;
  152. #endif
  153. }
  154. }
  155. }