boundConstruction.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  6. //setup code
  7. let constructionCount = 0;
  8. let functionCallCount = 0;
  9. var classConstructorCount = 0; // var not let, so it can be accessed from other context
  10. function a(arg1, arg2) {
  11. this[arg1] = arg2;
  12. this.localVal = this.protoVal;
  13. ++functionCallCount;
  14. }
  15. a.prototype.protoVal = 1;
  16. class A {
  17. constructor(arg1, arg2) {
  18. this[arg1] = arg2;
  19. this.localVal = this.protoVal;
  20. ++classConstructorCount;
  21. }
  22. }
  23. A.prototype.protoVal = 2;
  24. const crossContext = WScript.LoadScript("\
  25. class A { \
  26. constructor(arg1, arg2) { \
  27. this[arg1] = arg2; \
  28. this.localVal = this.protoVal; \
  29. ++other.classConstructorCount; \
  30. } \
  31. } \
  32. A.prototype.protoVal = 2; \
  33. this.A2 = A; \
  34. ", "samethread");
  35. crossContext.other = this;
  36. const trapped = new Proxy(a, {
  37. construct: function (x, y, z) {
  38. ++constructionCount;
  39. return Reflect.construct(x, y, z);
  40. }
  41. });
  42. const trappedClass = new Proxy(A, {
  43. construct: function (x, y, z) {
  44. ++constructionCount;
  45. return Reflect.construct(x, y, z);
  46. }
  47. });
  48. const evalTrappedClass = new Proxy(A, {
  49. construct: function (x, y, z) {
  50. return eval("++constructionCount; Reflect.construct(x, y, z);");
  51. }
  52. });
  53. const withTrappedClass = new Proxy(A, {
  54. construct: function (x, y, z) {
  55. with (Reflect) {
  56. ++constructionCount;
  57. return construct(x, y, z);
  58. }
  59. }
  60. });
  61. const noTrap = new Proxy(a, {});
  62. const noTrapClass = new Proxy(A, {});
  63. const noTrapClassCrossContext = new Proxy(crossContext.A2, {});
  64. const boundObject = {};
  65. const boundFunc = a.bind(boundObject, "prop-name");
  66. boundFunc.prototype = {}; // so we can extend from it
  67. const boundClass = A.bind(boundObject, "prop-name");
  68. boundClass.prototype = {};
  69. const boundTrapped = trapped.bind(boundObject, "prop-name");
  70. const boundUnTrapped = noTrap.bind(boundObject, "prop-name");
  71. const boundTrappedClass = trappedClass.bind(boundObject, "prop-name");
  72. const boundUnTrappedClass = noTrapClass.bind(boundObject, "prop-name");
  73. const boundUnTrappedClassCrossContext = noTrapClassCrossContext.bind(boundObject, "prop-name");
  74. class newTarget {}
  75. newTarget.prototype.protoVal = 3;
  76. class ExtendsBoundFunc extends boundFunc {}
  77. ExtendsBoundFunc.prototype.protoVal = 4;
  78. class ExtendsBoundClass extends boundClass {}
  79. ExtendsBoundClass.prototype.protoVal = 5;
  80. class ExtendsFunc extends a {}
  81. ExtendsFunc.prototype.protoVal = 6;
  82. class ExtendsClass extends A {}
  83. ExtendsClass.prototype.protoVal = 7;
  84. const boundClassExtendsFunc = ExtendsFunc.bind(boundObject, "prop-name");
  85. const boundClassExtendsClass = ExtendsClass.bind(boundObject, "prop-name");
  86. // flags
  87. const ConstructionMode = {
  88. useReflect: 1,
  89. useEval: 2,
  90. useWith: 4,
  91. };
  92. function testImpl(ctor, constructionMode, expectedPrototype, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount) {
  93. const useReflect = (constructionMode & 1) === 1;
  94. constructionCount = 0;
  95. functionCallCount = 0;
  96. classConstructorCount = 0;
  97. let obj;
  98. switch (constructionMode) {
  99. case 0:
  100. obj = new ctor("prop-value");
  101. break;
  102. case 1:
  103. obj = Reflect.construct(ctor, ["prop-value"], newTarget);
  104. break;
  105. case 2:
  106. eval('obj = new ctor("prop-value");');
  107. break;
  108. case 3:
  109. eval('obj = Reflect.construct(ctor, ["prop-value"], newTarget);');
  110. break;
  111. case 4:
  112. with ({}) { obj = new ctor("prop-value"); }
  113. break;
  114. case 5:
  115. with (Reflect) { obj = construct(ctor, ["prop-value"], newTarget); }
  116. break;
  117. case 6:
  118. eval('with ({}) { obj = new ctor("prop-value"); }');
  119. break;
  120. case 7:
  121. eval('with (Reflect) { obj = construct(ctor, ["prop-value"], newTarget); }');
  122. break;
  123. default:
  124. throw new Error("unrecognized mode");
  125. }
  126. assert.areNotEqual(boundObject, obj, "bound function should ignore bound this when constructing");
  127. assert.areEqual("prop-value", obj["prop-name"], "bound function should keep bound arguments when constructing");
  128. assert.areEqual(expectedConstructionCount, constructionCount, `proxy construct trap should be called ${expectedConstructionCount} times`);
  129. assert.areEqual(expectedFunctionCallCount, functionCallCount, `base function-style constructor should be called ${expectedFunctionCallCount} times`);
  130. assert.areEqual(expectedClassConstructorCount, classConstructorCount, `base class constructor should be called ${expectedClassConstructorCount} times`);
  131. assert.strictEqual(expectedPrototype.prototype, obj.__proto__, useReflect ? "bound function should use explicit newTarget if provided" : "constructed object should be instance of original function");
  132. assert.areEqual(expectedPrototype.prototype.protoVal, obj.localVal, "prototype should be available during construction");
  133. }
  134. function test(ctor, expectedPrototype, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount) {
  135. testImpl(ctor, 0, expectedPrototype, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  136. testImpl(ctor, 1, newTarget, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  137. testImpl(ctor, 2, expectedPrototype, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  138. testImpl(ctor, 3, newTarget, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  139. testImpl(ctor, 4, expectedPrototype, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  140. testImpl(ctor, 5, newTarget, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  141. testImpl(ctor, 6, expectedPrototype, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  142. testImpl(ctor, 7, newTarget, expectedConstructionCount, expectedFunctionCallCount, expectedClassConstructorCount);
  143. }
  144. const tests = [
  145. {
  146. name : "Construct trapped bound proxy around function",
  147. body : function() {
  148. test(boundTrapped, a, 1, 1, 0);
  149. }
  150. },
  151. {
  152. name : "Construct bound proxy around function",
  153. body : function() {
  154. test(boundUnTrapped, a, 0, 1, 0);
  155. }
  156. },
  157. {
  158. name : "Construct trapped bound proxy around class",
  159. body : function() {
  160. test(boundTrappedClass, A, 1, 0, 1);
  161. }
  162. },
  163. {
  164. name : "Construct bound proxy around class",
  165. body : function() {
  166. test(boundUnTrappedClass, A, 0, 0, 1);
  167. }
  168. },
  169. {
  170. name : "Construct bound proxy around class with cross-context construction",
  171. body : function() {
  172. test(boundUnTrappedClassCrossContext, crossContext.A2, 0, 0, 1);
  173. }
  174. },
  175. {
  176. name: "Trapped bound proxy around class using eval",
  177. body: function () {
  178. test(evalTrappedClass.bind(boundObject, "prop-name"), A, 1, 0, 1);
  179. }
  180. },
  181. {
  182. name: "Trapped bound proxy around class using with",
  183. body: function () {
  184. test(withTrappedClass.bind(boundObject, "prop-name"), A, 1, 0, 1);
  185. }
  186. },
  187. {
  188. name : "Construct bound function",
  189. body : function() {
  190. test(boundFunc, a, 0, 1, 0);
  191. }
  192. },
  193. {
  194. name : "Construct bound class",
  195. body : function() {
  196. test(boundClass, A, 0, 0, 1);
  197. }
  198. },
  199. {
  200. name : "Construct class extending bound function",
  201. body : function() {
  202. test(ExtendsBoundFunc, ExtendsBoundFunc, 0, 1, 0);
  203. }
  204. },
  205. {
  206. name : "Construct class extending bound class",
  207. body : function() {
  208. test(ExtendsBoundClass, ExtendsBoundClass, 0, 0, 1);
  209. }
  210. },
  211. {
  212. name : "Construct bound class that extends a function",
  213. body : function() {
  214. test(boundClassExtendsFunc, ExtendsFunc, 0, 1, 0);
  215. }
  216. },
  217. {
  218. name : "Construct bound class that extends another class",
  219. body : function() {
  220. test(boundClassExtendsClass, ExtendsClass, 0, 0, 1);
  221. }
  222. },
  223. {
  224. name : "Construct bound proxy around proxy",
  225. body : function() {
  226. const baseProxies = [
  227. { proxy: trapped, func: true, trap: true },
  228. { proxy: trappedClass, func: false, trap: true },
  229. { proxy: noTrap, func: true, trap: false },
  230. { proxy: noTrapClass, func: false, trap: false }
  231. ];
  232. for (const { proxy, func, trap } of baseProxies) {
  233. const p1 = new Proxy(proxy, {});
  234. const p2 = new Proxy(proxy, {
  235. construct: function (x, y, z) {
  236. ++constructionCount;
  237. return Reflect.construct(x, y, z);
  238. }
  239. });
  240. const b1 = p1.bind(boundObject, "prop-name");
  241. const b2 = p2.bind(boundObject, "prop-name");
  242. test(b1, func ? a : A, trap ? 1 : 0, func ? 1 : 0, func ? 0 : 1);
  243. test(b2, func ? a : A, 1 + (trap ? 1 : 0), func ? 1 : 0, func ? 0 : 1);
  244. }
  245. }
  246. },
  247. {
  248. name: "Construct built-in class",
  249. body: function () {
  250. const bound = Boolean.bind(boundObject, false);
  251. const noTrap = new Proxy(Boolean, {});
  252. const trap = new Proxy(Boolean, {
  253. construct: function (x, y, z) {
  254. return Reflect.construct(x, y, z);
  255. }
  256. });
  257. function verify(obj, expectedNewTarget) {
  258. assert.isTrue(Boolean.prototype.valueOf.call(obj) === false, "Boolean should represent value false");
  259. assert.isFalse(obj === false, "Boolean is not a value type");
  260. assert.strictEqual(expectedNewTarget.prototype, obj.__proto__, "Object should get constructed with appropriate prototype");
  261. }
  262. verify(new bound(true), Boolean);
  263. verify(Reflect.construct(bound, [true], newTarget), newTarget);
  264. verify(new noTrap(false), Boolean);
  265. verify(eval("new noTrap(false)"), Boolean);
  266. verify(Reflect.construct(noTrap, [false], newTarget), newTarget);
  267. verify(new trap(false), Boolean);
  268. verify(Reflect.construct(trap, [false], newTarget), newTarget);
  269. verify(eval("Reflect.construct(trap, [false], newTarget)"), newTarget);
  270. }
  271. }
  272. ];
  273. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });