TypeConverter.cs 22 KB

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