ProxyInstance.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. using System.Collections.Generic;
  2. using Jint.Native.Object;
  3. using Jint.Runtime;
  4. using Jint.Runtime.Descriptors;
  5. namespace Jint.Native.Proxy
  6. {
  7. public 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. }
  35. public JsValue Call(JsValue thisObject, JsValue[] arguments)
  36. {
  37. var jsValues = new[] { _target, thisObject, _engine.Realm.Intrinsics.Array.ConstructFast(arguments) };
  38. if (TryCallHandler(TrapApply, jsValues, out var result))
  39. {
  40. return result;
  41. }
  42. var callable = _target as ICallable;
  43. if (callable is null)
  44. {
  45. ExceptionHelper.ThrowTypeError(_engine.Realm, _target + " is not a function");
  46. }
  47. return callable.Call(thisObject, arguments);
  48. }
  49. public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
  50. {
  51. var argArray = _engine.Realm.Intrinsics.Array.Construct(arguments, _engine.Realm.Intrinsics.Array);
  52. if (!TryCallHandler(TrapConstruct, new[] { _target, argArray, newTarget }, out var result))
  53. {
  54. var constructor = _target as IConstructor;
  55. if (constructor is null)
  56. {
  57. ExceptionHelper.ThrowTypeError(_engine.Realm);
  58. }
  59. return constructor.Construct(arguments, newTarget);
  60. }
  61. var oi = result as ObjectInstance;
  62. if (oi is null)
  63. {
  64. ExceptionHelper.ThrowTypeError(_engine.Realm);
  65. }
  66. return oi;
  67. }
  68. public override bool IsArray()
  69. {
  70. AssertNotRevoked(KeyIsArray);
  71. return _target.IsArray();
  72. }
  73. internal override bool IsConstructor =>
  74. _target is not null && _target.IsConstructor
  75. || _handler is not null
  76. && _handler.TryGetValue(TrapConstruct, out var handlerFunction)
  77. && handlerFunction is IConstructor;
  78. public override JsValue Get(JsValue property, JsValue receiver)
  79. {
  80. if (property == KeyFunctionRevoke || !TryCallHandler(TrapGet, new JsValue[] {_target, property, this}, out var result))
  81. {
  82. AssertTargetNotRevoked(property);
  83. return _target.Get(property, receiver);
  84. }
  85. AssertTargetNotRevoked(property);
  86. var targetDesc = _target.GetOwnProperty(property);
  87. if (targetDesc != PropertyDescriptor.Undefined)
  88. {
  89. if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable && !ReferenceEquals(result, targetDesc._value))
  90. {
  91. ExceptionHelper.ThrowTypeError(_engine.Realm);
  92. }
  93. if (targetDesc.IsAccessorDescriptor() && !targetDesc.Configurable && targetDesc.Get.IsUndefined() && !result.IsUndefined())
  94. {
  95. 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}')");
  96. }
  97. }
  98. return result;
  99. }
  100. public override List<JsValue> GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
  101. {
  102. if (!TryCallHandler(TrapOwnKeys, new JsValue[] {_target }, out var result))
  103. {
  104. return _target.GetOwnPropertyKeys(types);
  105. }
  106. var trapResult = new List<JsValue>(_engine.Realm.Intrinsics.Function.PrototypeObject.CreateListFromArrayLike(result, Types.String | Types.Symbol));
  107. if (trapResult.Count != new HashSet<JsValue>(trapResult).Count)
  108. {
  109. ExceptionHelper.ThrowTypeError(_engine.Realm);
  110. }
  111. var extensibleTarget = _target.Extensible;
  112. var targetKeys = _target.GetOwnPropertyKeys(types);
  113. var targetConfigurableKeys = new List<JsValue>();
  114. var targetNonconfigurableKeys = new List<JsValue>();
  115. foreach (var property in targetKeys)
  116. {
  117. var desc = _target.GetOwnProperty(property);
  118. if (desc != PropertyDescriptor.Undefined && !desc.Configurable)
  119. {
  120. targetNonconfigurableKeys.Add(property);
  121. }
  122. else
  123. {
  124. targetConfigurableKeys.Add(property);
  125. }
  126. }
  127. var uncheckedResultKeys = new HashSet<JsValue>(trapResult);
  128. for (var i = 0; i < targetNonconfigurableKeys.Count; i++)
  129. {
  130. var key = targetNonconfigurableKeys[i];
  131. if (!uncheckedResultKeys.Remove(key))
  132. {
  133. ExceptionHelper.ThrowTypeError(_engine.Realm);
  134. }
  135. }
  136. if (extensibleTarget)
  137. {
  138. return trapResult;
  139. }
  140. for (var i = 0; i < targetConfigurableKeys.Count; i++)
  141. {
  142. var key = targetConfigurableKeys[i];
  143. if (!uncheckedResultKeys.Remove(key))
  144. {
  145. ExceptionHelper.ThrowTypeError(_engine.Realm);
  146. }
  147. }
  148. if (uncheckedResultKeys.Count > 0)
  149. {
  150. ExceptionHelper.ThrowTypeError(_engine.Realm);
  151. }
  152. return trapResult;
  153. }
  154. public override PropertyDescriptor GetOwnProperty(JsValue property)
  155. {
  156. if (!TryCallHandler(TrapGetOwnPropertyDescriptor, new[] { _target, property }, out var result))
  157. {
  158. return _target.GetOwnProperty(property);
  159. }
  160. if (!result.IsObject() && !result.IsUndefined())
  161. {
  162. ExceptionHelper.ThrowTypeError(_engine.Realm);
  163. }
  164. var targetDesc = _target.GetOwnProperty(property);
  165. if (result.IsUndefined())
  166. {
  167. if (targetDesc == PropertyDescriptor.Undefined)
  168. {
  169. return targetDesc;
  170. }
  171. if (!targetDesc.Configurable || !_target.Extensible)
  172. {
  173. ExceptionHelper.ThrowTypeError(_engine.Realm);
  174. }
  175. return PropertyDescriptor.Undefined;
  176. }
  177. var extensibleTarget = _target.Extensible;
  178. var resultDesc = PropertyDescriptor.ToPropertyDescriptor(_engine.Realm, result);
  179. var valid = IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc);
  180. if (!valid)
  181. {
  182. ExceptionHelper.ThrowTypeError(_engine.Realm);
  183. }
  184. if (!resultDesc.Configurable && (targetDesc == PropertyDescriptor.Undefined || targetDesc.Configurable))
  185. {
  186. ExceptionHelper.ThrowTypeError(_engine.Realm);
  187. }
  188. return resultDesc;
  189. }
  190. public override bool Set(JsValue property, JsValue value, JsValue receiver)
  191. {
  192. if (!TryCallHandler(TrapSet, new[] { _target, property, value, this }, out var trapResult))
  193. {
  194. return _target.Set(property, value, receiver);
  195. }
  196. var result = TypeConverter.ToBoolean(trapResult);
  197. if (!result)
  198. {
  199. return false;
  200. }
  201. var targetDesc = _target.GetOwnProperty(property);
  202. if (targetDesc != PropertyDescriptor.Undefined)
  203. {
  204. if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable)
  205. {
  206. if (targetDesc.Value != value)
  207. {
  208. ExceptionHelper.ThrowTypeError(_engine.Realm);
  209. }
  210. }
  211. if (targetDesc.IsAccessorDescriptor() && !targetDesc.Configurable)
  212. {
  213. if (targetDesc.Set.IsUndefined())
  214. {
  215. ExceptionHelper.ThrowTypeError(_engine.Realm);
  216. }
  217. }
  218. }
  219. return true;
  220. }
  221. public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
  222. {
  223. var arguments = new[] { _target, property, PropertyDescriptor.FromPropertyDescriptor(_engine, desc) };
  224. if (!TryCallHandler(TrapDefineProperty, arguments, out var result))
  225. {
  226. return _target.DefineOwnProperty(property, desc);
  227. }
  228. var success = TypeConverter.ToBoolean(result);
  229. if (!success)
  230. {
  231. return false;
  232. }
  233. var targetDesc = _target.GetOwnProperty(property);
  234. var extensibleTarget = _target.Extensible;
  235. var settingConfigFalse = !desc.Configurable;
  236. if (targetDesc == PropertyDescriptor.Undefined)
  237. {
  238. if (!extensibleTarget || settingConfigFalse)
  239. {
  240. ExceptionHelper.ThrowTypeError(_engine.Realm);
  241. }
  242. }
  243. else
  244. {
  245. if (!IsCompatiblePropertyDescriptor(extensibleTarget, desc, targetDesc))
  246. {
  247. ExceptionHelper.ThrowTypeError(_engine.Realm);
  248. }
  249. if (targetDesc.Configurable && settingConfigFalse)
  250. {
  251. ExceptionHelper.ThrowTypeError(_engine.Realm);
  252. }
  253. }
  254. return true;
  255. }
  256. private static bool IsCompatiblePropertyDescriptor(bool extensible, PropertyDescriptor desc, PropertyDescriptor current)
  257. {
  258. return ValidateAndApplyPropertyDescriptor(null, JsString.Empty, extensible, desc, current);
  259. }
  260. public override bool HasProperty(JsValue property)
  261. {
  262. if (!TryCallHandler(TrapHas, new [] { _target, property }, out var jsValue))
  263. {
  264. return _target.HasProperty(property);
  265. }
  266. var trapResult = TypeConverter.ToBoolean(jsValue);
  267. if (!trapResult)
  268. {
  269. var targetDesc = _target.GetOwnProperty(property);
  270. if (targetDesc != PropertyDescriptor.Undefined)
  271. {
  272. if (!targetDesc.Configurable)
  273. {
  274. ExceptionHelper.ThrowTypeError(_engine.Realm);
  275. }
  276. if (!_target.Extensible)
  277. {
  278. ExceptionHelper.ThrowTypeError(_engine.Realm);
  279. }
  280. }
  281. }
  282. return trapResult;
  283. }
  284. public override bool Delete(JsValue property)
  285. {
  286. if (!TryCallHandler(TrapDeleteProperty, new JsValue[] { _target, property }, out var result))
  287. {
  288. return _target.Delete(property);
  289. }
  290. var success = TypeConverter.ToBoolean(result);
  291. if (success)
  292. {
  293. var targetDesc = _target.GetOwnProperty(property);
  294. if (targetDesc != PropertyDescriptor.Undefined && !targetDesc.Configurable)
  295. {
  296. ExceptionHelper.ThrowTypeError(_engine.Realm, $"'deleteProperty' on proxy: trap returned truish for property '{property}' which is non-configurable in the proxy target");
  297. }
  298. }
  299. return success;
  300. }
  301. public override JsValue PreventExtensions()
  302. {
  303. if (!TryCallHandler(TrapPreventExtensions, new[] { _target }, out var result))
  304. {
  305. return _target.PreventExtensions();
  306. }
  307. var success = TypeConverter.ToBoolean(result);
  308. if (success && _target.Extensible)
  309. {
  310. ExceptionHelper.ThrowTypeError(_engine.Realm);
  311. }
  312. return success ? JsBoolean.True : JsBoolean.False;
  313. }
  314. public override bool Extensible
  315. {
  316. get
  317. {
  318. if (!TryCallHandler(TrapIsExtensible, new[] { _target }, out var result))
  319. {
  320. return _target.Extensible;
  321. }
  322. var booleanTrapResult = TypeConverter.ToBoolean(result);
  323. var targetResult = _target.Extensible;
  324. if (booleanTrapResult != targetResult)
  325. {
  326. ExceptionHelper.ThrowTypeError(_engine.Realm);
  327. }
  328. return booleanTrapResult;
  329. }
  330. }
  331. protected internal override ObjectInstance GetPrototypeOf()
  332. {
  333. if (!TryCallHandler(TrapGetProtoTypeOf, new [] { _target }, out var handlerProto ))
  334. {
  335. return _target.Prototype;
  336. }
  337. if (!handlerProto.IsObject() && !handlerProto.IsNull())
  338. {
  339. ExceptionHelper.ThrowTypeError(_engine.Realm, "'getPrototypeOf' on proxy: trap returned neither object nor null");
  340. }
  341. if (_target.Extensible)
  342. {
  343. return (ObjectInstance) handlerProto;
  344. }
  345. if (!ReferenceEquals(handlerProto, _target.Prototype))
  346. {
  347. ExceptionHelper.ThrowTypeError(_engine.Realm);
  348. }
  349. return (ObjectInstance) handlerProto;
  350. }
  351. public override bool SetPrototypeOf(JsValue value)
  352. {
  353. if (!TryCallHandler(TrapSetProtoTypeOf, new[] { _target, value }, out var result))
  354. {
  355. return _target.SetPrototypeOf(value);
  356. }
  357. var success = TypeConverter.ToBoolean(result);
  358. if (!success)
  359. {
  360. return false;
  361. }
  362. if (_target.Extensible)
  363. {
  364. return true;
  365. }
  366. if (!ReferenceEquals(value, _target.Prototype))
  367. {
  368. ExceptionHelper.ThrowTypeError(_engine.Realm);
  369. }
  370. return true;
  371. }
  372. internal override bool IsCallable => _target is ICallable;
  373. private bool TryCallHandler(JsValue propertyName, JsValue[] arguments, out JsValue result)
  374. {
  375. AssertNotRevoked(propertyName);
  376. result = Undefined;
  377. var handlerFunction = _handler.Get(propertyName);
  378. if (!handlerFunction.IsNullOrUndefined())
  379. {
  380. var callable = handlerFunction as ICallable;
  381. if (callable is null)
  382. {
  383. ExceptionHelper.ThrowTypeError(_engine.Realm, $"{_handler} returned for property '{propertyName}' of object '{_target}' is not a function");
  384. }
  385. result = callable.Call(_handler, arguments);
  386. return true;
  387. }
  388. return false;
  389. }
  390. private void AssertNotRevoked(JsValue key)
  391. {
  392. if (_handler is null)
  393. {
  394. ExceptionHelper.ThrowTypeError(_engine.Realm, $"Cannot perform '{key}' on a proxy that has been revoked");
  395. }
  396. }
  397. private void AssertTargetNotRevoked(JsValue key)
  398. {
  399. if (_target is null)
  400. {
  401. ExceptionHelper.ThrowTypeError(_engine.Realm, $"Cannot perform '{key}' on a proxy that has been revoked");
  402. }
  403. }
  404. public override string ToString() => "function () { [native code] }";
  405. }
  406. }