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