ProxyInstance.cs 21 KB

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