DefaultTypeConverter.cs 15 KB

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