DefaultTypeConverter.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using System.Collections.Concurrent;
  2. using System.Collections.ObjectModel;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using System.Reflection;
  7. using System.Runtime.CompilerServices;
  8. using Jint.Extensions;
  9. using Jint.Native;
  10. using Jint.Native.Function;
  11. using Jint.Native.Object;
  12. using Jint.Runtime.Descriptors;
  13. using Expression = System.Linq.Expressions.Expression;
  14. #pragma warning disable IL2026
  15. #pragma warning disable IL2067
  16. #pragma warning disable IL2070
  17. #pragma warning disable IL2072
  18. #pragma warning disable IL3050
  19. namespace Jint.Runtime.Interop;
  20. public class DefaultTypeConverter : ITypeConverter
  21. {
  22. private readonly Engine _engine;
  23. private readonly record struct TypeConversionKey(Type Source, Type Target);
  24. private static readonly ConcurrentDictionary<TypeConversionKey, MethodInfo?> _knownCastOperators = new();
  25. private static readonly Type intType = typeof(int);
  26. private static readonly Type iCallableType = typeof(JsCallDelegate);
  27. private static readonly Type jsValueType = typeof(JsValue);
  28. private static readonly Type objectType = typeof(object);
  29. private static readonly Type engineType = typeof(Engine);
  30. private static readonly MethodInfo changeTypeIfConvertible = typeof(DefaultTypeConverter).GetMethod(
  31. "ChangeTypeOnlyIfConvertible", BindingFlags.NonPublic | BindingFlags.Static)!;
  32. private static readonly MethodInfo jsValueFromObject = jsValueType.GetMethod(nameof(JsValue.FromObject))!;
  33. private static readonly MethodInfo jsValueToObject = jsValueType.GetMethod(nameof(JsValue.ToObject))!;
  34. public DefaultTypeConverter(Engine engine)
  35. {
  36. _engine = engine;
  37. }
  38. public virtual object? Convert(
  39. object? value,
  40. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields)] Type type,
  41. IFormatProvider formatProvider)
  42. {
  43. if (!TryConvert(value, type, formatProvider, propagateException: true, out var converted, out var problemMessage))
  44. {
  45. ExceptionHelper.ThrowError(_engine, problemMessage ?? $"Unable to convert {value} to type {type}");
  46. }
  47. return converted;
  48. }
  49. public virtual bool TryConvert(
  50. object? value,
  51. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields)] Type type,
  52. IFormatProvider formatProvider,
  53. [NotNullWhen(true)] out object? converted)
  54. {
  55. return TryConvert(value, type, formatProvider, propagateException: false, out converted, out _);
  56. }
  57. private static readonly ConditionalWeakTable<IFunction, Delegate> _delegateCache = new();
  58. private bool TryConvert(
  59. object? value,
  60. [DynamicallyAccessedMembers(InteropHelper.DefaultDynamicallyAccessedMemberTypes)] Type type,
  61. IFormatProvider formatProvider,
  62. bool propagateException,
  63. out object? converted,
  64. out string? problemMessage)
  65. {
  66. converted = null;
  67. problemMessage = null;
  68. if (value is null)
  69. {
  70. if (InteropHelper.TypeIsNullable(type))
  71. {
  72. return true;
  73. }
  74. problemMessage = $"Unable to convert null to '{type.FullName}'";
  75. return false;
  76. }
  77. // don't try to convert if value is derived from type
  78. if (type.IsInstanceOfType(value))
  79. {
  80. converted = value;
  81. return true;
  82. }
  83. if (type.IsGenericType)
  84. {
  85. var result = InteropHelper.IsAssignableToGenericType(value.GetType(), type);
  86. if (result.IsAssignable)
  87. {
  88. converted = value;
  89. return true;
  90. }
  91. }
  92. if (type.IsNullable())
  93. {
  94. type = Nullable.GetUnderlyingType(type)!;
  95. }
  96. if (type.IsEnum)
  97. {
  98. var integer = System.Convert.ChangeType(value, intType, formatProvider);
  99. if (integer == null)
  100. {
  101. ExceptionHelper.ThrowArgumentOutOfRangeException();
  102. }
  103. converted = Enum.ToObject(type, integer);
  104. return true;
  105. }
  106. var valueType = value.GetType();
  107. // is the javascript value an ICallable instance ?
  108. if (valueType == iCallableType)
  109. {
  110. if (typeof(Delegate).IsAssignableFrom(type) && !type.IsAbstract)
  111. {
  112. var func = (JsCallDelegate) value;
  113. var astFunction = (func.Target as Function)?._functionDefinition?.Function;
  114. var d = astFunction is not null
  115. ? _delegateCache.GetValue(astFunction, _ => BuildDelegate(type, func))
  116. : BuildDelegate(type, func);
  117. converted = d;
  118. return true;
  119. }
  120. }
  121. if (type.IsArray)
  122. {
  123. if (value is not object[] source)
  124. {
  125. problemMessage = $"Value of object[] type is expected, but actual type is {value.GetType()}";
  126. return false;
  127. }
  128. var targetElementType = type.GetElementType()!;
  129. var itemsConverted = new object?[source.Length];
  130. for (var i = 0; i < source.Length; i++)
  131. {
  132. itemsConverted[i] = Convert(source[i], targetElementType, formatProvider);
  133. }
  134. var result = Array.CreateInstance(targetElementType, source.Length);
  135. itemsConverted.CopyTo(result, 0);
  136. converted = result;
  137. return true;
  138. }
  139. var typeDescriptor = TypeDescriptor.Get(valueType);
  140. if (typeDescriptor.IsStringKeyedGenericDictionary)
  141. {
  142. // public empty constructor required
  143. var constructors = type.GetConstructors();
  144. // value types
  145. if (type.IsValueType && constructors.Length > 0)
  146. {
  147. problemMessage = $"No valid constructors found for {type}";
  148. return false;
  149. }
  150. var constructorParameters = Array.Empty<object>();
  151. // reference types - return null if no valid constructor is found
  152. if (!type.IsValueType)
  153. {
  154. var found = false;
  155. foreach (var constructor in constructors)
  156. {
  157. if (constructor.GetParameters().Length == 0 && constructor.IsPublic)
  158. {
  159. found = true;
  160. break;
  161. }
  162. }
  163. if (!found)
  164. {
  165. foreach (var constructor in constructors)
  166. {
  167. var parameterInfos = constructor.GetParameters();
  168. if (Array.TrueForAll(parameterInfos, static p => p.IsOptional) && constructor.IsPublic)
  169. {
  170. constructorParameters = new object[parameterInfos.Length];
  171. found = true;
  172. break;
  173. }
  174. }
  175. }
  176. if (!found)
  177. {
  178. problemMessage = $"No valid constructors found for type {type}";
  179. return false;
  180. }
  181. }
  182. var obj = Activator.CreateInstance(type, constructorParameters)!;
  183. var members = type.GetMembers();
  184. foreach (var member in members)
  185. {
  186. // only use fields an properties
  187. if (member.MemberType != MemberTypes.Property &&
  188. member.MemberType != MemberTypes.Field)
  189. {
  190. continue;
  191. }
  192. if (typeDescriptor.TryGetValue(value, member.Name, out var val)
  193. || typeDescriptor.TryGetValue(value, member.Name.UpperToLowerCamelCase(), out val))
  194. {
  195. var output = Convert(val, member.GetDefinedType(), formatProvider);
  196. member.SetValue(obj, output);
  197. }
  198. }
  199. converted = obj;
  200. return true;
  201. }
  202. try
  203. {
  204. converted = System.Convert.ChangeType(value, type, formatProvider);
  205. return true;
  206. }
  207. catch (Exception e)
  208. {
  209. // check if we can do a cast with operator overloading
  210. if (TryCastWithOperators(value, type, valueType, out var invoke))
  211. {
  212. converted = invoke;
  213. return true;
  214. }
  215. if (propagateException && !_engine.Options.Interop.ExceptionHandler(e))
  216. {
  217. throw;
  218. }
  219. problemMessage = e.Message;
  220. return false;
  221. }
  222. }
  223. private Delegate BuildDelegate(
  224. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type,
  225. JsCallDelegate function)
  226. {
  227. var method = type.GetMethod("Invoke");
  228. var arguments = method!.GetParameters();
  229. var parameters = new ParameterExpression[arguments.Length];
  230. for (var i = 0; i < parameters.Length; i++)
  231. {
  232. parameters[i] = Expression.Parameter(arguments[i].ParameterType, arguments[i].Name);
  233. }
  234. var initializers = new List<MethodCallExpression>(parameters.Length);
  235. for (var i = 0; i < parameters.Length; i++)
  236. {
  237. var param = parameters[i];
  238. if (param.Type.IsValueType)
  239. {
  240. var boxing = Expression.Convert(param, objectType);
  241. initializers.Add(Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxing));
  242. }
  243. else if (param.Type.IsArray &&
  244. arguments[i].GetCustomAttribute<ParamArrayAttribute>() is not null &&
  245. function.Target is Function instance)
  246. {
  247. for (var j = 0; j < instance.GetLength(); j++)
  248. {
  249. var returnLabel = Expression.Label(typeof(object));
  250. var checkIndex = Expression.GreaterThanOrEqual(Expression.Property(param, nameof(Array.Length)), Expression.Constant(j));
  251. var condition = Expression.IfThen(checkIndex, Expression.Return(returnLabel, Expression.ArrayAccess(param, Expression.Constant(j))));
  252. var block = Expression.Block(condition, Expression.Label(returnLabel, Expression.Constant(JsValue.Undefined)));
  253. initializers.Add(Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), block));
  254. }
  255. }
  256. else
  257. {
  258. initializers.Add(Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), param));
  259. }
  260. }
  261. var vars = Expression.NewArrayInit(jsValueType, initializers);
  262. var callExpression = Expression.Call(
  263. Expression.Constant(function.Target),
  264. function.Method,
  265. Expression.Constant(JsValue.Undefined, jsValueType),
  266. vars);
  267. if (method.ReturnType != typeof(void))
  268. {
  269. return Expression.Lambda(
  270. type,
  271. Expression.Convert(
  272. Expression.Call(
  273. null,
  274. changeTypeIfConvertible,
  275. Expression.Call(callExpression, jsValueToObject),
  276. Expression.Constant(method.ReturnType),
  277. Expression.Constant(System.Globalization.CultureInfo.InvariantCulture, typeof(IFormatProvider))
  278. ),
  279. method.ReturnType
  280. ),
  281. new ReadOnlyCollection<ParameterExpression>(parameters)).Compile();
  282. }
  283. return Expression.Lambda(
  284. type,
  285. callExpression,
  286. new ReadOnlyCollection<ParameterExpression>(parameters)).Compile();
  287. }
  288. [return: NotNullIfNotNull(nameof(value))]
  289. private static object? ChangeTypeOnlyIfConvertible(object? value, Type conversionType, IFormatProvider? provider)
  290. {
  291. if (value == null || value is IConvertible)
  292. return System.Convert.ChangeType(value, conversionType, provider);
  293. return value;
  294. }
  295. private static bool TryCastWithOperators(object value, Type type, Type valueType, [NotNullWhen(true)] out object? converted)
  296. {
  297. var key = new TypeConversionKey(valueType, type);
  298. static MethodInfo? CreateValueFactory(TypeConversionKey k)
  299. {
  300. var (source, target) = k;
  301. foreach (var m in source.GetOperatorOverloadMethods().Concat(target.GetOperatorOverloadMethods()))
  302. {
  303. if (!target.IsAssignableFrom(m.ReturnType) || m.Name is not ("op_Implicit" or "op_Explicit"))
  304. {
  305. continue;
  306. }
  307. var parameters = m.GetParameters();
  308. if (parameters.Length != 1 || !parameters[0].ParameterType.IsAssignableFrom(source))
  309. {
  310. continue;
  311. }
  312. // we found a match
  313. return m;
  314. }
  315. return null;
  316. }
  317. var castOperator = _knownCastOperators.GetOrAdd(key, CreateValueFactory);
  318. if (castOperator != null)
  319. {
  320. try
  321. {
  322. converted = castOperator.Invoke(null, [value]);
  323. return converted is not null;
  324. }
  325. catch
  326. {
  327. converted = null;
  328. return false;
  329. }
  330. }
  331. converted = null;
  332. return false;
  333. }
  334. }