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 8 function sloppyDefaultSet(o, p, v) { return o[p] = v } 9 function sloppyReflectSet(o, p, v) { return Reflect.set(o, p, v) } 10 function strictDefaultSet(o, p, v) { "use strict"; return o[p] = v } 11 function strictReflectSet(o, p, v) { "use strict"; return Reflect.set(o, p, v) } 12 13 sloppyDefaultSet.shouldThrow = false; 14 sloppyReflectSet.shouldThrow = false; 15 strictDefaultSet.shouldThrow = true; 16 strictReflectSet.shouldThrow = false; 17 18 sloppyDefaultSet.returnsBool = false; 19 sloppyReflectSet.returnsBool = true; 20 strictDefaultSet.returnsBool = false; 21 strictReflectSet.returnsBool = true; 22 23 24 function assertTrueIf(flag, x) { if (flag) assertTrue(x) } 25 function assertFalseIf(flag, x) { if (flag) assertFalse(x) } 26 function 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 35 function dataDescriptor(x) { 36 return {value: x, writable: true, enumerable: true, configurable: true}; 37 } 38 39 40 function toKey(x) { 41 if (typeof x === "symbol") return x; 42 return String(x); 43 } 44 45 46 var properties = 47 ["bla", "0", 1, Symbol(), {[Symbol.toPrimitive]() {return "a"}}]; 48 49 50 function 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 99 function 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 137 TestForwarding2(sloppyDefaultSet); 138 TestForwarding2(sloppyReflectSet); 139 TestForwarding2(strictDefaultSet); 140 TestForwarding2(strictReflectSet); 141 142 143 function 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 161 function 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 171 TestTrappingFalsish(sloppyDefaultSet); 172 TestTrappingFalsish(sloppyReflectSet); 173 TestTrappingFalsish(strictDefaultSet); 174 TestTrappingFalsish(strictReflectSet); 175 176 177 function 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 198 TestTrappingTrueish(sloppyDefaultSet); 199 TestTrappingTrueish(sloppyReflectSet); 200 TestTrappingTrueish(strictDefaultSet); 201 TestTrappingTrueish(strictReflectSet); 202 203 204 function 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 219 TestTrappingTrueish2(sloppyDefaultSet); 220 TestTrappingTrueish2(sloppyReflectSet); 221 TestTrappingTrueish2(strictDefaultSet); 222 TestTrappingTrueish2(strictReflectSet); 223 224 225 function 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 243 TestTrappingTrueish3(sloppyDefaultSet); 244 TestTrappingTrueish3(sloppyReflectSet); 245 TestTrappingTrueish3(strictDefaultSet); 246 TestTrappingTrueish3(strictReflectSet); 247 248 249 function 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 271 TestTrapReceiverArgument(sloppyDefaultSet); 272 TestTrapReceiverArgument(sloppyReflectSet); 273 TestTrapReceiverArgument(strictDefaultSet); 274 TestTrapReceiverArgument(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