TypeConverter.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Reflection;
  5. using System.Runtime.CompilerServices;
  6. using Esprima.Ast;
  7. using Jint.Native;
  8. using Jint.Native.Number;
  9. using Jint.Native.Number.Dtoa;
  10. using Jint.Native.Object;
  11. using Jint.Native.String;
  12. using Jint.Pooling;
  13. using Jint.Runtime.References;
  14. namespace Jint.Runtime
  15. {
  16. public enum Types
  17. {
  18. None = 0,
  19. Undefined = 1,
  20. Null = 2,
  21. Boolean = 3,
  22. String = 4,
  23. Number = 5,
  24. Symbol = 9,
  25. Object = 10,
  26. Completion = 20,
  27. }
  28. public static class TypeConverter
  29. {
  30. // how many decimals to check when determining if double is actually an int
  31. private const double DoubleIsIntegerTolerance = double.Epsilon * 100;
  32. private static readonly string[] intToString = new string[1024];
  33. private static readonly string[] charToString = new string[256];
  34. static TypeConverter()
  35. {
  36. for (var i = 0; i < intToString.Length; ++i)
  37. {
  38. intToString[i] = i.ToString();
  39. }
  40. for (var i = 0; i < charToString.Length; ++i)
  41. {
  42. var c = (char) i;
  43. charToString[i] = c.ToString();
  44. }
  45. }
  46. /// <summary>
  47. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.1
  48. /// </summary>
  49. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  50. public static JsValue ToPrimitive(JsValue input, Types preferredType = Types.None)
  51. {
  52. if (input._type > Types.None && input._type < Types.Object)
  53. {
  54. return input;
  55. }
  56. return input.AsObject().DefaultValue(preferredType);
  57. }
  58. /// <summary>
  59. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
  60. /// </summary>
  61. public static bool ToBoolean(JsValue o)
  62. {
  63. switch (o._type)
  64. {
  65. case Types.Boolean:
  66. return ((JsBoolean) o)._value;
  67. case Types.Undefined:
  68. case Types.Null:
  69. return false;
  70. case Types.Number:
  71. var n = ((JsNumber) o)._value;
  72. return n != 0 && !double.IsNaN(n);
  73. case Types.String:
  74. return !((JsString) o).IsNullOrEmpty();
  75. default:
  76. return true;
  77. }
  78. }
  79. /// <summary>
  80. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.3
  81. /// </summary>
  82. public static double ToNumber(JsValue o)
  83. {
  84. switch (o._type)
  85. {
  86. // check number first as this is what is usually expected
  87. case Types.Number:
  88. return ((JsNumber) o)._value;
  89. case Types.Undefined:
  90. return double.NaN;
  91. case Types.Null:
  92. return 0;
  93. case Types.Object when o is IPrimitiveInstance p:
  94. return ToNumber(ToPrimitive(p.PrimitiveValue, Types.Number));
  95. case Types.Boolean:
  96. return ((JsBoolean) o)._value ? 1 : 0;
  97. case Types.String:
  98. return ToNumber(o.AsStringWithoutTypeCheck());
  99. case Types.Symbol:
  100. // TODO proper TypeError would require Engine instance and a lot of API changes
  101. return ExceptionHelper.ThrowTypeErrorNoEngine<double>("Cannot convert a Symbol value to a number");
  102. default:
  103. return ToNumber(ToPrimitive(o, Types.Number));
  104. }
  105. }
  106. internal static bool CanBeIndex(string input)
  107. {
  108. if (string.IsNullOrEmpty(input))
  109. {
  110. return false;
  111. }
  112. char first = input[0];
  113. if (first < 32 || (first > 57 && first != 73))
  114. {
  115. // does not start with space, +, -, number or I
  116. return false;
  117. }
  118. // might be
  119. return true;
  120. }
  121. private static double ToNumber(string input)
  122. {
  123. // eager checks to save time and trimming
  124. if (string.IsNullOrEmpty(input))
  125. {
  126. return 0;
  127. }
  128. char first = input[0];
  129. if (input.Length == 1 && first >= '0' && first <= '9')
  130. {
  131. // simple constant number
  132. return first - '0';
  133. }
  134. var s = StringPrototype.IsWhiteSpaceEx(input[0]) || StringPrototype.IsWhiteSpaceEx(input[input.Length - 1])
  135. ? StringPrototype.TrimEx(input)
  136. : input;
  137. if (s.Length == 0)
  138. {
  139. return 0;
  140. }
  141. if (s.Length == 8 || s.Length == 9)
  142. {
  143. if ("+Infinity" == s || "Infinity" == s)
  144. {
  145. return double.PositiveInfinity;
  146. }
  147. if ("-Infinity" == s)
  148. {
  149. return double.NegativeInfinity;
  150. }
  151. }
  152. // todo: use a common implementation with JavascriptParser
  153. try
  154. {
  155. if (s.Length > 2 && s[0] == '0' && char.IsLetter(s[1]))
  156. {
  157. int fromBase = 0;
  158. if (s[1] == 'x' || s[1] == 'X')
  159. {
  160. fromBase = 16;
  161. }
  162. if (s[1] == 'o' || s[1] == 'O')
  163. {
  164. fromBase = 8;
  165. }
  166. if (s[1] == 'b' || s[1] == 'B')
  167. {
  168. fromBase = 2;
  169. }
  170. if (fromBase > 0)
  171. {
  172. return Convert.ToInt32(s.Substring(2), fromBase);
  173. }
  174. }
  175. var start = s[0];
  176. if (start != '+' && start != '-' && start != '.' && !char.IsDigit(start))
  177. {
  178. return double.NaN;
  179. }
  180. double n = double.Parse(s,
  181. NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign |
  182. NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite |
  183. NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
  184. if (s.StartsWith("-") && n == 0)
  185. {
  186. return -0.0;
  187. }
  188. return n;
  189. }
  190. catch (OverflowException)
  191. {
  192. return s.StartsWith("-") ? double.NegativeInfinity : double.PositiveInfinity;
  193. }
  194. catch
  195. {
  196. return double.NaN;
  197. }
  198. }
  199. /// <summary>
  200. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
  201. /// </summary>
  202. /// <param name="o"></param>
  203. /// <returns></returns>
  204. public static double ToInteger(JsValue o)
  205. {
  206. var number = ToNumber(o);
  207. if (double.IsNaN(number))
  208. {
  209. return 0;
  210. }
  211. if (number == 0 || double.IsInfinity(number))
  212. {
  213. return number;
  214. }
  215. return (long) number;
  216. }
  217. internal static double ToInteger(string o)
  218. {
  219. var number = ToNumber(o);
  220. if (double.IsNaN(number))
  221. {
  222. return 0;
  223. }
  224. if (number == 0 || double.IsInfinity(number))
  225. {
  226. return number;
  227. }
  228. return (long) number;
  229. }
  230. /// <summary>
  231. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5
  232. /// </summary>
  233. /// <param name="o"></param>
  234. /// <returns></returns>
  235. public static int ToInt32(JsValue o)
  236. {
  237. return (int) (uint) ToNumber(o);
  238. }
  239. /// <summary>
  240. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.6
  241. /// </summary>
  242. /// <param name="o"></param>
  243. /// <returns></returns>
  244. public static uint ToUint32(JsValue o)
  245. {
  246. return (uint) ToNumber(o);
  247. }
  248. /// <summary>
  249. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.7
  250. /// </summary>
  251. /// <param name="o"></param>
  252. /// <returns></returns>
  253. public static ushort ToUint16(JsValue o)
  254. {
  255. return (ushort) (uint) ToNumber(o);
  256. }
  257. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  258. internal static string ToString(long i)
  259. {
  260. return i >= 0 && i < intToString.Length
  261. ? intToString[i]
  262. : i.ToString();
  263. }
  264. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  265. internal static string ToString(int i)
  266. {
  267. return i >= 0 && i < intToString.Length
  268. ? intToString[i]
  269. : i.ToString();
  270. }
  271. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  272. internal static string ToString(uint i)
  273. {
  274. return i < (uint) intToString.Length
  275. ? intToString[i]
  276. : i.ToString();
  277. }
  278. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  279. internal static string ToString(char c)
  280. {
  281. return c >= 0 && c < charToString.Length
  282. ? charToString[c]
  283. : c.ToString();
  284. }
  285. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  286. internal static string ToString(ulong i)
  287. {
  288. return i >= 0 && i < (ulong) intToString.Length
  289. ? intToString[i]
  290. : i.ToString();
  291. }
  292. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  293. internal static string ToString(double d)
  294. {
  295. if (d > long.MinValue && d < long.MaxValue && Math.Abs(d % 1) <= DoubleIsIntegerTolerance)
  296. {
  297. // we are dealing with integer that can be cached
  298. return ToString((long) d);
  299. }
  300. using (var stringBuilder = StringBuilderPool.Rent())
  301. {
  302. // we can create smaller array as we know the format to be short
  303. return NumberPrototype.NumberToString(d, new DtoaBuilder(17), stringBuilder.Builder);
  304. }
  305. }
  306. /// <summary>
  307. /// http://www.ecma-international.org/ecma-262/6.0/#sec-topropertykey
  308. /// </summary>
  309. public static string ToPropertyKey(JsValue o)
  310. {
  311. var key = ToPrimitive(o, Types.String);
  312. if (key is JsSymbol s)
  313. {
  314. return s._value;
  315. }
  316. return ToString(key);
  317. }
  318. /// <summary>
  319. /// http://www.ecma-international.org/ecma-262/6.0/#sec-tostring
  320. /// </summary>
  321. public static string ToString(JsValue o)
  322. {
  323. switch (o._type)
  324. {
  325. case Types.String:
  326. return o.AsStringWithoutTypeCheck();
  327. case Types.Boolean:
  328. return ((JsBoolean) o)._value ? "true" : "false";
  329. case Types.Number:
  330. return ToString(((JsNumber) o)._value);
  331. case Types.Symbol:
  332. return ExceptionHelper.ThrowTypeErrorNoEngine<string>("Cannot convert a Symbol value to a string");
  333. case Types.Undefined:
  334. return Undefined.Text;
  335. case Types.Null:
  336. return Null.Text;
  337. case Types.Object when o is IPrimitiveInstance p:
  338. return ToString(ToPrimitive(p.PrimitiveValue, Types.String));
  339. default:
  340. return ToString(ToPrimitive(o, Types.String));
  341. }
  342. }
  343. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  344. public static ObjectInstance ToObject(Engine engine, JsValue value)
  345. {
  346. switch (value._type)
  347. {
  348. case Types.Object:
  349. return (ObjectInstance) value;
  350. case Types.Boolean:
  351. return engine.Boolean.Construct(((JsBoolean) value)._value);
  352. case Types.Number:
  353. return engine.Number.Construct(((JsNumber) value)._value);
  354. case Types.String:
  355. return engine.String.Construct(value.AsStringWithoutTypeCheck());
  356. case Types.Symbol:
  357. return engine.Symbol.Construct(((JsSymbol) value)._value);
  358. default:
  359. ExceptionHelper.ThrowTypeError(engine);
  360. return null;
  361. }
  362. }
  363. public static Types GetPrimitiveType(JsValue value)
  364. {
  365. if (value._type == Types.Object)
  366. {
  367. if (value is IPrimitiveInstance primitive)
  368. {
  369. return primitive.Type;
  370. }
  371. return Types.Object;
  372. }
  373. return value.Type;
  374. }
  375. public static void CheckObjectCoercible(
  376. Engine engine,
  377. JsValue o,
  378. MemberExpression expression,
  379. object baseReference)
  380. {
  381. if (o._type > Types.Null)
  382. {
  383. return;
  384. }
  385. var referenceResolver = engine.Options.ReferenceResolver;
  386. if (referenceResolver != null && referenceResolver.CheckCoercible(o))
  387. {
  388. return;
  389. }
  390. ThrowTypeError(engine, o, expression, baseReference);
  391. }
  392. internal static void CheckObjectCoercible(
  393. Engine engine,
  394. JsValue o,
  395. MemberExpression expression,
  396. string referenceName)
  397. {
  398. if (o._type > Types.Null)
  399. {
  400. return;
  401. }
  402. var referenceResolver = engine.Options.ReferenceResolver;
  403. if (referenceResolver != null && referenceResolver.CheckCoercible(o))
  404. {
  405. return;
  406. }
  407. ThrowTypeError(engine, o, expression, referenceName);
  408. }
  409. private static void ThrowTypeError(
  410. Engine engine,
  411. JsValue o,
  412. MemberExpression expression,
  413. object baseReference)
  414. {
  415. ThrowTypeError(engine, o, expression, (baseReference as Reference)?.GetReferencedName());
  416. }
  417. private static void ThrowTypeError(
  418. Engine engine,
  419. JsValue o,
  420. MemberExpression expression,
  421. string referencedName)
  422. {
  423. referencedName = referencedName ?? "The value";
  424. var message = $"{referencedName} is {o}";
  425. throw new JavaScriptException(engine.TypeError, message).SetCallstack(engine, expression.Location);
  426. }
  427. public static void CheckObjectCoercible(Engine engine, JsValue o)
  428. {
  429. if (o._type == Types.Undefined || o._type == Types.Null)
  430. {
  431. ExceptionHelper.ThrowTypeError(engine);
  432. }
  433. }
  434. public static IEnumerable<Tuple<MethodBase, JsValue[]>> FindBestMatch<T>(Engine engine, T[] methods, Func<T, bool, JsValue[]> argumentProvider) where T : MethodBase
  435. {
  436. System.Collections.Generic.List<Tuple<T, JsValue[]>> matchingByParameterCount = null;
  437. foreach (var m in methods)
  438. {
  439. bool hasParams = false;
  440. var parameterInfos = m.GetParameters();
  441. foreach (var parameter in parameterInfos)
  442. {
  443. if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
  444. {
  445. hasParams = true;
  446. break;
  447. }
  448. }
  449. var arguments = argumentProvider(m, hasParams);
  450. if (parameterInfos.Length == arguments.Length)
  451. {
  452. if (methods.Length == 0 && arguments.Length == 0)
  453. {
  454. yield return new Tuple<MethodBase, JsValue[]>(m, arguments);
  455. yield break;
  456. }
  457. matchingByParameterCount = matchingByParameterCount ?? new System.Collections.Generic.List<Tuple<T, JsValue[]>>();
  458. matchingByParameterCount.Add(new Tuple<T, JsValue[]>(m, arguments));
  459. }
  460. else if (parameterInfos.Length > arguments.Length)
  461. {
  462. // check if we got enough default values to provide all parameters (or more in case some default values are provided/overwritten)
  463. var defaultValuesCount = 0;
  464. foreach (var param in parameterInfos)
  465. {
  466. if (param.HasDefaultValue) defaultValuesCount++;
  467. }
  468. if (parameterInfos.Length <= arguments.Length + defaultValuesCount)
  469. {
  470. // create missing arguments from default values
  471. var argsWithDefaults = new System.Collections.Generic.List<JsValue>(arguments);
  472. for (var i = arguments.Length; i < parameterInfos.Length; i++)
  473. {
  474. var param = parameterInfos[i];
  475. var value = JsValue.FromObject(engine, param.DefaultValue);
  476. argsWithDefaults.Add(value);
  477. }
  478. matchingByParameterCount = matchingByParameterCount ?? new System.Collections.Generic.List<Tuple<T, JsValue[]>>();
  479. matchingByParameterCount.Add(new Tuple<T, JsValue[]>(m, argsWithDefaults.ToArray()));
  480. }
  481. }
  482. }
  483. if (matchingByParameterCount == null)
  484. {
  485. yield break;
  486. }
  487. foreach (var tuple in matchingByParameterCount)
  488. {
  489. var perfectMatch = true;
  490. var parameters = tuple.Item1.GetParameters();
  491. var arguments = tuple.Item2;
  492. for (var i = 0; i < arguments.Length; i++)
  493. {
  494. var arg = arguments[i].ToObject();
  495. var paramType = parameters[i].ParameterType;
  496. if (arg == null)
  497. {
  498. if (!TypeIsNullable(paramType))
  499. {
  500. perfectMatch = false;
  501. break;
  502. }
  503. }
  504. else if (arg.GetType() != paramType)
  505. {
  506. perfectMatch = false;
  507. break;
  508. }
  509. }
  510. if (perfectMatch)
  511. {
  512. yield return new Tuple<MethodBase, JsValue[]>(tuple.Item1, arguments);
  513. yield break;
  514. }
  515. }
  516. for (var i = 0; i < matchingByParameterCount.Count; i++)
  517. {
  518. var tuple = matchingByParameterCount[i];
  519. yield return new Tuple<MethodBase, JsValue[]>(tuple.Item1, tuple.Item2);
  520. }
  521. }
  522. public static bool TypeIsNullable(Type type)
  523. {
  524. return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
  525. }
  526. }
  527. }