2
0

ProxyInstance.cs 21 KB

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