TypeConverter.cs 14 KB

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