2
0

DelegateWrapper.cs 5.8 KB

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