1 | /* |
2 | * Copyright (C) 2016-2019 Apple Inc. All Rights Reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "ProxyObject.h" |
28 | |
29 | #include "ArrayConstructor.h" |
30 | #include "Error.h" |
31 | #include "IdentifierInlines.h" |
32 | #include "JSCInlines.h" |
33 | #include "JSObjectInlines.h" |
34 | #include "ObjectConstructor.h" |
35 | #include "SlotVisitorInlines.h" |
36 | #include "StructureInlines.h" |
37 | #include "VMInlines.h" |
38 | #include <wtf/NoTailCalls.h> |
39 | |
40 | // Note that we use NO_TAIL_CALLS() throughout this file because we rely on the machine stack |
41 | // growing larger for throwing OOM errors for when we have an effectively cyclic prototype chain. |
42 | |
43 | namespace JSC { |
44 | |
45 | STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(ProxyObject); |
46 | |
47 | const ClassInfo ProxyObject::s_info = { "ProxyObject" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ProxyObject) }; |
48 | |
49 | ProxyObject::ProxyObject(VM& vm, Structure* structure) |
50 | : Base(vm, structure) |
51 | { |
52 | } |
53 | |
54 | String ProxyObject::toStringName(const JSObject* object, JSGlobalObject* globalObject) |
55 | { |
56 | VM& vm = globalObject->vm(); |
57 | auto scope = DECLARE_THROW_SCOPE(vm); |
58 | const ProxyObject* proxy = jsCast<const ProxyObject*>(object); |
59 | while (proxy) { |
60 | const JSObject* target = proxy->target(); |
61 | bool targetIsArray = isArray(globalObject, target); |
62 | if (UNLIKELY(scope.exception())) |
63 | break; |
64 | if (targetIsArray) |
65 | RELEASE_AND_RETURN(scope, target->classInfo(vm)->methodTable.toStringName(target, globalObject)); |
66 | |
67 | proxy = jsDynamicCast<const ProxyObject*>(vm, target); |
68 | } |
69 | return "Object"_s ; |
70 | } |
71 | |
72 | Structure* ProxyObject::structureForTarget(JSGlobalObject* globalObject, JSValue target) |
73 | { |
74 | if (!target.isObject()) |
75 | return globalObject->proxyObjectStructure(); |
76 | |
77 | JSObject* targetAsObject = jsCast<JSObject*>(target); |
78 | CallData ignoredCallData; |
79 | VM& vm = globalObject->vm(); |
80 | bool isCallable = targetAsObject->methodTable(vm)->getCallData(targetAsObject, ignoredCallData) != CallType::None; |
81 | return isCallable ? globalObject->callableProxyObjectStructure() : globalObject->proxyObjectStructure(); |
82 | } |
83 | |
84 | void ProxyObject::finishCreation(VM& vm, JSGlobalObject* globalObject, JSValue target, JSValue handler) |
85 | { |
86 | auto scope = DECLARE_THROW_SCOPE(vm); |
87 | Base::finishCreation(vm); |
88 | ASSERT(type() == ProxyObjectType); |
89 | if (!target.isObject()) { |
90 | throwTypeError(globalObject, scope, "A Proxy's 'target' should be an Object"_s ); |
91 | return; |
92 | } |
93 | if (ProxyObject* targetAsProxy = jsDynamicCast<ProxyObject*>(vm, target)) { |
94 | if (targetAsProxy->isRevoked()) { |
95 | throwTypeError(globalObject, scope, "A Proxy's 'target' shouldn't be a revoked Proxy"_s ); |
96 | return; |
97 | } |
98 | } |
99 | if (!handler.isObject()) { |
100 | throwTypeError(globalObject, scope, "A Proxy's 'handler' should be an Object"_s ); |
101 | return; |
102 | } |
103 | if (ProxyObject* handlerAsProxy = jsDynamicCast<ProxyObject*>(vm, handler)) { |
104 | if (handlerAsProxy->isRevoked()) { |
105 | throwTypeError(globalObject, scope, "A Proxy's 'handler' shouldn't be a revoked Proxy"_s ); |
106 | return; |
107 | } |
108 | } |
109 | |
110 | JSObject* targetAsObject = jsCast<JSObject*>(target); |
111 | |
112 | CallData ignoredCallData; |
113 | m_isCallable = targetAsObject->methodTable(vm)->getCallData(targetAsObject, ignoredCallData) != CallType::None; |
114 | if (m_isCallable) { |
115 | TypeInfo info = structure(vm)->typeInfo(); |
116 | RELEASE_ASSERT(info.implementsHasInstance() && info.implementsDefaultHasInstance()); |
117 | } |
118 | |
119 | m_isConstructible = jsCast<JSObject*>(target)->isConstructor(vm); |
120 | |
121 | m_target.set(vm, this, targetAsObject); |
122 | m_handler.set(vm, this, handler); |
123 | } |
124 | |
125 | static const ASCIILiteral s_proxyAlreadyRevokedErrorMessage { "Proxy has already been revoked. No more operations are allowed to be performed on it"_s }; |
126 | |
127 | static JSValue performProxyGet(JSGlobalObject* globalObject, ProxyObject* proxyObject, JSValue receiver, PropertyName propertyName) |
128 | { |
129 | NO_TAIL_CALLS(); |
130 | |
131 | VM& vm = globalObject->vm(); |
132 | auto scope = DECLARE_THROW_SCOPE(vm); |
133 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
134 | throwStackOverflowError(globalObject, scope); |
135 | return { }; |
136 | } |
137 | |
138 | JSObject* target = proxyObject->target(); |
139 | |
140 | auto performDefaultGet = [&] { |
141 | scope.release(); |
142 | PropertySlot slot(receiver, PropertySlot::InternalMethodType::Get); |
143 | bool hasProperty = target->getPropertySlot(globalObject, propertyName, slot); |
144 | EXCEPTION_ASSERT(!scope.exception() || !hasProperty); |
145 | if (hasProperty) |
146 | RELEASE_AND_RETURN(scope, slot.getValue(globalObject, propertyName)); |
147 | |
148 | return jsUndefined(); |
149 | }; |
150 | |
151 | if (propertyName.isPrivateName()) |
152 | return jsUndefined(); |
153 | |
154 | JSValue handlerValue = proxyObject->handler(); |
155 | if (handlerValue.isNull()) |
156 | return throwTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
157 | |
158 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
159 | CallData callData; |
160 | CallType callType; |
161 | JSValue getHandler = handler->getMethod(globalObject, callData, callType, vm.propertyNames->get, "'get' property of a Proxy's handler object should be callable"_s ); |
162 | RETURN_IF_EXCEPTION(scope, { }); |
163 | |
164 | if (getHandler.isUndefined()) |
165 | return performDefaultGet(); |
166 | |
167 | MarkedArgumentBuffer arguments; |
168 | arguments.append(target); |
169 | arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid()))); |
170 | arguments.append(receiver); |
171 | ASSERT(!arguments.hasOverflowed()); |
172 | JSValue trapResult = call(globalObject, getHandler, callType, callData, handler, arguments); |
173 | RETURN_IF_EXCEPTION(scope, { }); |
174 | |
175 | PropertyDescriptor descriptor; |
176 | bool result = target->getOwnPropertyDescriptor(globalObject, propertyName, descriptor); |
177 | EXCEPTION_ASSERT(!scope.exception() || !result); |
178 | if (result) { |
179 | if (descriptor.isDataDescriptor() && !descriptor.configurable() && !descriptor.writable()) { |
180 | bool isSame = sameValue(globalObject, descriptor.value(), trapResult); |
181 | RETURN_IF_EXCEPTION(scope, { }); |
182 | if (!isSame) |
183 | return throwTypeError(globalObject, scope, "Proxy handler's 'get' result of a non-configurable and non-writable property should be the same value as the target's property"_s ); |
184 | } else if (descriptor.isAccessorDescriptor() && !descriptor.configurable() && descriptor.getter().isUndefined()) { |
185 | if (!trapResult.isUndefined()) |
186 | return throwTypeError(globalObject, scope, "Proxy handler's 'get' result of a non-configurable accessor property without a getter should be undefined"_s ); |
187 | } |
188 | } |
189 | |
190 | RETURN_IF_EXCEPTION(scope, { }); |
191 | |
192 | return trapResult; |
193 | } |
194 | |
195 | bool ProxyObject::performGet(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
196 | { |
197 | NO_TAIL_CALLS(); |
198 | |
199 | VM& vm = globalObject->vm(); |
200 | auto scope = DECLARE_THROW_SCOPE(vm); |
201 | JSValue result = performProxyGet(globalObject, this, slot.thisValue(), propertyName); |
202 | RETURN_IF_EXCEPTION(scope, false); |
203 | unsigned ignoredAttributes = 0; |
204 | slot.setValue(this, ignoredAttributes, result); |
205 | return true; |
206 | } |
207 | |
208 | bool ProxyObject::performInternalMethodGetOwnProperty(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
209 | { |
210 | NO_TAIL_CALLS(); |
211 | |
212 | VM& vm = globalObject->vm(); |
213 | auto scope = DECLARE_THROW_SCOPE(vm); |
214 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
215 | throwStackOverflowError(globalObject, scope); |
216 | return false; |
217 | } |
218 | JSObject* target = this->target(); |
219 | |
220 | auto performDefaultGetOwnProperty = [&] { |
221 | return target->methodTable(vm)->getOwnPropertySlot(target, globalObject, propertyName, slot); |
222 | }; |
223 | |
224 | if (propertyName.isPrivateName()) |
225 | return false; |
226 | |
227 | JSValue handlerValue = this->handler(); |
228 | if (handlerValue.isNull()) { |
229 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
230 | return false; |
231 | } |
232 | |
233 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
234 | CallData callData; |
235 | CallType callType; |
236 | JSValue getOwnPropertyDescriptorMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "getOwnPropertyDescriptor" ), "'getOwnPropertyDescriptor' property of a Proxy's handler should be callable"_s ); |
237 | RETURN_IF_EXCEPTION(scope, false); |
238 | if (getOwnPropertyDescriptorMethod.isUndefined()) |
239 | RELEASE_AND_RETURN(scope, performDefaultGetOwnProperty()); |
240 | |
241 | MarkedArgumentBuffer arguments; |
242 | arguments.append(target); |
243 | arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid()))); |
244 | ASSERT(!arguments.hasOverflowed()); |
245 | JSValue trapResult = call(globalObject, getOwnPropertyDescriptorMethod, callType, callData, handler, arguments); |
246 | RETURN_IF_EXCEPTION(scope, false); |
247 | |
248 | if (!trapResult.isUndefined() && !trapResult.isObject()) { |
249 | throwVMTypeError(globalObject, scope, "result of 'getOwnPropertyDescriptor' call should either be an Object or undefined"_s ); |
250 | return false; |
251 | } |
252 | |
253 | PropertyDescriptor targetPropertyDescriptor; |
254 | bool isTargetPropertyDescriptorDefined = target->getOwnPropertyDescriptor(globalObject, propertyName, targetPropertyDescriptor); |
255 | RETURN_IF_EXCEPTION(scope, false); |
256 | |
257 | if (trapResult.isUndefined()) { |
258 | if (!isTargetPropertyDescriptorDefined) |
259 | return false; |
260 | if (!targetPropertyDescriptor.configurable()) { |
261 | throwVMTypeError(globalObject, scope, "When the result of 'getOwnPropertyDescriptor' is undefined the target must be configurable"_s ); |
262 | return false; |
263 | } |
264 | bool isExtensible = target->isExtensible(globalObject); |
265 | RETURN_IF_EXCEPTION(scope, false); |
266 | if (!isExtensible) { |
267 | throwVMTypeError(globalObject, scope, "When 'getOwnPropertyDescriptor' returns undefined, the 'target' of a Proxy should be extensible"_s ); |
268 | return false; |
269 | } |
270 | |
271 | return false; |
272 | } |
273 | |
274 | bool isExtensible = target->isExtensible(globalObject); |
275 | RETURN_IF_EXCEPTION(scope, false); |
276 | PropertyDescriptor trapResultAsDescriptor; |
277 | toPropertyDescriptor(globalObject, trapResult, trapResultAsDescriptor); |
278 | RETURN_IF_EXCEPTION(scope, false); |
279 | bool throwException = false; |
280 | bool valid = validateAndApplyPropertyDescriptor(globalObject, nullptr, propertyName, isExtensible, |
281 | trapResultAsDescriptor, isTargetPropertyDescriptorDefined, targetPropertyDescriptor, throwException); |
282 | RETURN_IF_EXCEPTION(scope, false); |
283 | if (!valid) { |
284 | throwVMTypeError(globalObject, scope, "Result from 'getOwnPropertyDescriptor' fails the IsCompatiblePropertyDescriptor test"_s ); |
285 | return false; |
286 | } |
287 | |
288 | if (!trapResultAsDescriptor.configurable()) { |
289 | if (!isTargetPropertyDescriptorDefined || targetPropertyDescriptor.configurable()) { |
290 | throwVMTypeError(globalObject, scope, "Result from 'getOwnPropertyDescriptor' can't be non-configurable when the 'target' doesn't have it as an own property or if it is a configurable own property on 'target'"_s ); |
291 | return false; |
292 | } |
293 | if (trapResultAsDescriptor.writablePresent() && !trapResultAsDescriptor.writable() && targetPropertyDescriptor.writable()) { |
294 | throwVMTypeError(globalObject, scope, "Result from 'getOwnPropertyDescriptor' can't be non-configurable and non-writable when the target's property is writable"_s ); |
295 | return false; |
296 | } |
297 | } |
298 | |
299 | if (trapResultAsDescriptor.isAccessorDescriptor()) { |
300 | GetterSetter* getterSetter = trapResultAsDescriptor.slowGetterSetter(globalObject); |
301 | RETURN_IF_EXCEPTION(scope, false); |
302 | slot.setGetterSlot(this, trapResultAsDescriptor.attributes(), getterSetter); |
303 | } else if (trapResultAsDescriptor.isDataDescriptor() && !trapResultAsDescriptor.value().isEmpty()) |
304 | slot.setValue(this, trapResultAsDescriptor.attributes(), trapResultAsDescriptor.value()); |
305 | else |
306 | slot.setValue(this, trapResultAsDescriptor.attributes(), jsUndefined()); // We use undefined because it's the default value in object properties. |
307 | |
308 | return true; |
309 | } |
310 | |
311 | bool ProxyObject::performHasProperty(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
312 | { |
313 | NO_TAIL_CALLS(); |
314 | |
315 | VM& vm = globalObject->vm(); |
316 | auto scope = DECLARE_THROW_SCOPE(vm); |
317 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
318 | throwStackOverflowError(globalObject, scope); |
319 | return false; |
320 | } |
321 | JSObject* target = this->target(); |
322 | slot.setValue(this, static_cast<unsigned>(PropertyAttribute::None), jsUndefined()); // Nobody should rely on our value, but be safe and protect against any bad actors reading our value. |
323 | |
324 | auto performDefaultHasProperty = [&] { |
325 | return target->getPropertySlot(globalObject, propertyName, slot); |
326 | }; |
327 | |
328 | if (propertyName.isPrivateName()) |
329 | return false; |
330 | |
331 | JSValue handlerValue = this->handler(); |
332 | if (handlerValue.isNull()) { |
333 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
334 | return false; |
335 | } |
336 | |
337 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
338 | CallData callData; |
339 | CallType callType; |
340 | JSValue hasMethod = handler->getMethod(globalObject, callData, callType, vm.propertyNames->has, "'has' property of a Proxy's handler should be callable"_s ); |
341 | RETURN_IF_EXCEPTION(scope, false); |
342 | if (hasMethod.isUndefined()) |
343 | RELEASE_AND_RETURN(scope, performDefaultHasProperty()); |
344 | |
345 | MarkedArgumentBuffer arguments; |
346 | arguments.append(target); |
347 | arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid()))); |
348 | ASSERT(!arguments.hasOverflowed()); |
349 | JSValue trapResult = call(globalObject, hasMethod, callType, callData, handler, arguments); |
350 | RETURN_IF_EXCEPTION(scope, false); |
351 | |
352 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
353 | RETURN_IF_EXCEPTION(scope, false); |
354 | |
355 | if (!trapResultAsBool) { |
356 | PropertyDescriptor descriptor; |
357 | bool isPropertyDescriptorDefined = target->getOwnPropertyDescriptor(globalObject, propertyName, descriptor); |
358 | RETURN_IF_EXCEPTION(scope, false); |
359 | if (isPropertyDescriptorDefined) { |
360 | if (!descriptor.configurable()) { |
361 | throwVMTypeError(globalObject, scope, "Proxy 'has' must return 'true' for non-configurable properties"_s ); |
362 | return false; |
363 | } |
364 | bool isExtensible = target->isExtensible(globalObject); |
365 | RETURN_IF_EXCEPTION(scope, false); |
366 | if (!isExtensible) { |
367 | throwVMTypeError(globalObject, scope, "Proxy 'has' must return 'true' for a non-extensible 'target' object with a configurable property"_s ); |
368 | return false; |
369 | } |
370 | } |
371 | } |
372 | |
373 | return trapResultAsBool; |
374 | } |
375 | |
376 | bool ProxyObject::getOwnPropertySlotCommon(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
377 | { |
378 | slot.disableCaching(); |
379 | slot.setIsTaintedByOpaqueObject(); |
380 | |
381 | if (slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry) |
382 | return false; |
383 | |
384 | VM& vm = globalObject->vm(); |
385 | auto scope = DECLARE_THROW_SCOPE(vm); |
386 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
387 | throwStackOverflowError(globalObject, scope); |
388 | return false; |
389 | } |
390 | switch (slot.internalMethodType()) { |
391 | case PropertySlot::InternalMethodType::Get: |
392 | RELEASE_AND_RETURN(scope, performGet(globalObject, propertyName, slot)); |
393 | case PropertySlot::InternalMethodType::GetOwnProperty: |
394 | RELEASE_AND_RETURN(scope, performInternalMethodGetOwnProperty(globalObject, propertyName, slot)); |
395 | case PropertySlot::InternalMethodType::HasProperty: |
396 | RELEASE_AND_RETURN(scope, performHasProperty(globalObject, propertyName, slot)); |
397 | default: |
398 | return false; |
399 | } |
400 | |
401 | RELEASE_ASSERT_NOT_REACHED(); |
402 | return false; |
403 | } |
404 | |
405 | bool ProxyObject::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
406 | { |
407 | ProxyObject* thisObject = jsCast<ProxyObject*>(object); |
408 | return thisObject->getOwnPropertySlotCommon(globalObject, propertyName, slot); |
409 | } |
410 | |
411 | bool ProxyObject::getOwnPropertySlotByIndex(JSObject* object, JSGlobalObject* globalObject, unsigned propertyName, PropertySlot& slot) |
412 | { |
413 | VM& vm = globalObject->vm(); |
414 | ProxyObject* thisObject = jsCast<ProxyObject*>(object); |
415 | Identifier ident = Identifier::from(vm, propertyName); |
416 | return thisObject->getOwnPropertySlotCommon(globalObject, ident.impl(), slot); |
417 | } |
418 | |
419 | template <typename PerformDefaultPutFunction> |
420 | bool ProxyObject::performPut(JSGlobalObject* globalObject, JSValue putValue, JSValue thisValue, PropertyName propertyName, PerformDefaultPutFunction performDefaultPut, bool shouldThrow) |
421 | { |
422 | NO_TAIL_CALLS(); |
423 | |
424 | VM& vm = globalObject->vm(); |
425 | auto scope = DECLARE_THROW_SCOPE(vm); |
426 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
427 | throwStackOverflowError(globalObject, scope); |
428 | return false; |
429 | } |
430 | |
431 | if (propertyName.isPrivateName()) |
432 | return false; |
433 | |
434 | JSValue handlerValue = this->handler(); |
435 | if (handlerValue.isNull()) { |
436 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
437 | return false; |
438 | } |
439 | |
440 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
441 | CallData callData; |
442 | CallType callType; |
443 | JSValue setMethod = handler->getMethod(globalObject, callData, callType, vm.propertyNames->set, "'set' property of a Proxy's handler should be callable"_s ); |
444 | RETURN_IF_EXCEPTION(scope, false); |
445 | JSObject* target = this->target(); |
446 | if (setMethod.isUndefined()) |
447 | RELEASE_AND_RETURN(scope, performDefaultPut()); |
448 | |
449 | MarkedArgumentBuffer arguments; |
450 | arguments.append(target); |
451 | arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid()))); |
452 | arguments.append(putValue); |
453 | arguments.append(thisValue); |
454 | ASSERT(!arguments.hasOverflowed()); |
455 | JSValue trapResult = call(globalObject, setMethod, callType, callData, handler, arguments); |
456 | RETURN_IF_EXCEPTION(scope, false); |
457 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
458 | RETURN_IF_EXCEPTION(scope, false); |
459 | if (!trapResultAsBool) { |
460 | if (shouldThrow) |
461 | throwVMTypeError(globalObject, scope, makeString("Proxy object's 'set' trap returned falsy value for property '" , String(propertyName.uid()), "'" )); |
462 | return false; |
463 | } |
464 | |
465 | PropertyDescriptor descriptor; |
466 | bool hasProperty = target->getOwnPropertyDescriptor(globalObject, propertyName, descriptor); |
467 | EXCEPTION_ASSERT(!scope.exception() || !hasProperty); |
468 | if (hasProperty) { |
469 | if (descriptor.isDataDescriptor() && !descriptor.configurable() && !descriptor.writable()) { |
470 | bool isSame = sameValue(globalObject, descriptor.value(), putValue); |
471 | RETURN_IF_EXCEPTION(scope, false); |
472 | if (!isSame) { |
473 | throwVMTypeError(globalObject, scope, "Proxy handler's 'set' on a non-configurable and non-writable property on 'target' should either return false or be the same value already on the 'target'"_s ); |
474 | return false; |
475 | } |
476 | } else if (descriptor.isAccessorDescriptor() && !descriptor.configurable() && descriptor.setter().isUndefined()) { |
477 | throwVMTypeError(globalObject, scope, "Proxy handler's 'set' method on a non-configurable accessor property without a setter should return false"_s ); |
478 | return false; |
479 | } |
480 | } |
481 | return true; |
482 | } |
483 | |
484 | bool ProxyObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) |
485 | { |
486 | VM& vm = globalObject->vm(); |
487 | slot.disableCaching(); |
488 | |
489 | ProxyObject* thisObject = jsCast<ProxyObject*>(cell); |
490 | auto performDefaultPut = [&] () { |
491 | JSObject* target = jsCast<JSObject*>(thisObject->target()); |
492 | return target->methodTable(vm)->put(target, globalObject, propertyName, value, slot); |
493 | }; |
494 | return thisObject->performPut(globalObject, value, slot.thisValue(), propertyName, performDefaultPut, slot.isStrictMode()); |
495 | } |
496 | |
497 | bool ProxyObject::putByIndexCommon(JSGlobalObject* globalObject, JSValue thisValue, unsigned propertyName, JSValue putValue, bool shouldThrow) |
498 | { |
499 | VM& vm = globalObject->vm(); |
500 | auto scope = DECLARE_THROW_SCOPE(vm); |
501 | Identifier ident = Identifier::from(vm, propertyName); |
502 | RETURN_IF_EXCEPTION(scope, false); |
503 | auto performDefaultPut = [&] () { |
504 | JSObject* target = this->target(); |
505 | bool isStrictMode = shouldThrow; |
506 | PutPropertySlot slot(thisValue, isStrictMode); // We must preserve the "this" target of the putByIndex. |
507 | return target->methodTable(vm)->put(target, globalObject, ident.impl(), putValue, slot); |
508 | }; |
509 | RELEASE_AND_RETURN(scope, performPut(globalObject, putValue, thisValue, ident.impl(), performDefaultPut, shouldThrow)); |
510 | } |
511 | |
512 | bool ProxyObject::putByIndex(JSCell* cell, JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool shouldThrow) |
513 | { |
514 | ProxyObject* thisObject = jsCast<ProxyObject*>(cell); |
515 | return thisObject->putByIndexCommon(globalObject, thisObject, propertyName, value, shouldThrow); |
516 | } |
517 | |
518 | static EncodedJSValue JSC_HOST_CALL performProxyCall(JSGlobalObject* globalObject, CallFrame* callFrame) |
519 | { |
520 | NO_TAIL_CALLS(); |
521 | |
522 | VM& vm = globalObject->vm(); |
523 | auto scope = DECLARE_THROW_SCOPE(vm); |
524 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
525 | throwStackOverflowError(globalObject, scope); |
526 | return encodedJSValue(); |
527 | } |
528 | ProxyObject* proxy = jsCast<ProxyObject*>(callFrame->jsCallee()); |
529 | JSValue handlerValue = proxy->handler(); |
530 | if (handlerValue.isNull()) |
531 | return throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
532 | |
533 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
534 | CallData callData; |
535 | CallType callType; |
536 | JSValue applyMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "apply" ), "'apply' property of a Proxy's handler should be callable"_s ); |
537 | RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
538 | JSObject* target = proxy->target(); |
539 | if (applyMethod.isUndefined()) { |
540 | CallData callData; |
541 | CallType callType = target->methodTable(vm)->getCallData(target, callData); |
542 | RELEASE_ASSERT(callType != CallType::None); |
543 | RELEASE_AND_RETURN(scope, JSValue::encode(call(globalObject, target, callType, callData, callFrame->thisValue(), ArgList(callFrame)))); |
544 | } |
545 | |
546 | JSArray* argArray = constructArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), ArgList(callFrame)); |
547 | RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
548 | MarkedArgumentBuffer arguments; |
549 | arguments.append(target); |
550 | arguments.append(callFrame->thisValue().toThis(globalObject, ECMAMode::StrictMode)); |
551 | arguments.append(argArray); |
552 | ASSERT(!arguments.hasOverflowed()); |
553 | RELEASE_AND_RETURN(scope, JSValue::encode(call(globalObject, applyMethod, callType, callData, handler, arguments))); |
554 | } |
555 | |
556 | CallType ProxyObject::getCallData(JSCell* cell, CallData& callData) |
557 | { |
558 | ProxyObject* proxy = jsCast<ProxyObject*>(cell); |
559 | if (!proxy->m_isCallable) { |
560 | callData.js.functionExecutable = nullptr; |
561 | callData.js.scope = nullptr; |
562 | return CallType::None; |
563 | } |
564 | |
565 | callData.native.function = performProxyCall; |
566 | return CallType::Host; |
567 | } |
568 | |
569 | static EncodedJSValue JSC_HOST_CALL performProxyConstruct(JSGlobalObject* globalObject, CallFrame* callFrame) |
570 | { |
571 | NO_TAIL_CALLS(); |
572 | |
573 | VM& vm = globalObject->vm(); |
574 | auto scope = DECLARE_THROW_SCOPE(vm); |
575 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
576 | throwStackOverflowError(globalObject, scope); |
577 | return encodedJSValue(); |
578 | } |
579 | ProxyObject* proxy = jsCast<ProxyObject*>(callFrame->jsCallee()); |
580 | JSValue handlerValue = proxy->handler(); |
581 | if (handlerValue.isNull()) |
582 | return throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
583 | |
584 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
585 | CallData callData; |
586 | CallType callType; |
587 | JSValue constructMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "construct" ), "'construct' property of a Proxy's handler should be callable"_s ); |
588 | RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
589 | JSObject* target = proxy->target(); |
590 | if (constructMethod.isUndefined()) { |
591 | ConstructData constructData; |
592 | ConstructType constructType = target->methodTable(vm)->getConstructData(target, constructData); |
593 | RELEASE_ASSERT(constructType != ConstructType::None); |
594 | RELEASE_AND_RETURN(scope, JSValue::encode(construct(globalObject, target, constructType, constructData, ArgList(callFrame), callFrame->newTarget()))); |
595 | } |
596 | |
597 | JSArray* argArray = constructArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), ArgList(callFrame)); |
598 | RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
599 | MarkedArgumentBuffer arguments; |
600 | arguments.append(target); |
601 | arguments.append(argArray); |
602 | arguments.append(callFrame->newTarget()); |
603 | ASSERT(!arguments.hasOverflowed()); |
604 | JSValue result = call(globalObject, constructMethod, callType, callData, handler, arguments); |
605 | RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
606 | if (!result.isObject()) |
607 | return throwVMTypeError(globalObject, scope, "Result from Proxy handler's 'construct' method should be an object"_s ); |
608 | return JSValue::encode(result); |
609 | } |
610 | |
611 | ConstructType ProxyObject::getConstructData(JSCell* cell, ConstructData& constructData) |
612 | { |
613 | ProxyObject* proxy = jsCast<ProxyObject*>(cell); |
614 | if (!proxy->m_isConstructible) { |
615 | constructData.js.functionExecutable = nullptr; |
616 | constructData.js.scope = nullptr; |
617 | return ConstructType::None; |
618 | } |
619 | |
620 | constructData.native.function = performProxyConstruct; |
621 | return ConstructType::Host; |
622 | } |
623 | |
624 | template <typename DefaultDeleteFunction> |
625 | bool ProxyObject::performDelete(JSGlobalObject* globalObject, PropertyName propertyName, DefaultDeleteFunction performDefaultDelete) |
626 | { |
627 | NO_TAIL_CALLS(); |
628 | |
629 | VM& vm = globalObject->vm(); |
630 | auto scope = DECLARE_THROW_SCOPE(vm); |
631 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
632 | throwStackOverflowError(globalObject, scope); |
633 | return false; |
634 | } |
635 | |
636 | if (propertyName.isPrivateName()) |
637 | return false; |
638 | |
639 | JSValue handlerValue = this->handler(); |
640 | if (handlerValue.isNull()) { |
641 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
642 | return false; |
643 | } |
644 | |
645 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
646 | CallData callData; |
647 | CallType callType; |
648 | JSValue deletePropertyMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "deleteProperty" ), "'deleteProperty' property of a Proxy's handler should be callable"_s ); |
649 | RETURN_IF_EXCEPTION(scope, false); |
650 | JSObject* target = this->target(); |
651 | if (deletePropertyMethod.isUndefined()) |
652 | RELEASE_AND_RETURN(scope, performDefaultDelete()); |
653 | |
654 | MarkedArgumentBuffer arguments; |
655 | arguments.append(target); |
656 | arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid()))); |
657 | ASSERT(!arguments.hasOverflowed()); |
658 | JSValue trapResult = call(globalObject, deletePropertyMethod, callType, callData, handler, arguments); |
659 | RETURN_IF_EXCEPTION(scope, false); |
660 | |
661 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
662 | RETURN_IF_EXCEPTION(scope, false); |
663 | |
664 | if (!trapResultAsBool) |
665 | return false; |
666 | |
667 | PropertyDescriptor descriptor; |
668 | bool result = target->getOwnPropertyDescriptor(globalObject, propertyName, descriptor); |
669 | EXCEPTION_ASSERT(!scope.exception() || !result); |
670 | if (result) { |
671 | if (!descriptor.configurable()) { |
672 | throwVMTypeError(globalObject, scope, "Proxy handler's 'deleteProperty' method should return false when the target's property is not configurable"_s ); |
673 | return false; |
674 | } |
675 | bool targetIsExtensible = target->isExtensible(globalObject); |
676 | RETURN_IF_EXCEPTION(scope, false); |
677 | if (!targetIsExtensible) { |
678 | throwVMTypeError(globalObject, scope, "Proxy handler's 'deleteProperty' method should return false when the target has property and is not extensible"_s ); |
679 | return false; |
680 | } |
681 | } |
682 | |
683 | RETURN_IF_EXCEPTION(scope, false); |
684 | |
685 | return true; |
686 | } |
687 | |
688 | bool ProxyObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName) |
689 | { |
690 | ProxyObject* thisObject = jsCast<ProxyObject*>(cell); |
691 | auto performDefaultDelete = [&] () -> bool { |
692 | JSObject* target = thisObject->target(); |
693 | return target->methodTable(globalObject->vm())->deleteProperty(target, globalObject, propertyName); |
694 | }; |
695 | return thisObject->performDelete(globalObject, propertyName, performDefaultDelete); |
696 | } |
697 | |
698 | bool ProxyObject::deletePropertyByIndex(JSCell* cell, JSGlobalObject* globalObject, unsigned propertyName) |
699 | { |
700 | VM& vm = globalObject->vm(); |
701 | ProxyObject* thisObject = jsCast<ProxyObject*>(cell); |
702 | Identifier ident = Identifier::from(vm, propertyName); |
703 | auto performDefaultDelete = [&] () -> bool { |
704 | JSObject* target = thisObject->target(); |
705 | return target->methodTable(vm)->deletePropertyByIndex(target, globalObject, propertyName); |
706 | }; |
707 | return thisObject->performDelete(globalObject, ident.impl(), performDefaultDelete); |
708 | } |
709 | |
710 | bool ProxyObject::performPreventExtensions(JSGlobalObject* globalObject) |
711 | { |
712 | NO_TAIL_CALLS(); |
713 | |
714 | VM& vm = globalObject->vm(); |
715 | auto scope = DECLARE_THROW_SCOPE(vm); |
716 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
717 | throwStackOverflowError(globalObject, scope); |
718 | return false; |
719 | } |
720 | |
721 | JSValue handlerValue = this->handler(); |
722 | if (handlerValue.isNull()) { |
723 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
724 | return false; |
725 | } |
726 | |
727 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
728 | CallData callData; |
729 | CallType callType; |
730 | JSValue preventExtensionsMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "preventExtensions" ), "'preventExtensions' property of a Proxy's handler should be callable"_s ); |
731 | RETURN_IF_EXCEPTION(scope, false); |
732 | JSObject* target = this->target(); |
733 | if (preventExtensionsMethod.isUndefined()) |
734 | RELEASE_AND_RETURN(scope, target->methodTable(vm)->preventExtensions(target, globalObject)); |
735 | |
736 | MarkedArgumentBuffer arguments; |
737 | arguments.append(target); |
738 | ASSERT(!arguments.hasOverflowed()); |
739 | JSValue trapResult = call(globalObject, preventExtensionsMethod, callType, callData, handler, arguments); |
740 | RETURN_IF_EXCEPTION(scope, false); |
741 | |
742 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
743 | RETURN_IF_EXCEPTION(scope, false); |
744 | |
745 | if (trapResultAsBool) { |
746 | bool targetIsExtensible = target->isExtensible(globalObject); |
747 | RETURN_IF_EXCEPTION(scope, false); |
748 | if (targetIsExtensible) { |
749 | throwVMTypeError(globalObject, scope, "Proxy's 'preventExtensions' trap returned true even though its target is extensible. It should have returned false"_s ); |
750 | return false; |
751 | } |
752 | } |
753 | |
754 | return trapResultAsBool; |
755 | } |
756 | |
757 | bool ProxyObject::preventExtensions(JSObject* object, JSGlobalObject* globalObject) |
758 | { |
759 | return jsCast<ProxyObject*>(object)->performPreventExtensions(globalObject); |
760 | } |
761 | |
762 | bool ProxyObject::performIsExtensible(JSGlobalObject* globalObject) |
763 | { |
764 | NO_TAIL_CALLS(); |
765 | |
766 | VM& vm = globalObject->vm(); |
767 | auto scope = DECLARE_THROW_SCOPE(vm); |
768 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
769 | throwStackOverflowError(globalObject, scope); |
770 | return false; |
771 | } |
772 | |
773 | JSValue handlerValue = this->handler(); |
774 | if (handlerValue.isNull()) { |
775 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
776 | return false; |
777 | } |
778 | |
779 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
780 | CallData callData; |
781 | CallType callType; |
782 | JSValue isExtensibleMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "isExtensible" ), "'isExtensible' property of a Proxy's handler should be callable"_s ); |
783 | RETURN_IF_EXCEPTION(scope, false); |
784 | |
785 | JSObject* target = this->target(); |
786 | if (isExtensibleMethod.isUndefined()) |
787 | RELEASE_AND_RETURN(scope, target->isExtensible(globalObject)); |
788 | |
789 | MarkedArgumentBuffer arguments; |
790 | arguments.append(target); |
791 | ASSERT(!arguments.hasOverflowed()); |
792 | JSValue trapResult = call(globalObject, isExtensibleMethod, callType, callData, handler, arguments); |
793 | RETURN_IF_EXCEPTION(scope, false); |
794 | |
795 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
796 | RETURN_IF_EXCEPTION(scope, false); |
797 | |
798 | bool isTargetExtensible = target->isExtensible(globalObject); |
799 | RETURN_IF_EXCEPTION(scope, false); |
800 | |
801 | if (trapResultAsBool != isTargetExtensible) { |
802 | if (isTargetExtensible) { |
803 | ASSERT(!trapResultAsBool); |
804 | throwVMTypeError(globalObject, scope, "Proxy object's 'isExtensible' trap returned false when the target is extensible. It should have returned true"_s ); |
805 | } else { |
806 | ASSERT(!isTargetExtensible); |
807 | ASSERT(trapResultAsBool); |
808 | throwVMTypeError(globalObject, scope, "Proxy object's 'isExtensible' trap returned true when the target is non-extensible. It should have returned false"_s ); |
809 | } |
810 | } |
811 | |
812 | return trapResultAsBool; |
813 | } |
814 | |
815 | bool ProxyObject::isExtensible(JSObject* object, JSGlobalObject* globalObject) |
816 | { |
817 | return jsCast<ProxyObject*>(object)->performIsExtensible(globalObject); |
818 | } |
819 | |
820 | bool ProxyObject::performDefineOwnProperty(JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) |
821 | { |
822 | NO_TAIL_CALLS(); |
823 | |
824 | VM& vm = globalObject->vm(); |
825 | auto scope = DECLARE_THROW_SCOPE(vm); |
826 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
827 | throwStackOverflowError(globalObject, scope); |
828 | return false; |
829 | } |
830 | |
831 | JSObject* target = this->target(); |
832 | auto performDefaultDefineOwnProperty = [&] { |
833 | RELEASE_AND_RETURN(scope, target->methodTable(vm)->defineOwnProperty(target, globalObject, propertyName, descriptor, shouldThrow)); |
834 | }; |
835 | |
836 | if (propertyName.isPrivateName()) |
837 | return false; |
838 | |
839 | JSValue handlerValue = this->handler(); |
840 | if (handlerValue.isNull()) { |
841 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
842 | return false; |
843 | } |
844 | |
845 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
846 | CallData callData; |
847 | CallType callType; |
848 | JSValue definePropertyMethod = handler->getMethod(globalObject, callData, callType, vm.propertyNames->defineProperty, "'defineProperty' property of a Proxy's handler should be callable"_s ); |
849 | RETURN_IF_EXCEPTION(scope, false); |
850 | |
851 | if (definePropertyMethod.isUndefined()) |
852 | return performDefaultDefineOwnProperty(); |
853 | |
854 | JSObject* descriptorObject = constructObjectFromPropertyDescriptor(globalObject, descriptor); |
855 | RETURN_IF_EXCEPTION(scope, false); |
856 | |
857 | MarkedArgumentBuffer arguments; |
858 | arguments.append(target); |
859 | arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid()))); |
860 | arguments.append(descriptorObject); |
861 | ASSERT(!arguments.hasOverflowed()); |
862 | JSValue trapResult = call(globalObject, definePropertyMethod, callType, callData, handler, arguments); |
863 | RETURN_IF_EXCEPTION(scope, false); |
864 | |
865 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
866 | RETURN_IF_EXCEPTION(scope, false); |
867 | |
868 | if (!trapResultAsBool) |
869 | return false; |
870 | |
871 | PropertyDescriptor targetDescriptor; |
872 | bool isTargetDescriptorDefined = target->getOwnPropertyDescriptor(globalObject, propertyName, targetDescriptor); |
873 | RETURN_IF_EXCEPTION(scope, false); |
874 | |
875 | bool targetIsExtensible = target->isExtensible(globalObject); |
876 | RETURN_IF_EXCEPTION(scope, false); |
877 | bool settingConfigurableToFalse = descriptor.configurablePresent() && !descriptor.configurable(); |
878 | |
879 | if (!isTargetDescriptorDefined) { |
880 | if (!targetIsExtensible) { |
881 | throwVMTypeError(globalObject, scope, "Proxy's 'defineProperty' trap returned true even though getOwnPropertyDescriptor of the Proxy's target returned undefined and the target is non-extensible"_s ); |
882 | return false; |
883 | } |
884 | if (settingConfigurableToFalse) { |
885 | throwVMTypeError(globalObject, scope, "Proxy's 'defineProperty' trap returned true for a non-configurable field even though getOwnPropertyDescriptor of the Proxy's target returned undefined"_s ); |
886 | return false; |
887 | } |
888 | |
889 | return true; |
890 | } |
891 | |
892 | ASSERT(isTargetDescriptorDefined); |
893 | bool isCurrentDefined = isTargetDescriptorDefined; |
894 | const PropertyDescriptor& current = targetDescriptor; |
895 | bool throwException = false; |
896 | bool isCompatibleDescriptor = validateAndApplyPropertyDescriptor(globalObject, nullptr, propertyName, targetIsExtensible, descriptor, isCurrentDefined, current, throwException); |
897 | RETURN_IF_EXCEPTION(scope, false); |
898 | if (!isCompatibleDescriptor) { |
899 | throwVMTypeError(globalObject, scope, "Proxy's 'defineProperty' trap did not define a property on its target that is compatible with the trap's input descriptor"_s ); |
900 | return false; |
901 | } |
902 | if (settingConfigurableToFalse && targetDescriptor.configurable()) { |
903 | throwVMTypeError(globalObject, scope, "Proxy's 'defineProperty' trap did not define a non-configurable property on its target even though the input descriptor to the trap said it must do so"_s ); |
904 | return false; |
905 | } |
906 | if (targetDescriptor.isDataDescriptor() && !targetDescriptor.configurable() && targetDescriptor.writable()) { |
907 | if (descriptor.writablePresent() && !descriptor.writable()) { |
908 | throwTypeError(globalObject, scope, "Proxy's 'defineProperty' trap returned true for a non-writable input descriptor when the target's property is non-configurable and writable"_s ); |
909 | return false; |
910 | } |
911 | } |
912 | |
913 | return true; |
914 | } |
915 | |
916 | bool ProxyObject::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) |
917 | { |
918 | ProxyObject* thisObject = jsCast<ProxyObject*>(object); |
919 | return thisObject->performDefineOwnProperty(globalObject, propertyName, descriptor, shouldThrow); |
920 | } |
921 | |
922 | void ProxyObject::performGetOwnPropertyNames(JSGlobalObject* globalObject, PropertyNameArray& propertyNames, EnumerationMode enumerationMode) |
923 | { |
924 | NO_TAIL_CALLS(); |
925 | |
926 | VM& vm = globalObject->vm(); |
927 | auto scope = DECLARE_THROW_SCOPE(vm); |
928 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
929 | throwStackOverflowError(globalObject, scope); |
930 | return; |
931 | } |
932 | JSValue handlerValue = this->handler(); |
933 | if (handlerValue.isNull()) { |
934 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
935 | return; |
936 | } |
937 | |
938 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
939 | CallData callData; |
940 | CallType callType; |
941 | JSValue ownKeysMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "ownKeys" ), "'ownKeys' property of a Proxy's handler should be callable"_s ); |
942 | RETURN_IF_EXCEPTION(scope, void()); |
943 | JSObject* target = this->target(); |
944 | if (ownKeysMethod.isUndefined()) { |
945 | scope.release(); |
946 | target->methodTable(vm)->getOwnPropertyNames(target, globalObject, propertyNames, enumerationMode); |
947 | return; |
948 | } |
949 | |
950 | MarkedArgumentBuffer arguments; |
951 | arguments.append(target); |
952 | ASSERT(!arguments.hasOverflowed()); |
953 | JSValue arrayLikeObject = call(globalObject, ownKeysMethod, callType, callData, handler, arguments); |
954 | RETURN_IF_EXCEPTION(scope, void()); |
955 | |
956 | PropertyNameArray trapResult(vm, propertyNames.propertyNameMode(), propertyNames.privateSymbolMode()); |
957 | HashSet<UniquedStringImpl*> uncheckedResultKeys; |
958 | { |
959 | HashSet<RefPtr<UniquedStringImpl>> seenKeys; |
960 | |
961 | RuntimeTypeMask resultFilter = 0; |
962 | switch (propertyNames.propertyNameMode()) { |
963 | case PropertyNameMode::Symbols: |
964 | resultFilter = TypeSymbol; |
965 | break; |
966 | case PropertyNameMode::Strings: |
967 | resultFilter = TypeString; |
968 | break; |
969 | case PropertyNameMode::StringsAndSymbols: |
970 | resultFilter = TypeSymbol | TypeString; |
971 | break; |
972 | } |
973 | ASSERT(resultFilter); |
974 | |
975 | auto addPropName = [&] (JSValue value, RuntimeType type) -> bool { |
976 | static constexpr bool doExitEarly = true; |
977 | static constexpr bool dontExitEarly = false; |
978 | |
979 | Identifier ident = value.toPropertyKey(globalObject); |
980 | RETURN_IF_EXCEPTION(scope, doExitEarly); |
981 | |
982 | // If trapResult contains any duplicate entries, throw a TypeError exception. |
983 | // |
984 | // Per spec[1], filtering by type should occur _after_ [[OwnPropertyKeys]], so duplicates |
985 | // are tracked in a separate hashtable from uncheckedResultKeys (which only contain the |
986 | // keys filtered by type). |
987 | // |
988 | // [1] Per https://tc39.github.io/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeysmust not contain any duplicate names"_s); |
989 | if (!seenKeys.add(ident.impl()).isNewEntry) { |
990 | throwTypeError(globalObject, scope, "Proxy handler's 'ownKeys' trap result must not contain any duplicate names"_s ); |
991 | return doExitEarly; |
992 | } |
993 | |
994 | if (!(type & resultFilter)) |
995 | return dontExitEarly; |
996 | |
997 | uncheckedResultKeys.add(ident.impl()); |
998 | trapResult.add(ident.impl()); |
999 | return dontExitEarly; |
1000 | }; |
1001 | |
1002 | RuntimeTypeMask dontThrowAnExceptionTypeFilter = TypeString | TypeSymbol; |
1003 | createListFromArrayLike(globalObject, arrayLikeObject, dontThrowAnExceptionTypeFilter, "Proxy handler's 'ownKeys' method must return an object"_s , "Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"_s , addPropName); |
1004 | RETURN_IF_EXCEPTION(scope, void()); |
1005 | } |
1006 | |
1007 | bool targetIsExensible = target->isExtensible(globalObject); |
1008 | RETURN_IF_EXCEPTION(scope, void()); |
1009 | |
1010 | PropertyNameArray targetKeys(vm, propertyNames.propertyNameMode(), propertyNames.privateSymbolMode()); |
1011 | target->methodTable(vm)->getOwnPropertyNames(target, globalObject, targetKeys, enumerationMode); |
1012 | RETURN_IF_EXCEPTION(scope, void()); |
1013 | Vector<UniquedStringImpl*> targetConfigurableKeys; |
1014 | Vector<UniquedStringImpl*> targetNonConfigurableKeys; |
1015 | for (const Identifier& ident : targetKeys) { |
1016 | PropertyDescriptor descriptor; |
1017 | bool isPropertyDefined = target->getOwnPropertyDescriptor(globalObject, ident.impl(), descriptor); |
1018 | RETURN_IF_EXCEPTION(scope, void()); |
1019 | if (isPropertyDefined && !descriptor.configurable()) |
1020 | targetNonConfigurableKeys.append(ident.impl()); |
1021 | else |
1022 | targetConfigurableKeys.append(ident.impl()); |
1023 | } |
1024 | |
1025 | enum ContainedIn { IsContainedIn, IsNotContainedIn }; |
1026 | auto removeIfContainedInUncheckedResultKeys = [&] (UniquedStringImpl* impl) -> ContainedIn { |
1027 | auto iter = uncheckedResultKeys.find(impl); |
1028 | if (iter == uncheckedResultKeys.end()) |
1029 | return IsNotContainedIn; |
1030 | |
1031 | uncheckedResultKeys.remove(iter); |
1032 | return IsContainedIn; |
1033 | }; |
1034 | |
1035 | for (UniquedStringImpl* impl : targetNonConfigurableKeys) { |
1036 | if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) { |
1037 | throwVMTypeError(globalObject, scope, makeString("Proxy object's 'target' has the non-configurable property '" , String(impl), "' that was not in the result from the 'ownKeys' trap" )); |
1038 | return; |
1039 | } |
1040 | } |
1041 | |
1042 | if (!targetIsExensible) { |
1043 | for (UniquedStringImpl* impl : targetConfigurableKeys) { |
1044 | if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) { |
1045 | throwVMTypeError(globalObject, scope, makeString("Proxy object's non-extensible 'target' has configurable property '" , String(impl), "' that was not in the result from the 'ownKeys' trap" )); |
1046 | return; |
1047 | } |
1048 | } |
1049 | |
1050 | if (uncheckedResultKeys.size()) { |
1051 | throwVMTypeError(globalObject, scope, "Proxy handler's 'ownKeys' method returned a key that was not present in its non-extensible target"_s ); |
1052 | return; |
1053 | } |
1054 | } |
1055 | |
1056 | if (!enumerationMode.includeDontEnumProperties()) { |
1057 | // Filtering DontEnum properties is observable in proxies and must occur following the invariant checks above. |
1058 | for (const auto& propertyName : trapResult) { |
1059 | PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty); |
1060 | auto result = getOwnPropertySlotCommon(globalObject, propertyName, slot); |
1061 | RETURN_IF_EXCEPTION(scope, void()); |
1062 | if (!result) |
1063 | continue; |
1064 | if (slot.attributes() & PropertyAttribute::DontEnum) |
1065 | continue; |
1066 | propertyNames.add(propertyName.impl()); |
1067 | } |
1068 | } else { |
1069 | for (const auto& propertyName : trapResult) |
1070 | propertyNames.add(propertyName.impl()); |
1071 | } |
1072 | } |
1073 | |
1074 | void ProxyObject::getOwnPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode) |
1075 | { |
1076 | ProxyObject* thisObject = jsCast<ProxyObject*>(object); |
1077 | thisObject->performGetOwnPropertyNames(globalObject, propertyNameArray, enumerationMode); |
1078 | } |
1079 | |
1080 | void ProxyObject::getPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode) |
1081 | { |
1082 | NO_TAIL_CALLS(); |
1083 | JSObject::getPropertyNames(object, globalObject, propertyNameArray, enumerationMode); |
1084 | } |
1085 | |
1086 | void ProxyObject::getOwnNonIndexPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, EnumerationMode) |
1087 | { |
1088 | RELEASE_ASSERT_NOT_REACHED(); |
1089 | } |
1090 | |
1091 | void ProxyObject::getStructurePropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, EnumerationMode) |
1092 | { |
1093 | // We should always go down the getOwnPropertyNames path. |
1094 | RELEASE_ASSERT_NOT_REACHED(); |
1095 | } |
1096 | |
1097 | void ProxyObject::getGenericPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, EnumerationMode) |
1098 | { |
1099 | RELEASE_ASSERT_NOT_REACHED(); |
1100 | } |
1101 | |
1102 | bool ProxyObject::performSetPrototype(JSGlobalObject* globalObject, JSValue prototype, bool shouldThrowIfCantSet) |
1103 | { |
1104 | NO_TAIL_CALLS(); |
1105 | |
1106 | ASSERT(prototype.isObject() || prototype.isNull()); |
1107 | |
1108 | VM& vm = globalObject->vm(); |
1109 | auto scope = DECLARE_THROW_SCOPE(vm); |
1110 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
1111 | throwStackOverflowError(globalObject, scope); |
1112 | return false; |
1113 | } |
1114 | |
1115 | JSValue handlerValue = this->handler(); |
1116 | if (handlerValue.isNull()) { |
1117 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
1118 | return false; |
1119 | } |
1120 | |
1121 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
1122 | CallData callData; |
1123 | CallType callType; |
1124 | JSValue setPrototypeOfMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "setPrototypeOf" ), "'setPrototypeOf' property of a Proxy's handler should be callable"_s ); |
1125 | RETURN_IF_EXCEPTION(scope, false); |
1126 | |
1127 | JSObject* target = this->target(); |
1128 | if (setPrototypeOfMethod.isUndefined()) |
1129 | RELEASE_AND_RETURN(scope, target->setPrototype(vm, globalObject, prototype, shouldThrowIfCantSet)); |
1130 | |
1131 | MarkedArgumentBuffer arguments; |
1132 | arguments.append(target); |
1133 | arguments.append(prototype); |
1134 | ASSERT(!arguments.hasOverflowed()); |
1135 | JSValue trapResult = call(globalObject, setPrototypeOfMethod, callType, callData, handler, arguments); |
1136 | RETURN_IF_EXCEPTION(scope, false); |
1137 | |
1138 | bool trapResultAsBool = trapResult.toBoolean(globalObject); |
1139 | RETURN_IF_EXCEPTION(scope, false); |
1140 | |
1141 | if (!trapResultAsBool) { |
1142 | if (shouldThrowIfCantSet) |
1143 | throwVMTypeError(globalObject, scope, "Proxy 'setPrototypeOf' returned false indicating it could not set the prototype value. The operation was expected to succeed"_s ); |
1144 | return false; |
1145 | } |
1146 | |
1147 | bool targetIsExtensible = target->isExtensible(globalObject); |
1148 | RETURN_IF_EXCEPTION(scope, false); |
1149 | if (targetIsExtensible) |
1150 | return true; |
1151 | |
1152 | JSValue targetPrototype = target->getPrototype(vm, globalObject); |
1153 | RETURN_IF_EXCEPTION(scope, false); |
1154 | bool isSame = sameValue(globalObject, prototype, targetPrototype); |
1155 | RETURN_IF_EXCEPTION(scope, false); |
1156 | if (!isSame) { |
1157 | throwVMTypeError(globalObject, scope, "Proxy 'setPrototypeOf' trap returned true when its target is non-extensible and the new prototype value is not the same as the current prototype value. It should have returned false"_s ); |
1158 | return false; |
1159 | } |
1160 | |
1161 | return true; |
1162 | } |
1163 | |
1164 | bool ProxyObject::setPrototype(JSObject* object, JSGlobalObject* globalObject, JSValue prototype, bool shouldThrowIfCantSet) |
1165 | { |
1166 | return jsCast<ProxyObject*>(object)->performSetPrototype(globalObject, prototype, shouldThrowIfCantSet); |
1167 | } |
1168 | |
1169 | JSValue ProxyObject::performGetPrototype(JSGlobalObject* globalObject) |
1170 | { |
1171 | NO_TAIL_CALLS(); |
1172 | |
1173 | VM& vm = globalObject->vm(); |
1174 | auto scope = DECLARE_THROW_SCOPE(vm); |
1175 | if (UNLIKELY(!vm.isSafeToRecurseSoft())) { |
1176 | throwStackOverflowError(globalObject, scope); |
1177 | return { }; |
1178 | } |
1179 | |
1180 | JSValue handlerValue = this->handler(); |
1181 | if (handlerValue.isNull()) { |
1182 | throwVMTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); |
1183 | return { }; |
1184 | } |
1185 | |
1186 | JSObject* handler = jsCast<JSObject*>(handlerValue); |
1187 | CallData callData; |
1188 | CallType callType; |
1189 | JSValue getPrototypeOfMethod = handler->getMethod(globalObject, callData, callType, makeIdentifier(vm, "getPrototypeOf" ), "'getPrototypeOf' property of a Proxy's handler should be callable"_s ); |
1190 | RETURN_IF_EXCEPTION(scope, { }); |
1191 | |
1192 | JSObject* target = this->target(); |
1193 | if (getPrototypeOfMethod.isUndefined()) |
1194 | RELEASE_AND_RETURN(scope, target->getPrototype(vm, globalObject)); |
1195 | |
1196 | MarkedArgumentBuffer arguments; |
1197 | arguments.append(target); |
1198 | ASSERT(!arguments.hasOverflowed()); |
1199 | JSValue trapResult = call(globalObject, getPrototypeOfMethod, callType, callData, handler, arguments); |
1200 | RETURN_IF_EXCEPTION(scope, { }); |
1201 | |
1202 | if (!trapResult.isObject() && !trapResult.isNull()) { |
1203 | throwVMTypeError(globalObject, scope, "Proxy handler's 'getPrototypeOf' trap should either return an object or null"_s ); |
1204 | return { }; |
1205 | } |
1206 | |
1207 | bool targetIsExtensible = target->isExtensible(globalObject); |
1208 | RETURN_IF_EXCEPTION(scope, { }); |
1209 | if (targetIsExtensible) |
1210 | return trapResult; |
1211 | |
1212 | JSValue targetPrototype = target->getPrototype(vm, globalObject); |
1213 | RETURN_IF_EXCEPTION(scope, { }); |
1214 | bool isSame = sameValue(globalObject, targetPrototype, trapResult); |
1215 | RETURN_IF_EXCEPTION(scope, { }); |
1216 | if (!isSame) { |
1217 | throwVMTypeError(globalObject, scope, "Proxy's 'getPrototypeOf' trap for a non-extensible target should return the same value as the target's prototype"_s ); |
1218 | return { }; |
1219 | } |
1220 | |
1221 | return trapResult; |
1222 | } |
1223 | |
1224 | JSValue ProxyObject::getPrototype(JSObject* object, JSGlobalObject* globalObject) |
1225 | { |
1226 | return jsCast<ProxyObject*>(object)->performGetPrototype(globalObject); |
1227 | } |
1228 | |
1229 | void ProxyObject::revoke(VM& vm) |
1230 | { |
1231 | // This should only ever be called once and we should strictly transition from Object to null. |
1232 | RELEASE_ASSERT(!m_handler.get().isNull() && m_handler.get().isObject()); |
1233 | m_handler.set(vm, this, jsNull()); |
1234 | } |
1235 | |
1236 | bool ProxyObject::isRevoked() const |
1237 | { |
1238 | return handler().isNull(); |
1239 | } |
1240 | |
1241 | void ProxyObject::visitChildren(JSCell* cell, SlotVisitor& visitor) |
1242 | { |
1243 | ProxyObject* thisObject = jsCast<ProxyObject*>(cell); |
1244 | ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
1245 | Base::visitChildren(thisObject, visitor); |
1246 | |
1247 | visitor.append(thisObject->m_target); |
1248 | visitor.append(thisObject->m_handler); |
1249 | } |
1250 | |
1251 | } // namespace JSC |
1252 | |