TypeConverter.cs 17 KB

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