JsProxy.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. using Jint.Native.Function;
  2. using Jint.Native.Object;
  3. using Jint.Runtime;
  4. using Jint.Runtime.Descriptors;
  5. namespace Jint.Native
  6. {
  7. internal sealed class JsProxy : ObjectInstance, IConstructor, ICallable
  8. {
  9. internal ObjectInstance _target;
  10. internal ObjectInstance? _handler;
  11. private static readonly JsString TrapApply = new JsString("apply");
  12. private static readonly JsString TrapGet = new JsString("get");
  13. private static readonly JsString TrapSet = new JsString("set");
  14. private static readonly JsString TrapPreventExtensions = new JsString("preventExtensions");
  15. private static readonly JsString TrapIsExtensible = new JsString("isExtensible");
  16. private static readonly JsString TrapDefineProperty = new JsString("defineProperty");
  17. private static readonly JsString TrapDeleteProperty = new JsString("deleteProperty");
  18. private static readonly JsString TrapGetOwnPropertyDescriptor = new JsString("getOwnPropertyDescriptor");
  19. private static readonly JsString TrapHas = new JsString("has");
  20. private static readonly JsString TrapGetProtoTypeOf = new JsString("getPrototypeOf");
  21. private static readonly JsString TrapSetProtoTypeOf = new JsString("setPrototypeOf");
  22. private static readonly JsString TrapOwnKeys = new JsString("ownKeys");
  23. private static readonly JsString TrapConstruct = new JsString("construct");
  24. private static readonly JsString KeyFunctionRevoke = new JsString("revoke");
  25. private static readonly JsString KeyIsArray = new JsString("isArray");
  26. public JsProxy(
  27. Engine engine,
  28. ObjectInstance target,
  29. ObjectInstance handler)
  30. : base(engine, target.Class)
  31. {
  32. _target = target;
  33. _handler = handler;
  34. IsCallable = target.IsCallable;
  35. }
  36. /// <summary>
  37. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
  38. /// </summary>
  39. JsValue ICallable.Call(JsValue thisObject, JsValue[] arguments)
  40. {
  41. if (_target is not ICallable)
  42. {
  43. ExceptionHelper.ThrowTypeError(_engine.Realm, "(intermediate value) is not a function");
  44. }
  45. var jsValues = new[] { _target, thisObject, _engine.Realm.Intrinsics.Array.ConstructFast(arguments) };
  46. if (TryCallHandler(TrapApply, jsValues, out var result))
  47. {
  48. return result;
  49. }
  50. var callable = _target as ICallable;
  51. if (callable is null)
  52. {
  53. ExceptionHelper.ThrowTypeError(_engine.Realm, _target + " is not a function");
  54. }
  55. return callable.Call(thisObject, arguments);
  56. }
  57. /// <summary>
  58. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget
  59. /// </summary>
  60. ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget)
  61. {
  62. if (_target is not ICallable)
  63. {
  64. ExceptionHelper.ThrowTypeError(_engine.Realm, "(intermediate value) is not a constructor");
  65. }
  66. var argArray = _engine.Realm.Intrinsics.Array.Construct(arguments, _engine.Realm.Intrinsics.Array);
  67. if (!TryCallHandler(TrapConstruct, new[] { _target, argArray, newTarget }, out var result))
  68. {
  69. var constructor = _target as IConstructor;
  70. if (constructor is null)
  71. {
  72. ExceptionHelper.ThrowTypeError(_engine.Realm);
  73. }
  74. return constructor.Construct(arguments, newTarget);
  75. }
  76. var oi = result as ObjectInstance;
  77. if (oi is null)
  78. {
  79. ExceptionHelper.ThrowTypeError(_engine.Realm);
  80. }
  81. return oi;
  82. }
  83. internal override bool IsArray()
  84. {
  85. AssertNotRevoked(KeyIsArray);
  86. return _target.IsArray();
  87. }
  88. public override object ToObject() => _target.ToObject();
  89. internal override bool IsConstructor
  90. {
  91. get
  92. {
  93. if (_target is not null && _target.IsConstructor)
  94. {
  95. return true;
  96. }
  97. if (_handler is not null && _handler.TryGetValue(TrapConstruct, out var handlerFunction) && handlerFunction is IConstructor)
  98. {
  99. return true;
  100. }
  101. return false;
  102. }
  103. }
  104. /// <summary>
  105. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
  106. /// </summary>
  107. public override JsValue Get(JsValue property, JsValue receiver)
  108. {
  109. AssertTargetNotRevoked(property);
  110. var target = _target;
  111. if (KeyFunctionRevoke.Equals(property) || !TryCallHandler(TrapGet, new[] { target, TypeConverter.ToPropertyKey(property), receiver }, out var result))
  112. {
  113. return target.Get(property, receiver);
  114. }
  115. var targetDesc = target.GetOwnProperty(property);
  116. if (targetDesc != PropertyDescriptor.Undefined)
  117. {
  118. if (targetDesc.IsDataDescriptor())
  119. {
  120. var targetValue = targetDesc.Value;
  121. if (!targetDesc.Configurable && !targetDesc.Writable && !SameValue(result, targetValue))
  122. {
  123. ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '{targetValue}' but got '{result}')");
  124. }
  125. }
  126. if (targetDesc.IsAccessorDescriptor())
  127. {
  128. if (!targetDesc.Configurable && (targetDesc.Get ?? Undefined).IsUndefined() && !result.IsUndefined())
  129. {
  130. ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{result}')");
  131. }
  132. }
  133. }
  134. return result;
  135. }
  136. /// <summary>
  137. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
  138. /// </summary>
  139. public override List<JsValue> GetOwnPropertyKeys(Types types = Types.Empty | Types.String | Types.Symbol)
  140. {
  141. if (!TryCallHandler(TrapOwnKeys, new[] { _target }, out var result))
  142. {
  143. return _target.GetOwnPropertyKeys(types);
  144. }
  145. var trapResult = new List<JsValue>(FunctionPrototype.CreateListFromArrayLike(_engine.Realm, result, Types.String | Types.Symbol));
  146. if (trapResult.Count != new HashSet<JsValue>(trapResult).Count)
  147. {
  148. ExceptionHelper.ThrowTypeError(_engine.Realm);
  149. }
  150. var extensibleTarget = _target.Extensible;
  151. var targetKeys = _target.GetOwnPropertyKeys();
  152. var targetConfigurableKeys = new List<JsValue>();
  153. var targetNonconfigurableKeys = new List<JsValue>();
  154. foreach (var property in targetKeys)
  155. {
  156. var desc = _target.GetOwnProperty(property);
  157. if (desc != PropertyDescriptor.Undefined && !desc.Configurable)
  158. {
  159. targetNonconfigurableKeys.Add(property);
  160. }
  161. else
  162. {
  163. targetConfigurableKeys.Add(property);
  164. }
  165. }
  166. var uncheckedResultKeys = new HashSet<JsValue>(trapResult);
  167. for (var i = 0; i < targetNonconfigurableKeys.Count; i++)
  168. {
  169. var key = targetNonconfigurableKeys[i];
  170. if (!uncheckedResultKeys.Remove(key))
  171. {
  172. ExceptionHelper.ThrowTypeError(_engine.Realm);
  173. }
  174. }
  175. if (extensibleTarget)
  176. {
  177. return trapResult;
  178. }
  179. for (var i = 0; i < targetConfigurableKeys.Count; i++)
  180. {
  181. var key = targetConfigurableKeys[i];
  182. if (!uncheckedResultKeys.Remove(key))
  183. {
  184. ExceptionHelper.ThrowTypeError(_engine.Realm);
  185. }
  186. }
  187. if (uncheckedResultKeys.Count > 0)
  188. {
  189. ExceptionHelper.ThrowTypeError(_engine.Realm);
  190. }
  191. return trapResult;
  192. }
  193. /// <summary>
  194. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p
  195. /// </summary>
  196. public override PropertyDescriptor GetOwnProperty(JsValue property)
  197. {
  198. if (!TryCallHandler(TrapGetOwnPropertyDescriptor, new[] { _target, TypeConverter.ToPropertyKey(property) }, out var trapResultObj))
  199. {
  200. return _target.GetOwnProperty(property);
  201. }
  202. if (!trapResultObj.IsObject() && !trapResultObj.IsUndefined())
  203. {
  204. ExceptionHelper.ThrowTypeError(_engine.Realm);
  205. }
  206. var targetDesc = _target.GetOwnProperty(property);
  207. if (trapResultObj.IsUndefined())
  208. {
  209. if (targetDesc == PropertyDescriptor.Undefined)
  210. {
  211. return targetDesc;
  212. }
  213. if (!targetDesc.Configurable || !_target.Extensible)
  214. {
  215. ExceptionHelper.ThrowTypeError(_engine.Realm);
  216. }
  217. return PropertyDescriptor.Undefined;
  218. }
  219. var extensibleTarget = _target.Extensible;
  220. var resultDesc = PropertyDescriptor.ToPropertyDescriptor(_engine.Realm, trapResultObj);
  221. CompletePropertyDescriptor(resultDesc);
  222. var valid = IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc);
  223. if (!valid)
  224. {
  225. ExceptionHelper.ThrowTypeError(_engine.Realm);
  226. }
  227. if (!resultDesc.Configurable)
  228. {
  229. if (targetDesc == PropertyDescriptor.Undefined || targetDesc.Configurable)
  230. {
  231. ExceptionHelper.ThrowTypeError(_engine.Realm);
  232. }
  233. if (resultDesc.WritableSet && !resultDesc.Writable)
  234. {
  235. if (targetDesc.Writable)
  236. {
  237. ExceptionHelper.ThrowTypeError(_engine.Realm);
  238. }
  239. }
  240. }
  241. return resultDesc;
  242. }
  243. /// <summary>
  244. /// https://tc39.es/ecma262/#sec-completepropertydescriptor
  245. /// </summary>
  246. private static void CompletePropertyDescriptor(PropertyDescriptor desc)
  247. {
  248. if (desc.IsGenericDescriptor() || desc.IsDataDescriptor())
  249. {
  250. desc.Value ??= Undefined;
  251. if (!desc.WritableSet)
  252. {
  253. desc.Writable = false;
  254. }
  255. }
  256. else
  257. {
  258. var getSet = (GetSetPropertyDescriptor) desc;
  259. getSet.SetGet(getSet.Get ?? Undefined);
  260. getSet.SetSet(getSet.Set ?? Undefined);
  261. }
  262. if (!desc.EnumerableSet)
  263. {
  264. desc.Enumerable = false;
  265. }
  266. if (!desc.ConfigurableSet)
  267. {
  268. desc.Configurable = false;
  269. }
  270. }
  271. /// <summary>
  272. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
  273. /// </summary>
  274. public override bool Set(JsValue property, JsValue value, JsValue receiver)
  275. {
  276. if (!TryCallHandler(TrapSet, new[] { _target, TypeConverter.ToPropertyKey(property), value, receiver }, out var trapResult))
  277. {
  278. return _target.Set(property, value, receiver);
  279. }
  280. var result = TypeConverter.ToBoolean(trapResult);
  281. if (!result)
  282. {
  283. return false;
  284. }
  285. var targetDesc = _target.GetOwnProperty(property);
  286. if (targetDesc != PropertyDescriptor.Undefined)
  287. {
  288. if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable)
  289. {
  290. var targetValue = targetDesc.Value;
  291. if (!SameValue(targetValue, value))
  292. {
  293. ExceptionHelper.ThrowTypeError(_engine.Realm, $"'set' on proxy: trap returned truish for property '{property}' which exists in the proxy target as a non-configurable and non-writable data property with a different value");
  294. }
  295. }
  296. if (targetDesc.IsAccessorDescriptor() && !targetDesc.Configurable)
  297. {
  298. if ((targetDesc.Set ?? Undefined).IsUndefined())
  299. {
  300. ExceptionHelper.ThrowTypeError(_engine.Realm, $"'set' on proxy: trap returned truish for property '{property}' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter");
  301. }
  302. }
  303. }
  304. return true;
  305. }
  306. /// <summary>
  307. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc
  308. /// </summary>
  309. public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
  310. {
  311. var arguments = new[] { _target, TypeConverter.ToPropertyKey(property), PropertyDescriptor.FromPropertyDescriptor(_engine, desc, strictUndefined: true) };
  312. if (!TryCallHandler(TrapDefineProperty, arguments, out var result))
  313. {
  314. return _target.DefineOwnProperty(property, desc);
  315. }
  316. var success = TypeConverter.ToBoolean(result);
  317. if (!success)
  318. {
  319. return false;
  320. }
  321. var targetDesc = _target.GetOwnProperty(property);
  322. var extensibleTarget = _target.Extensible;
  323. var settingConfigFalse = desc.ConfigurableSet && !desc.Configurable;
  324. if (targetDesc == PropertyDescriptor.Undefined)
  325. {
  326. if (!extensibleTarget || settingConfigFalse)
  327. {
  328. ExceptionHelper.ThrowTypeError(_engine.Realm);
  329. }
  330. }
  331. else
  332. {
  333. if (!IsCompatiblePropertyDescriptor(extensibleTarget, desc, targetDesc))
  334. {
  335. ExceptionHelper.ThrowTypeError(_engine.Realm);
  336. }
  337. if (targetDesc.Configurable && settingConfigFalse)
  338. {
  339. ExceptionHelper.ThrowTypeError(_engine.Realm);
  340. }
  341. if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && targetDesc.Writable)
  342. {
  343. if (desc.WritableSet && !desc.Writable)
  344. {
  345. ExceptionHelper.ThrowTypeError(_engine.Realm);
  346. }
  347. }
  348. }
  349. return true;
  350. }
  351. private static bool IsCompatiblePropertyDescriptor(bool extensible, PropertyDescriptor desc, PropertyDescriptor current)
  352. {
  353. return ValidateAndApplyPropertyDescriptor(null, JsString.Empty, extensible, desc, current);
  354. }
  355. /// <summary>
  356. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p
  357. /// </summary>
  358. public override bool HasProperty(JsValue property)
  359. {
  360. if (!TryCallHandler(TrapHas, new[] { _target, TypeConverter.ToPropertyKey(property) }, out var jsValue))
  361. {
  362. return _target.HasProperty(property);
  363. }
  364. var trapResult = TypeConverter.ToBoolean(jsValue);
  365. if (!trapResult)
  366. {
  367. var targetDesc = _target.GetOwnProperty(property);
  368. if (targetDesc != PropertyDescriptor.Undefined)
  369. {
  370. if (!targetDesc.Configurable)
  371. {
  372. ExceptionHelper.ThrowTypeError(_engine.Realm);
  373. }
  374. if (!_target.Extensible)
  375. {
  376. ExceptionHelper.ThrowTypeError(_engine.Realm);
  377. }
  378. }
  379. }
  380. return trapResult;
  381. }
  382. /// <summary>
  383. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p
  384. /// </summary>
  385. public override bool Delete(JsValue property)
  386. {
  387. if (!TryCallHandler(TrapDeleteProperty, new[] { _target, TypeConverter.ToPropertyKey(property) }, out var result))
  388. {
  389. return _target.Delete(property);
  390. }
  391. var booleanTrapResult = TypeConverter.ToBoolean(result);
  392. if (!booleanTrapResult)
  393. {
  394. return false;
  395. }
  396. var targetDesc = _target.GetOwnProperty(property);
  397. if (targetDesc == PropertyDescriptor.Undefined)
  398. {
  399. return true;
  400. }
  401. if (!targetDesc.Configurable)
  402. {
  403. ExceptionHelper.ThrowTypeError(_engine.Realm, $"'deleteProperty' on proxy: trap returned truish for property '{property}' which is non-configurable in the proxy target");
  404. }
  405. if (!_target.Extensible)
  406. {
  407. ExceptionHelper.ThrowTypeError(_engine.Realm);
  408. }
  409. return true;
  410. }
  411. /// <summary>
  412. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions
  413. /// </summary>
  414. public override bool PreventExtensions()
  415. {
  416. if (!TryCallHandler(TrapPreventExtensions, new[] { _target }, out var result))
  417. {
  418. return _target.PreventExtensions();
  419. }
  420. var success = TypeConverter.ToBoolean(result);
  421. if (success && _target.Extensible)
  422. {
  423. ExceptionHelper.ThrowTypeError(_engine.Realm);
  424. }
  425. return success;
  426. }
  427. /// <summary>
  428. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible
  429. /// </summary>
  430. public override bool Extensible
  431. {
  432. get
  433. {
  434. if (!TryCallHandler(TrapIsExtensible, new[] { _target }, out var result))
  435. {
  436. return _target.Extensible;
  437. }
  438. var booleanTrapResult = TypeConverter.ToBoolean(result);
  439. var targetResult = _target.Extensible;
  440. if (booleanTrapResult != targetResult)
  441. {
  442. ExceptionHelper.ThrowTypeError(_engine.Realm);
  443. }
  444. return booleanTrapResult;
  445. }
  446. }
  447. /// <summary>
  448. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
  449. /// </summary>
  450. protected internal override ObjectInstance? GetPrototypeOf()
  451. {
  452. if (!TryCallHandler(TrapGetProtoTypeOf, new[] { _target }, out var handlerProto))
  453. {
  454. return _target.Prototype;
  455. }
  456. if (!handlerProto.IsObject() && !handlerProto.IsNull())
  457. {
  458. ExceptionHelper.ThrowTypeError(_engine.Realm, "'getPrototypeOf' on proxy: trap returned neither object nor null");
  459. }
  460. if (_target.Extensible)
  461. {
  462. return (ObjectInstance) handlerProto;
  463. }
  464. if (!ReferenceEquals(handlerProto, _target.Prototype))
  465. {
  466. ExceptionHelper.ThrowTypeError(_engine.Realm);
  467. }
  468. return (ObjectInstance) handlerProto;
  469. }
  470. /// <summary>
  471. /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
  472. /// </summary>
  473. internal override bool SetPrototypeOf(JsValue value)
  474. {
  475. if (!TryCallHandler(TrapSetProtoTypeOf, new[] { _target, value }, out var result))
  476. {
  477. return _target.SetPrototypeOf(value);
  478. }
  479. var success = TypeConverter.ToBoolean(result);
  480. if (!success)
  481. {
  482. return false;
  483. }
  484. if (_target.Extensible)
  485. {
  486. return true;
  487. }
  488. if (!ReferenceEquals(value, _target.Prototype))
  489. {
  490. ExceptionHelper.ThrowTypeError(_engine.Realm);
  491. }
  492. return true;
  493. }
  494. internal override bool IsCallable { get; }
  495. private bool TryCallHandler(JsValue propertyName, JsValue[] arguments, out JsValue result)
  496. {
  497. AssertNotRevoked(propertyName);
  498. result = Undefined;
  499. var handlerFunction = _handler!.Get(propertyName);
  500. if (!handlerFunction.IsNullOrUndefined())
  501. {
  502. var callable = handlerFunction as ICallable;
  503. if (callable is null)
  504. {
  505. ExceptionHelper.ThrowTypeError(_engine.Realm, $"{_handler} returned for property '{propertyName}' of object '{_target}' is not a function");
  506. }
  507. result = callable.Call(_handler, arguments);
  508. return true;
  509. }
  510. return false;
  511. }
  512. private void AssertNotRevoked(JsValue key)
  513. {
  514. if (_handler is null)
  515. {
  516. ExceptionHelper.ThrowTypeError(_engine.Realm, $"Cannot perform '{key}' on a proxy that has been revoked");
  517. }
  518. }
  519. private void AssertTargetNotRevoked(JsValue key)
  520. {
  521. if (_target is null)
  522. {
  523. ExceptionHelper.ThrowTypeError(_engine.Realm, $"Cannot perform '{key}' on a proxy that has been revoked");
  524. }
  525. }
  526. public override string ToString() => IsCallable ? "function () { [native code] }" : base.ToString();
  527. }
  528. }