1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Flags: --harmony-proxies --harmony-reflect
6
7
8function sloppyDefaultSet(o, p, v) { return o[p] = v }
9function sloppyReflectSet(o, p, v) { return Reflect.set(o, p, v) }
10function strictDefaultSet(o, p, v) { "use strict"; return o[p] = v }
11function strictReflectSet(o, p, v) { "use strict"; return Reflect.set(o, p, v) }
12
13sloppyDefaultSet.shouldThrow = false;
14sloppyReflectSet.shouldThrow = false;
15strictDefaultSet.shouldThrow = true;
16strictReflectSet.shouldThrow = false;
17
18sloppyDefaultSet.returnsBool = false;
19sloppyReflectSet.returnsBool = true;
20strictDefaultSet.returnsBool = false;
21strictReflectSet.returnsBool = true;
22
23
24function assertTrueIf(flag, x) { if (flag) assertTrue(x) }
25function assertFalseIf(flag, x) { if (flag) assertFalse(x) }
26function assertSetFails(mySet, o, p, v) {
27  if (mySet.shouldThrow) {
28    assertThrows(() => mySet(o, p, v), TypeError);
29  } else {
30    assertFalseIf(mySet.returnsBool, mySet(o, p, v));
31  }
32}
33
34
35function dataDescriptor(x) {
36  return {value: x, writable: true, enumerable: true, configurable: true};
37}
38
39
40function toKey(x) {
41  if (typeof x === "symbol") return x;
42  return String(x);
43}
44
45
46var properties =
47    ["bla", "0", 1, Symbol(), {[Symbol.toPrimitive]() {return "a"}}];
48
49
50function TestForwarding(handler, mySet) {
51  assertTrue(undefined == handler.set);
52  assertTrue(undefined == handler.getOwnPropertyDescriptor);
53  assertTrue(undefined == handler.defineProperty);
54
55  var target = {};
56  var proxy = new Proxy(target, handler);
57
58  // Property does not exist on target.
59  for (var p of properties) {
60    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 42));
61    assertSame(42, target[p]);
62  }
63
64  // Property exists as writable data on target.
65  for (var p of properties) {
66    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));
67    assertSame(0, target[p]);
68  }
69
70  // Property exists as non-writable data on target.
71  for (var p of properties) {
72    Object.defineProperty(target, p,
73        {value: 42, configurable: true, writable: false});
74    assertSetFails(mySet, proxy, p, 42);
75    assertSetFails(mySet, proxy, p, 0);
76    assertEquals(42, target[p]);
77  }
78};
79
80(function () {
81  // No trap.
82  var handler = {};
83  TestForwarding(handler, sloppyDefaultSet);
84  TestForwarding(handler, sloppyReflectSet);
85  TestForwarding(handler, strictDefaultSet);
86  TestForwarding(handler, strictReflectSet);
87})();
88
89(function () {
90  // "Undefined" trap.
91  var handler = { set: null };
92  TestForwarding(handler, sloppyDefaultSet);
93  TestForwarding(handler, sloppyReflectSet);
94  TestForwarding(handler, strictDefaultSet);
95  TestForwarding(handler, strictReflectSet);
96})();
97
98
99function TestForwarding2(mySet) {
100  // Check that setting on a proxy without "set" trap correctly triggers its
101  // "getOwnProperty" trap and its "defineProperty" trap.
102
103  var target = {};
104  var handler = {};
105  var observations = [];
106  var proxy = new Proxy(target, handler);
107
108  handler.getOwnPropertyDescriptor = function() {
109      observations.push(arguments);
110      return Reflect.getOwnPropertyDescriptor(...arguments);
111  }
112
113  handler.defineProperty = function() {
114      observations.push(arguments);
115      return Reflect.defineProperty(...arguments);
116  }
117
118  for (var p of properties) {
119    mySet(proxy, p, 42);
120    assertEquals(2, observations.length)
121    assertArrayEquals([target, toKey(p)], observations[0]);
122    assertSame(target, observations[0][0]);
123    assertArrayEquals([target, toKey(p), dataDescriptor(42)], observations[1]);
124    assertSame(target, observations[1][0]);
125    observations = [];
126
127    mySet(proxy, p, 42);
128    assertEquals(2, observations.length)
129    assertArrayEquals([target, toKey(p)], observations[0]);
130    assertSame(target, observations[0][0]);
131    assertArrayEquals([target, toKey(p), {value: 42}], observations[1]);
132    assertSame(target, observations[1][0]);
133    observations = [];
134  }
135}
136
137TestForwarding2(sloppyDefaultSet);
138TestForwarding2(sloppyReflectSet);
139TestForwarding2(strictDefaultSet);
140TestForwarding2(strictReflectSet);
141
142
143function TestInvalidTrap(proxy, mySet) {
144  for (var p of properties) {
145    assertThrows(() => mySet(proxy, p, 42), TypeError);
146  }
147}
148
149(function () {
150  var target = {};
151  var handler = { set: true };
152  var proxy = new Proxy(target, handler);
153
154  TestInvalidTrap(proxy, sloppyDefaultSet);
155  TestInvalidTrap(proxy, sloppyReflectSet);
156  TestInvalidTrap(proxy, strictDefaultSet);
157  TestInvalidTrap(proxy, strictReflectSet);
158})();
159
160
161function TestTrappingFalsish(mySet) {
162  var target = {};
163  var handler = { set() {return ""} };
164  var proxy = new Proxy(target, handler);
165
166  for (var p of properties) {
167    assertSetFails(mySet, proxy, p, 42);
168  }
169}
170
171TestTrappingFalsish(sloppyDefaultSet);
172TestTrappingFalsish(sloppyReflectSet);
173TestTrappingFalsish(strictDefaultSet);
174TestTrappingFalsish(strictReflectSet);
175
176
177function TestTrappingTrueish(mySet) {
178  var target = {};
179  var handler = { set() {return 42} };
180  var proxy = new Proxy(target, handler);
181
182  // Trap returns trueish and property does not exist in target.
183  for (var p of properties) {
184    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));
185  }
186
187  // Trap returns trueish and target property is configurable or writable data.
188  for (var p of properties) {
189    Object.defineProperty(target, p, {configurable: true, writable: true});
190    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));
191    Object.defineProperty(target, p, {configurable: true, writable: false});
192    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));
193    Object.defineProperty(target, p, {configurable: false, writable: true});
194    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));
195  }
196}
197
198TestTrappingTrueish(sloppyDefaultSet);
199TestTrappingTrueish(sloppyReflectSet);
200TestTrappingTrueish(strictDefaultSet);
201TestTrappingTrueish(strictReflectSet);
202
203
204function TestTrappingTrueish2(mySet) {
205  var target = {};
206  var handler = { set() {return 42} };
207  var proxy = new Proxy(target, handler);
208
209  // Trap returns trueish but target property is frozen data.
210  for (var p of properties) {
211    Object.defineProperty(target, p, {
212        configurable: false, writable: false, value: 0
213    });
214    assertThrows(() => mySet(proxy, p, 666), TypeError);  // New value.
215    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));  // Old value.
216  }
217};
218
219TestTrappingTrueish2(sloppyDefaultSet);
220TestTrappingTrueish2(sloppyReflectSet);
221TestTrappingTrueish2(strictDefaultSet);
222TestTrappingTrueish2(strictReflectSet);
223
224
225function TestTrappingTrueish3(mySet) {
226  var target = {};
227  var handler = { set() {return 42} };
228  var proxy = new Proxy(target, handler);
229
230  // Trap returns trueish and target property is configurable accessor.
231  for (var p of properties) {
232    Object.defineProperty(target, p, { configurable: true, set: undefined });
233    assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0));
234  }
235
236  // Trap returns trueish and target property is non-configurable accessor.
237  for (var p of properties) {
238    Object.defineProperty(target, p, { configurable: false, set: undefined });
239    assertThrows(() => mySet(proxy, p, 0));
240  }
241};
242
243TestTrappingTrueish3(sloppyDefaultSet);
244TestTrappingTrueish3(sloppyReflectSet);
245TestTrappingTrueish3(strictDefaultSet);
246TestTrappingTrueish3(strictReflectSet);
247
248
249function TestTrapReceiverArgument(mySet) {
250  var target = {};
251  var handler = {};
252  var observations = [];
253  var proxy = new Proxy(target, handler);
254  var object = Object.create(proxy);
255
256  handler.set = function() {
257      observations.push(arguments);
258      return Reflect.set(...arguments);
259  }
260
261  for (var p of properties) {
262    mySet(object, p, 42);
263    assertEquals(1, observations.length)
264    assertArrayEquals([target, toKey(p), 42, object], observations[0]);
265    assertSame(target, observations[0][0]);
266    assertSame(object, observations[0][3]);
267    observations = [];
268  }
269};
270
271TestTrapReceiverArgument(sloppyDefaultSet);
272TestTrapReceiverArgument(sloppyReflectSet);
273TestTrapReceiverArgument(strictDefaultSet);
274TestTrapReceiverArgument(strictReflectSet);
275
276
277(function TestTrapReceiverArgument2() {
278  // Check that non-object receiver is passed through as well.
279
280  var target = {};
281  var handler = {};
282  var observations = [];
283  var proxy = new Proxy(target, handler);
284
285  handler.set = function() {
286      observations.push(arguments);
287      return Reflect.set(...arguments);
288  }
289
290  for (var p of properties) {
291    for (var receiver of [null, undefined, 1]) {
292      Reflect.set(proxy, p, 42, receiver);
293      assertEquals(1, observations.length)
294      assertArrayEquals([target, toKey(p), 42, receiver], observations[0]);
295      assertSame(target, observations[0][0]);
296      assertSame(receiver, observations[0][3]);
297      observations = [];
298    }
299  }
300
301  var object = Object.create(proxy);
302  for (var p of properties) {
303    for (var receiver of [null, undefined, 1]) {
304      Reflect.set(object, p, 42, receiver);
305      assertEquals(1, observations.length);
306      assertArrayEquals([target, toKey(p), 42, receiver], observations[0]);
307      assertSame(target, observations[0][0]);
308      assertSame(receiver, observations[0][3]);
309      observations = [];
310    }
311  }
312})();
313