JsValue.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Diagnostics.Contracts;
  5. using System.Runtime.CompilerServices;
  6. using System.Threading;
  7. using Jint.Native.Array;
  8. using Jint.Native.Date;
  9. using Jint.Native.Function;
  10. using Jint.Native.Iterator;
  11. using Jint.Native.Object;
  12. using Jint.Native.RegExp;
  13. using Jint.Native.Symbol;
  14. using Jint.Runtime;
  15. using Jint.Runtime.Descriptors;
  16. using Jint.Runtime.Interop;
  17. namespace Jint.Native
  18. {
  19. [DebuggerTypeProxy(typeof(JsValueDebugView))]
  20. public abstract class JsValue : IEquatable<JsValue>
  21. {
  22. public static readonly JsValue Undefined = new JsUndefined();
  23. public static readonly JsValue Null = new JsNull();
  24. internal readonly InternalTypes _type;
  25. protected JsValue(Types type)
  26. {
  27. _type = (InternalTypes) type;
  28. }
  29. internal JsValue(InternalTypes type)
  30. {
  31. _type = type;
  32. }
  33. [Pure]
  34. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  35. public bool IsPrimitive()
  36. {
  37. return _type != InternalTypes.Object && _type != InternalTypes.None;
  38. }
  39. [Pure]
  40. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  41. public bool IsUndefined()
  42. {
  43. return _type == InternalTypes.Undefined;
  44. }
  45. [Pure]
  46. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  47. internal bool IsNullOrUndefined()
  48. {
  49. return _type < InternalTypes.Boolean;
  50. }
  51. [Pure]
  52. public virtual bool IsArray()
  53. {
  54. return this is ArrayInstance;
  55. }
  56. [Pure]
  57. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  58. public bool IsDate()
  59. {
  60. return this is DateInstance;
  61. }
  62. [Pure]
  63. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  64. public bool IsRegExp()
  65. {
  66. return this is RegExpInstance;
  67. }
  68. [Pure]
  69. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  70. public bool IsObject()
  71. {
  72. return _type == InternalTypes.Object;
  73. }
  74. [Pure]
  75. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  76. public bool IsString()
  77. {
  78. return _type == InternalTypes.String;
  79. }
  80. [Pure]
  81. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  82. public bool IsNumber()
  83. {
  84. return _type == InternalTypes.Number || _type == InternalTypes.Integer;
  85. }
  86. [Pure]
  87. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  88. internal bool IsInteger()
  89. {
  90. return _type == InternalTypes.Integer;
  91. }
  92. [Pure]
  93. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  94. public bool IsBoolean()
  95. {
  96. return _type == InternalTypes.Boolean;
  97. }
  98. [Pure]
  99. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  100. public bool IsNull()
  101. {
  102. return _type == InternalTypes.Null;
  103. }
  104. [Pure]
  105. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  106. public bool IsCompletion()
  107. {
  108. return _type == InternalTypes.Completion;
  109. }
  110. [Pure]
  111. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  112. public bool IsSymbol()
  113. {
  114. return _type == InternalTypes.Symbol;
  115. }
  116. [Pure]
  117. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  118. public ObjectInstance AsObject()
  119. {
  120. if (!IsObject())
  121. {
  122. ExceptionHelper.ThrowArgumentException("The value is not an object");
  123. }
  124. return this as ObjectInstance;
  125. }
  126. [Pure]
  127. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  128. public TInstance AsInstance<TInstance>() where TInstance : class
  129. {
  130. if (!IsObject())
  131. {
  132. ExceptionHelper.ThrowArgumentException("The value is not an object");
  133. }
  134. return this as TInstance;
  135. }
  136. [Pure]
  137. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  138. public ArrayInstance AsArray()
  139. {
  140. if (!IsArray())
  141. {
  142. ExceptionHelper.ThrowArgumentException("The value is not an array");
  143. }
  144. return this as ArrayInstance;
  145. }
  146. [Pure]
  147. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  148. internal IIterator GetIterator(Engine engine)
  149. {
  150. if (!TryGetIterator(engine, out var iterator))
  151. {
  152. return ExceptionHelper.ThrowTypeError<IIterator>(engine, "The value is not iterable");
  153. }
  154. return iterator;
  155. }
  156. [Pure]
  157. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  158. internal bool TryGetIterator(Engine engine, out IIterator iterator)
  159. {
  160. var objectInstance = TypeConverter.ToObject(engine, this);
  161. if (!objectInstance.TryGetValue(GlobalSymbolRegistry.Iterator, out var value)
  162. || !(value is ICallable callable))
  163. {
  164. iterator = null;
  165. return false;
  166. }
  167. var obj = callable.Call(this, Arguments.Empty) as ObjectInstance
  168. ?? ExceptionHelper.ThrowTypeError<ObjectInstance>(engine, "Result of the Symbol.iterator method is not an object");
  169. if (obj is IIterator i)
  170. {
  171. iterator = i;
  172. }
  173. else
  174. {
  175. iterator = new IteratorInstance.ObjectWrapper(obj);
  176. }
  177. return true;
  178. }
  179. [Pure]
  180. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  181. public DateInstance AsDate()
  182. {
  183. if (!IsDate())
  184. {
  185. ExceptionHelper.ThrowArgumentException("The value is not a date");
  186. }
  187. return this as DateInstance;
  188. }
  189. [Pure]
  190. public RegExpInstance AsRegExp()
  191. {
  192. if (!IsRegExp())
  193. {
  194. ExceptionHelper.ThrowArgumentException("The value is not a regex");
  195. }
  196. return this as RegExpInstance;
  197. }
  198. [Pure]
  199. public Completion AsCompletion()
  200. {
  201. if (_type != InternalTypes.Completion)
  202. {
  203. ExceptionHelper.ThrowArgumentException("The value is not a completion record");
  204. }
  205. // TODO not implemented
  206. return new Completion(CompletionType.Normal, Native.Undefined.Instance, null, default);
  207. }
  208. [Pure]
  209. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  210. public T TryCast<T>() where T : class
  211. {
  212. return this as T;
  213. }
  214. [Pure]
  215. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  216. public T TryCast<T>(Action<JsValue> fail) where T : class
  217. {
  218. if (this is T o)
  219. {
  220. return o;
  221. }
  222. fail.Invoke(this);
  223. return null;
  224. }
  225. [Pure]
  226. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  227. public bool Is<T>()
  228. {
  229. return IsObject() && this is T;
  230. }
  231. [Pure]
  232. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  233. public T As<T>() where T : ObjectInstance
  234. {
  235. if (IsObject())
  236. {
  237. return this as T;
  238. }
  239. return null;
  240. }
  241. public Types Type
  242. {
  243. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  244. get => _type == InternalTypes.Integer ? Types.Number : (Types) _type;
  245. }
  246. internal virtual bool IsConstructor => this is IConstructor;
  247. /// <summary>
  248. /// Creates a valid <see cref="JsValue"/> instance from any <see cref="Object"/> instance
  249. /// </summary>
  250. /// <param name="engine"></param>
  251. /// <param name="value"></param>
  252. /// <returns></returns>
  253. public static JsValue FromObject(Engine engine, object value)
  254. {
  255. if (value == null)
  256. {
  257. return Null;
  258. }
  259. if (value is JsValue jsValue)
  260. {
  261. return jsValue;
  262. }
  263. var converters = engine.Options._ObjectConverters;
  264. var convertersCount = converters.Count;
  265. for (var i = 0; i < convertersCount; i++)
  266. {
  267. var converter = converters[i];
  268. if (converter.TryConvert(engine, value, out var result))
  269. {
  270. return result;
  271. }
  272. }
  273. var valueType = value.GetType();
  274. var typeMappers = Engine.TypeMappers;
  275. if (typeMappers.TryGetValue(valueType, out var typeMapper))
  276. {
  277. return typeMapper(engine, value);
  278. }
  279. var type = value as Type;
  280. if (type != null)
  281. {
  282. var typeReference = TypeReference.CreateTypeReference(engine, type);
  283. return typeReference;
  284. }
  285. if (value is System.Array a)
  286. {
  287. // racy, we don't care, worst case we'll catch up later
  288. Interlocked.CompareExchange(ref Engine.TypeMappers, new Dictionary<Type, Func<Engine, object, JsValue>>(typeMappers)
  289. {
  290. [valueType] = Convert
  291. }, typeMappers);
  292. return Convert(engine, a);
  293. }
  294. if (value is Delegate d)
  295. {
  296. return new DelegateWrapper(engine, d);
  297. }
  298. Type t = value.GetType();
  299. if (t.IsEnum)
  300. {
  301. Type ut = Enum.GetUnderlyingType(t);
  302. if (ut == typeof(ulong))
  303. return JsNumber.Create(System.Convert.ToDouble(value));
  304. if (ut == typeof(uint) || ut == typeof(long))
  305. return JsNumber.Create(System.Convert.ToInt64(value));
  306. return JsNumber.Create(System.Convert.ToInt32(value));
  307. }
  308. // if no known type could be guessed, wrap it as an ObjectInstance
  309. var h = engine.Options._WrapObjectHandler;
  310. ObjectInstance o = h != null ? h(value) : null;
  311. return o ?? new ObjectWrapper(engine, value);
  312. }
  313. private static JsValue Convert(Engine e, object v)
  314. {
  315. var array = (System.Array) v;
  316. var arrayLength = (uint) array.Length;
  317. var jsArray = new ArrayInstance(e, arrayLength);
  318. jsArray._prototype = e.Array.PrototypeObject;
  319. for (uint i = 0; i < arrayLength; ++i)
  320. {
  321. var jsItem = FromObject(e, array.GetValue(i));
  322. jsArray.WriteArrayValue(i, new PropertyDescriptor(jsItem, PropertyFlag.ConfigurableEnumerableWritable));
  323. }
  324. jsArray.SetOwnProperty(KnownKeys.Length, new PropertyDescriptor(arrayLength, PropertyFlag.OnlyWritable));
  325. return jsArray;
  326. }
  327. /// <summary>
  328. /// Converts a <see cref="JsValue"/> to its underlying CLR value.
  329. /// </summary>
  330. /// <returns>The underlying CLR value of the <see cref="JsValue"/> instance.</returns>
  331. public abstract object ToObject();
  332. /// <summary>
  333. /// Invoke the current value as function.
  334. /// </summary>
  335. /// <param name="arguments">The arguments of the function call.</param>
  336. /// <returns>The value returned by the function call.</returns>
  337. public JsValue Invoke(params JsValue[] arguments)
  338. {
  339. return Invoke(Undefined, arguments);
  340. }
  341. /// <summary>
  342. /// Invoke the current value as function.
  343. /// </summary>
  344. /// <param name="thisObj">The this value inside the function call.</param>
  345. /// <param name="arguments">The arguments of the function call.</param>
  346. /// <returns>The value returned by the function call.</returns>
  347. public JsValue Invoke(JsValue thisObj, JsValue[] arguments)
  348. {
  349. var callable = this as ICallable ?? ExceptionHelper.ThrowArgumentException<ICallable>("Can only invoke functions");
  350. return callable.Call(thisObj, arguments);
  351. }
  352. public static bool ReturnOnAbruptCompletion(ref JsValue argument)
  353. {
  354. if (!argument.IsCompletion())
  355. {
  356. return false;
  357. }
  358. var completion = argument.AsCompletion();
  359. if (completion.IsAbrupt())
  360. {
  361. return true;
  362. }
  363. argument = completion.Value;
  364. return false;
  365. }
  366. internal virtual Key ToPropertyKey()
  367. {
  368. return new Key(ToString());
  369. }
  370. public override string ToString()
  371. {
  372. return "None";
  373. }
  374. public static bool operator ==(JsValue a, JsValue b)
  375. {
  376. if ((object)a == null)
  377. {
  378. if ((object)b == null)
  379. {
  380. return true;
  381. }
  382. return false;
  383. }
  384. if ((object)b == null)
  385. {
  386. return false;
  387. }
  388. return a.Equals(b);
  389. }
  390. public static bool operator !=(JsValue a, JsValue b)
  391. {
  392. if ((object)a == null)
  393. {
  394. if ((object)b == null)
  395. {
  396. return false;
  397. }
  398. return true;
  399. }
  400. if ((object)b == null)
  401. {
  402. return true;
  403. }
  404. return !a.Equals(b);
  405. }
  406. public static implicit operator JsValue(char value)
  407. {
  408. return JsString.Create(value);
  409. }
  410. public static implicit operator JsValue(int value)
  411. {
  412. return JsNumber.Create(value);
  413. }
  414. public static implicit operator JsValue(uint value)
  415. {
  416. return JsNumber.Create(value);
  417. }
  418. public static implicit operator JsValue(double value)
  419. {
  420. return JsNumber.Create(value);
  421. }
  422. public static implicit operator JsValue(long value)
  423. {
  424. return JsNumber.Create(value);
  425. }
  426. public static implicit operator JsValue(ulong value)
  427. {
  428. return JsNumber.Create(value);
  429. }
  430. public static implicit operator JsValue(bool value)
  431. {
  432. return value ? JsBoolean.True : JsBoolean.False;
  433. }
  434. public static implicit operator JsValue(string value)
  435. {
  436. if (value == null)
  437. {
  438. return Null;
  439. }
  440. return JsString.Create(value);
  441. }
  442. public override bool Equals(object obj)
  443. {
  444. if (ReferenceEquals(null, obj))
  445. {
  446. return false;
  447. }
  448. if (ReferenceEquals(this, obj))
  449. {
  450. return true;
  451. }
  452. return obj is JsValue value && Equals(value);
  453. }
  454. public abstract bool Equals(JsValue other);
  455. public override int GetHashCode()
  456. {
  457. return _type.GetHashCode();
  458. }
  459. internal class JsValueDebugView
  460. {
  461. public string Value;
  462. public JsValueDebugView(JsValue value)
  463. {
  464. switch (value.Type)
  465. {
  466. case Types.None:
  467. Value = "None";
  468. break;
  469. case Types.Undefined:
  470. Value = "undefined";
  471. break;
  472. case Types.Null:
  473. Value = "null";
  474. break;
  475. case Types.Boolean:
  476. Value = ((JsBoolean) value)._value + " (bool)";
  477. break;
  478. case Types.String:
  479. Value = value.AsStringWithoutTypeCheck() + " (string)";
  480. break;
  481. case Types.Number:
  482. Value = ((JsNumber) value)._value + " (number)";
  483. break;
  484. case Types.Object:
  485. Value = value.AsObject().GetType().Name;
  486. break;
  487. case Types.Symbol:
  488. Value = value.AsSymbol() + " (symbol)";
  489. break;
  490. default:
  491. Value = "Unknown";
  492. break;
  493. }
  494. }
  495. }
  496. /// <summary>
  497. /// Some values need to be cloned in order to be assigned, like ConcatenatedString.
  498. /// </summary>
  499. internal virtual JsValue Clone()
  500. {
  501. return this;
  502. }
  503. }
  504. }