DefaultTypeConverter.cs 15 KB

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