ProxyInstance.cs 17 KB

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