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