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)
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 return false;
453
454 PropertyDescriptor descriptor;
455 bool hasProperty = target->getOwnPropertyDescriptor(exec, propertyName, descriptor);
456 EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
457 if (hasProperty) {
458 if (descriptor.isDataDescriptor() && !descriptor.configurable() && !descriptor.writable()) {
459 if (!sameValue(exec, descriptor.value(), putValue)) {
460 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);
461 return false;
462 }
463 } else if (descriptor.isAccessorDescriptor() && !descriptor.configurable() && descriptor.setter().isUndefined()) {
464 throwVMTypeError(exec, scope, "Proxy handler's 'set' method on a non-configurable accessor property without a setter should return false"_s);
465 return false;
466 }
467 }
468 return true;
469}
470
471bool ProxyObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
472{
473 VM& vm = exec->vm();
474 slot.disableCaching();
475
476 ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
477 auto performDefaultPut = [&] () {
478 JSObject* target = jsCast<JSObject*>(thisObject->target());
479 return target->methodTable(vm)->put(target, exec, propertyName, value, slot);
480 };
481 return thisObject->performPut(exec, value, slot.thisValue(), propertyName, performDefaultPut);
482}
483
484bool ProxyObject::putByIndexCommon(ExecState* exec, JSValue thisValue, unsigned propertyName, JSValue putValue, bool shouldThrow)
485{
486 VM& vm = exec->vm();
487 auto scope = DECLARE_THROW_SCOPE(vm);
488 Identifier ident = Identifier::from(exec, propertyName);
489 RETURN_IF_EXCEPTION(scope, false);
490 auto performDefaultPut = [&] () {
491 JSObject* target = this->target();
492 bool isStrictMode = shouldThrow;
493 PutPropertySlot slot(thisValue, isStrictMode); // We must preserve the "this" target of the putByIndex.
494 return target->methodTable(vm)->put(target, exec, ident.impl(), putValue, slot);
495 };
496 RELEASE_AND_RETURN(scope, performPut(exec, putValue, thisValue, ident.impl(), performDefaultPut));
497}
498
499bool ProxyObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
500{
501 ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
502 return thisObject->putByIndexCommon(exec, thisObject, propertyName, value, shouldThrow);
503}
504
505static EncodedJSValue JSC_HOST_CALL performProxyCall(ExecState* exec)
506{
507 NO_TAIL_CALLS();
508
509 VM& vm = exec->vm();
510 auto scope = DECLARE_THROW_SCOPE(vm);
511 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
512 throwStackOverflowError(exec, scope);
513 return encodedJSValue();
514 }
515 ProxyObject* proxy = jsCast<ProxyObject*>(exec->jsCallee());
516 JSValue handlerValue = proxy->handler();
517 if (handlerValue.isNull())
518 return throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
519
520 JSObject* handler = jsCast<JSObject*>(handlerValue);
521 CallData callData;
522 CallType callType;
523 JSValue applyMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "apply"), "'apply' property of a Proxy's handler should be callable"_s);
524 RETURN_IF_EXCEPTION(scope, encodedJSValue());
525 JSObject* target = proxy->target();
526 if (applyMethod.isUndefined()) {
527 CallData callData;
528 CallType callType = target->methodTable(vm)->getCallData(target, callData);
529 RELEASE_ASSERT(callType != CallType::None);
530 RELEASE_AND_RETURN(scope, JSValue::encode(call(exec, target, callType, callData, exec->thisValue(), ArgList(exec))));
531 }
532
533 JSArray* argArray = constructArray(exec, static_cast<ArrayAllocationProfile*>(nullptr), ArgList(exec));
534 RETURN_IF_EXCEPTION(scope, encodedJSValue());
535 MarkedArgumentBuffer arguments;
536 arguments.append(target);
537 arguments.append(exec->thisValue().toThis(exec, ECMAMode::StrictMode));
538 arguments.append(argArray);
539 ASSERT(!arguments.hasOverflowed());
540 RELEASE_AND_RETURN(scope, JSValue::encode(call(exec, applyMethod, callType, callData, handler, arguments)));
541}
542
543CallType ProxyObject::getCallData(JSCell* cell, CallData& callData)
544{
545 ProxyObject* proxy = jsCast<ProxyObject*>(cell);
546 if (!proxy->m_isCallable) {
547 callData.js.functionExecutable = nullptr;
548 callData.js.scope = nullptr;
549 return CallType::None;
550 }
551
552 callData.native.function = performProxyCall;
553 return CallType::Host;
554}
555
556static EncodedJSValue JSC_HOST_CALL performProxyConstruct(ExecState* exec)
557{
558 NO_TAIL_CALLS();
559
560 VM& vm = exec->vm();
561 auto scope = DECLARE_THROW_SCOPE(vm);
562 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
563 throwStackOverflowError(exec, scope);
564 return encodedJSValue();
565 }
566 ProxyObject* proxy = jsCast<ProxyObject*>(exec->jsCallee());
567 JSValue handlerValue = proxy->handler();
568 if (handlerValue.isNull())
569 return throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
570
571 JSObject* handler = jsCast<JSObject*>(handlerValue);
572 CallData callData;
573 CallType callType;
574 JSValue constructMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "construct"), "'construct' property of a Proxy's handler should be constructible"_s);
575 RETURN_IF_EXCEPTION(scope, encodedJSValue());
576 JSObject* target = proxy->target();
577 if (constructMethod.isUndefined()) {
578 ConstructData constructData;
579 ConstructType constructType = target->methodTable(vm)->getConstructData(target, constructData);
580 RELEASE_ASSERT(constructType != ConstructType::None);
581 RELEASE_AND_RETURN(scope, JSValue::encode(construct(exec, target, constructType, constructData, ArgList(exec), exec->newTarget())));
582 }
583
584 JSArray* argArray = constructArray(exec, static_cast<ArrayAllocationProfile*>(nullptr), ArgList(exec));
585 RETURN_IF_EXCEPTION(scope, encodedJSValue());
586 MarkedArgumentBuffer arguments;
587 arguments.append(target);
588 arguments.append(argArray);
589 arguments.append(exec->newTarget());
590 ASSERT(!arguments.hasOverflowed());
591 JSValue result = call(exec, constructMethod, callType, callData, handler, arguments);
592 RETURN_IF_EXCEPTION(scope, encodedJSValue());
593 if (!result.isObject())
594 return throwVMTypeError(exec, scope, "Result from Proxy handler's 'construct' method should be an object"_s);
595 return JSValue::encode(result);
596}
597
598ConstructType ProxyObject::getConstructData(JSCell* cell, ConstructData& constructData)
599{
600 ProxyObject* proxy = jsCast<ProxyObject*>(cell);
601 if (!proxy->m_isConstructible) {
602 constructData.js.functionExecutable = nullptr;
603 constructData.js.scope = nullptr;
604 return ConstructType::None;
605 }
606
607 constructData.native.function = performProxyConstruct;
608 return ConstructType::Host;
609}
610
611template <typename DefaultDeleteFunction>
612bool ProxyObject::performDelete(ExecState* exec, PropertyName propertyName, DefaultDeleteFunction performDefaultDelete)
613{
614 NO_TAIL_CALLS();
615
616 VM& vm = exec->vm();
617 auto scope = DECLARE_THROW_SCOPE(vm);
618 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
619 throwStackOverflowError(exec, scope);
620 return false;
621 }
622
623 if (propertyName.isPrivateName())
624 RELEASE_AND_RETURN(scope, performDefaultDelete());
625
626 JSValue handlerValue = this->handler();
627 if (handlerValue.isNull()) {
628 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
629 return false;
630 }
631
632 JSObject* handler = jsCast<JSObject*>(handlerValue);
633 CallData callData;
634 CallType callType;
635 JSValue deletePropertyMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "deleteProperty"), "'deleteProperty' property of a Proxy's handler should be callable"_s);
636 RETURN_IF_EXCEPTION(scope, false);
637 JSObject* target = this->target();
638 if (deletePropertyMethod.isUndefined())
639 RELEASE_AND_RETURN(scope, performDefaultDelete());
640
641 MarkedArgumentBuffer arguments;
642 arguments.append(target);
643 arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
644 ASSERT(!arguments.hasOverflowed());
645 JSValue trapResult = call(exec, deletePropertyMethod, callType, callData, handler, arguments);
646 RETURN_IF_EXCEPTION(scope, false);
647
648 bool trapResultAsBool = trapResult.toBoolean(exec);
649 RETURN_IF_EXCEPTION(scope, false);
650
651 if (!trapResultAsBool)
652 return false;
653
654 PropertyDescriptor descriptor;
655 bool result = target->getOwnPropertyDescriptor(exec, propertyName, descriptor);
656 EXCEPTION_ASSERT(!scope.exception() || !result);
657 if (result) {
658 if (!descriptor.configurable()) {
659 throwVMTypeError(exec, scope, "Proxy handler's 'deleteProperty' method should return false when the target's property is not configurable"_s);
660 return false;
661 }
662 }
663
664 RETURN_IF_EXCEPTION(scope, false);
665
666 return true;
667}
668
669bool ProxyObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
670{
671 ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
672 auto performDefaultDelete = [&] () -> bool {
673 JSObject* target = thisObject->target();
674 return target->methodTable(exec->vm())->deleteProperty(target, exec, propertyName);
675 };
676 return thisObject->performDelete(exec, propertyName, performDefaultDelete);
677}
678
679bool ProxyObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
680{
681 ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
682 Identifier ident = Identifier::from(exec, propertyName);
683 auto performDefaultDelete = [&] () -> bool {
684 JSObject* target = thisObject->target();
685 return target->methodTable(exec->vm())->deletePropertyByIndex(target, exec, propertyName);
686 };
687 return thisObject->performDelete(exec, ident.impl(), performDefaultDelete);
688}
689
690bool ProxyObject::performPreventExtensions(ExecState* exec)
691{
692 NO_TAIL_CALLS();
693
694 VM& vm = exec->vm();
695 auto scope = DECLARE_THROW_SCOPE(vm);
696 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
697 throwStackOverflowError(exec, scope);
698 return false;
699 }
700
701 JSValue handlerValue = this->handler();
702 if (handlerValue.isNull()) {
703 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
704 return false;
705 }
706
707 JSObject* handler = jsCast<JSObject*>(handlerValue);
708 CallData callData;
709 CallType callType;
710 JSValue preventExtensionsMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "preventExtensions"), "'preventExtensions' property of a Proxy's handler should be callable"_s);
711 RETURN_IF_EXCEPTION(scope, false);
712 JSObject* target = this->target();
713 if (preventExtensionsMethod.isUndefined())
714 RELEASE_AND_RETURN(scope, target->methodTable(vm)->preventExtensions(target, exec));
715
716 MarkedArgumentBuffer arguments;
717 arguments.append(target);
718 ASSERT(!arguments.hasOverflowed());
719 JSValue trapResult = call(exec, preventExtensionsMethod, callType, callData, handler, arguments);
720 RETURN_IF_EXCEPTION(scope, false);
721
722 bool trapResultAsBool = trapResult.toBoolean(exec);
723 RETURN_IF_EXCEPTION(scope, false);
724
725 if (trapResultAsBool) {
726 bool targetIsExtensible = target->isExtensible(exec);
727 RETURN_IF_EXCEPTION(scope, false);
728 if (targetIsExtensible) {
729 throwVMTypeError(exec, scope, "Proxy's 'preventExtensions' trap returned true even though its target is extensible. It should have returned false"_s);
730 return false;
731 }
732 }
733
734 return trapResultAsBool;
735}
736
737bool ProxyObject::preventExtensions(JSObject* object, ExecState* exec)
738{
739 return jsCast<ProxyObject*>(object)->performPreventExtensions(exec);
740}
741
742bool ProxyObject::performIsExtensible(ExecState* exec)
743{
744 NO_TAIL_CALLS();
745
746 VM& vm = exec->vm();
747 auto scope = DECLARE_THROW_SCOPE(vm);
748 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
749 throwStackOverflowError(exec, scope);
750 return false;
751 }
752
753 JSValue handlerValue = this->handler();
754 if (handlerValue.isNull()) {
755 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
756 return false;
757 }
758
759 JSObject* handler = jsCast<JSObject*>(handlerValue);
760 CallData callData;
761 CallType callType;
762 JSValue isExtensibleMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "isExtensible"), "'isExtensible' property of a Proxy's handler should be callable"_s);
763 RETURN_IF_EXCEPTION(scope, false);
764
765 JSObject* target = this->target();
766 if (isExtensibleMethod.isUndefined())
767 RELEASE_AND_RETURN(scope, target->isExtensible(exec));
768
769 MarkedArgumentBuffer arguments;
770 arguments.append(target);
771 ASSERT(!arguments.hasOverflowed());
772 JSValue trapResult = call(exec, isExtensibleMethod, callType, callData, handler, arguments);
773 RETURN_IF_EXCEPTION(scope, false);
774
775 bool trapResultAsBool = trapResult.toBoolean(exec);
776 RETURN_IF_EXCEPTION(scope, false);
777
778 bool isTargetExtensible = target->isExtensible(exec);
779 RETURN_IF_EXCEPTION(scope, false);
780
781 if (trapResultAsBool != isTargetExtensible) {
782 if (isTargetExtensible) {
783 ASSERT(!trapResultAsBool);
784 throwVMTypeError(exec, scope, "Proxy object's 'isExtensible' trap returned false when the target is extensible. It should have returned true"_s);
785 } else {
786 ASSERT(!isTargetExtensible);
787 ASSERT(trapResultAsBool);
788 throwVMTypeError(exec, scope, "Proxy object's 'isExtensible' trap returned true when the target is non-extensible. It should have returned false"_s);
789 }
790 }
791
792 return trapResultAsBool;
793}
794
795bool ProxyObject::isExtensible(JSObject* object, ExecState* exec)
796{
797 return jsCast<ProxyObject*>(object)->performIsExtensible(exec);
798}
799
800bool ProxyObject::performDefineOwnProperty(ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
801{
802 NO_TAIL_CALLS();
803
804 VM& vm = exec->vm();
805 auto scope = DECLARE_THROW_SCOPE(vm);
806 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
807 throwStackOverflowError(exec, scope);
808 return false;
809 }
810
811 JSObject* target = this->target();
812 auto performDefaultDefineOwnProperty = [&] {
813 RELEASE_AND_RETURN(scope, target->methodTable(vm)->defineOwnProperty(target, exec, propertyName, descriptor, shouldThrow));
814 };
815
816 if (propertyName.isPrivateName())
817 return performDefaultDefineOwnProperty();
818
819 JSValue handlerValue = this->handler();
820 if (handlerValue.isNull()) {
821 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
822 return false;
823 }
824
825 JSObject* handler = jsCast<JSObject*>(handlerValue);
826 CallData callData;
827 CallType callType;
828 JSValue definePropertyMethod = handler->getMethod(exec, callData, callType, vm.propertyNames->defineProperty, "'defineProperty' property of a Proxy's handler should be callable"_s);
829 RETURN_IF_EXCEPTION(scope, false);
830
831 if (definePropertyMethod.isUndefined())
832 return performDefaultDefineOwnProperty();
833
834 JSObject* descriptorObject = constructObjectFromPropertyDescriptor(exec, descriptor);
835 RETURN_IF_EXCEPTION(scope, false);
836
837 MarkedArgumentBuffer arguments;
838 arguments.append(target);
839 arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
840 arguments.append(descriptorObject);
841 ASSERT(!arguments.hasOverflowed());
842 JSValue trapResult = call(exec, definePropertyMethod, callType, callData, handler, arguments);
843 RETURN_IF_EXCEPTION(scope, false);
844
845 bool trapResultAsBool = trapResult.toBoolean(exec);
846 RETURN_IF_EXCEPTION(scope, false);
847
848 if (!trapResultAsBool)
849 return false;
850
851 PropertyDescriptor targetDescriptor;
852 bool isTargetDescriptorDefined = target->getOwnPropertyDescriptor(exec, propertyName, targetDescriptor);
853 RETURN_IF_EXCEPTION(scope, false);
854
855 bool targetIsExtensible = target->isExtensible(exec);
856 RETURN_IF_EXCEPTION(scope, false);
857 bool settingConfigurableToFalse = descriptor.configurablePresent() && !descriptor.configurable();
858
859 if (!isTargetDescriptorDefined) {
860 if (!targetIsExtensible) {
861 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);
862 return false;
863 }
864 if (settingConfigurableToFalse) {
865 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);
866 return false;
867 }
868
869 return true;
870 }
871
872 ASSERT(isTargetDescriptorDefined);
873 bool isCurrentDefined = isTargetDescriptorDefined;
874 const PropertyDescriptor& current = targetDescriptor;
875 bool throwException = false;
876 bool isCompatibleDescriptor = validateAndApplyPropertyDescriptor(exec, nullptr, propertyName, targetIsExtensible, descriptor, isCurrentDefined, current, throwException);
877 RETURN_IF_EXCEPTION(scope, false);
878 if (!isCompatibleDescriptor) {
879 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);
880 return false;
881 }
882 if (settingConfigurableToFalse && targetDescriptor.configurable()) {
883 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);
884 return false;
885 }
886
887 return true;
888}
889
890bool ProxyObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
891{
892 ProxyObject* thisObject = jsCast<ProxyObject*>(object);
893 return thisObject->performDefineOwnProperty(exec, propertyName, descriptor, shouldThrow);
894}
895
896void ProxyObject::performGetOwnPropertyNames(ExecState* exec, PropertyNameArray& trapResult, EnumerationMode enumerationMode)
897{
898 NO_TAIL_CALLS();
899
900 VM& vm = exec->vm();
901 auto scope = DECLARE_THROW_SCOPE(vm);
902 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
903 throwStackOverflowError(exec, scope);
904 return;
905 }
906 JSValue handlerValue = this->handler();
907 if (handlerValue.isNull()) {
908 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
909 return;
910 }
911
912 JSObject* handler = jsCast<JSObject*>(handlerValue);
913 CallData callData;
914 CallType callType;
915 JSValue ownKeysMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "ownKeys"), "'ownKeys' property of a Proxy's handler should be callable"_s);
916 RETURN_IF_EXCEPTION(scope, void());
917 JSObject* target = this->target();
918 if (ownKeysMethod.isUndefined()) {
919 scope.release();
920 target->methodTable(vm)->getOwnPropertyNames(target, exec, trapResult, enumerationMode);
921 return;
922 }
923
924 MarkedArgumentBuffer arguments;
925 arguments.append(target);
926 ASSERT(!arguments.hasOverflowed());
927 JSValue arrayLikeObject = call(exec, ownKeysMethod, callType, callData, handler, arguments);
928 RETURN_IF_EXCEPTION(scope, void());
929
930 PropertyNameMode propertyNameMode = trapResult.propertyNameMode();
931 RuntimeTypeMask resultFilter = 0;
932 switch (propertyNameMode) {
933 case PropertyNameMode::Symbols:
934 resultFilter = TypeSymbol;
935 break;
936 case PropertyNameMode::Strings:
937 resultFilter = TypeString;
938 break;
939 case PropertyNameMode::StringsAndSymbols:
940 resultFilter = TypeSymbol | TypeString;
941 break;
942 }
943 ASSERT(resultFilter);
944 RuntimeTypeMask dontThrowAnExceptionTypeFilter = TypeString | TypeSymbol;
945 HashSet<UniquedStringImpl*> uncheckedResultKeys;
946 HashSet<UniquedStringImpl*> seenKeys;
947
948 auto addPropName = [&] (JSValue value, RuntimeType type) -> bool {
949 static const bool doExitEarly = true;
950 static const bool dontExitEarly = false;
951
952 Identifier ident = value.toPropertyKey(exec);
953 RETURN_IF_EXCEPTION(scope, doExitEarly);
954
955 // If trapResult contains any duplicate entries, throw a TypeError exception.
956 //
957 // Per spec[1], filtering by type should occur _after_ [[OwnPropertyKeys]], so duplicates
958 // are tracked in a separate hashtable from uncheckedResultKeys (which only contain the
959 // keys filtered by type).
960 //
961 // [1] Per https://tc39.github.io/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeysmust not contain any duplicate names"_s);
962 if (!seenKeys.add(ident.impl()).isNewEntry) {
963 throwTypeError(exec, scope, "Proxy handler's 'ownKeys' trap result must not contain any duplicate names"_s);
964 return doExitEarly;
965 }
966
967 if (!(type & resultFilter))
968 return dontExitEarly;
969
970 uncheckedResultKeys.add(ident.impl());
971 trapResult.addUnchecked(ident.impl());
972 return dontExitEarly;
973 };
974
975 createListFromArrayLike(exec, arrayLikeObject, dontThrowAnExceptionTypeFilter, "Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"_s, addPropName);
976 RETURN_IF_EXCEPTION(scope, void());
977
978 bool targetIsExensible = target->isExtensible(exec);
979 RETURN_IF_EXCEPTION(scope, void());
980
981 PropertyNameArray targetKeys(&vm, propertyNameMode, trapResult.privateSymbolMode());
982 target->methodTable(vm)->getOwnPropertyNames(target, exec, targetKeys, enumerationMode);
983 RETURN_IF_EXCEPTION(scope, void());
984 Vector<UniquedStringImpl*> targetConfigurableKeys;
985 Vector<UniquedStringImpl*> targetNonConfigurableKeys;
986 for (const Identifier& ident : targetKeys) {
987 PropertyDescriptor descriptor;
988 bool isPropertyDefined = target->getOwnPropertyDescriptor(exec, ident.impl(), descriptor);
989 RETURN_IF_EXCEPTION(scope, void());
990 if (isPropertyDefined && !descriptor.configurable())
991 targetNonConfigurableKeys.append(ident.impl());
992 else
993 targetConfigurableKeys.append(ident.impl());
994 }
995
996 enum ContainedIn { IsContainedIn, IsNotContainedIn };
997 auto removeIfContainedInUncheckedResultKeys = [&] (UniquedStringImpl* impl) -> ContainedIn {
998 auto iter = uncheckedResultKeys.find(impl);
999 if (iter == uncheckedResultKeys.end())
1000 return IsNotContainedIn;
1001
1002 uncheckedResultKeys.remove(iter);
1003 return IsContainedIn;
1004 };
1005
1006 for (UniquedStringImpl* impl : targetNonConfigurableKeys) {
1007 if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
1008 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"));
1009 return;
1010 }
1011 }
1012
1013 if (!targetIsExensible) {
1014 for (UniquedStringImpl* impl : targetConfigurableKeys) {
1015 if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
1016 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"));
1017 return;
1018 }
1019 }
1020
1021 if (uncheckedResultKeys.size()) {
1022 throwVMTypeError(exec, scope, "Proxy handler's 'ownKeys' method returned a key that was not present in its non-extensible target"_s);
1023 return;
1024 }
1025 }
1026
1027 if (!enumerationMode.includeDontEnumProperties()) {
1028 // Filtering DontEnum properties is observable in proxies and must occur following the invariant checks above.
1029 auto data = trapResult.releaseData();
1030 trapResult.reset();
1031
1032 for (auto propertyName : data->propertyNameVector()) {
1033 PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
1034 auto result = getOwnPropertySlotCommon(exec, propertyName, slot);
1035 RETURN_IF_EXCEPTION(scope, void());
1036 if (!result)
1037 continue;
1038 if (slot.attributes() & PropertyAttribute::DontEnum)
1039 continue;
1040 trapResult.addUnchecked(propertyName.impl());
1041 }
1042 }
1043}
1044
1045void ProxyObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode)
1046{
1047 ProxyObject* thisObject = jsCast<ProxyObject*>(object);
1048 thisObject->performGetOwnPropertyNames(exec, propertyNameArray, enumerationMode);
1049}
1050
1051void ProxyObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode)
1052{
1053 NO_TAIL_CALLS();
1054 JSObject::getPropertyNames(object, exec, propertyNameArray, enumerationMode);
1055}
1056
1057void ProxyObject::getOwnNonIndexPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
1058{
1059 RELEASE_ASSERT_NOT_REACHED();
1060}
1061
1062void ProxyObject::getStructurePropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
1063{
1064 // We should always go down the getOwnPropertyNames path.
1065 RELEASE_ASSERT_NOT_REACHED();
1066}
1067
1068void ProxyObject::getGenericPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
1069{
1070 RELEASE_ASSERT_NOT_REACHED();
1071}
1072
1073bool ProxyObject::performSetPrototype(ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet)
1074{
1075 NO_TAIL_CALLS();
1076
1077 ASSERT(prototype.isObject() || prototype.isNull());
1078
1079 VM& vm = exec->vm();
1080 auto scope = DECLARE_THROW_SCOPE(vm);
1081 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
1082 throwStackOverflowError(exec, scope);
1083 return false;
1084 }
1085
1086 JSValue handlerValue = this->handler();
1087 if (handlerValue.isNull()) {
1088 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
1089 return false;
1090 }
1091
1092 JSObject* handler = jsCast<JSObject*>(handlerValue);
1093 CallData callData;
1094 CallType callType;
1095 JSValue setPrototypeOfMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "setPrototypeOf"), "'setPrototypeOf' property of a Proxy's handler should be callable"_s);
1096 RETURN_IF_EXCEPTION(scope, false);
1097
1098 JSObject* target = this->target();
1099 if (setPrototypeOfMethod.isUndefined())
1100 RELEASE_AND_RETURN(scope, target->setPrototype(vm, exec, prototype, shouldThrowIfCantSet));
1101
1102 MarkedArgumentBuffer arguments;
1103 arguments.append(target);
1104 arguments.append(prototype);
1105 ASSERT(!arguments.hasOverflowed());
1106 JSValue trapResult = call(exec, setPrototypeOfMethod, callType, callData, handler, arguments);
1107 RETURN_IF_EXCEPTION(scope, false);
1108
1109 bool trapResultAsBool = trapResult.toBoolean(exec);
1110 RETURN_IF_EXCEPTION(scope, false);
1111
1112 if (!trapResultAsBool) {
1113 if (shouldThrowIfCantSet)
1114 throwVMTypeError(exec, scope, "Proxy 'setPrototypeOf' returned false indicating it could not set the prototype value. The operation was expected to succeed"_s);
1115 return false;
1116 }
1117
1118 bool targetIsExtensible = target->isExtensible(exec);
1119 RETURN_IF_EXCEPTION(scope, false);
1120 if (targetIsExtensible)
1121 return true;
1122
1123 JSValue targetPrototype = target->getPrototype(vm, exec);
1124 RETURN_IF_EXCEPTION(scope, false);
1125 if (!sameValue(exec, prototype, targetPrototype)) {
1126 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);
1127 return false;
1128 }
1129
1130 return true;
1131}
1132
1133bool ProxyObject::setPrototype(JSObject* object, ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet)
1134{
1135 return jsCast<ProxyObject*>(object)->performSetPrototype(exec, prototype, shouldThrowIfCantSet);
1136}
1137
1138JSValue ProxyObject::performGetPrototype(ExecState* exec)
1139{
1140 NO_TAIL_CALLS();
1141
1142 VM& vm = exec->vm();
1143 auto scope = DECLARE_THROW_SCOPE(vm);
1144 if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
1145 throwStackOverflowError(exec, scope);
1146 return { };
1147 }
1148
1149 JSValue handlerValue = this->handler();
1150 if (handlerValue.isNull()) {
1151 throwVMTypeError(exec, scope, s_proxyAlreadyRevokedErrorMessage);
1152 return { };
1153 }
1154
1155 JSObject* handler = jsCast<JSObject*>(handlerValue);
1156 CallData callData;
1157 CallType callType;
1158 JSValue getPrototypeOfMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "getPrototypeOf"), "'getPrototypeOf' property of a Proxy's handler should be callable"_s);
1159 RETURN_IF_EXCEPTION(scope, { });
1160
1161 JSObject* target = this->target();
1162 if (getPrototypeOfMethod.isUndefined())
1163 RELEASE_AND_RETURN(scope, target->getPrototype(vm, exec));
1164
1165 MarkedArgumentBuffer arguments;
1166 arguments.append(target);
1167 ASSERT(!arguments.hasOverflowed());
1168 JSValue trapResult = call(exec, getPrototypeOfMethod, callType, callData, handler, arguments);
1169 RETURN_IF_EXCEPTION(scope, { });
1170
1171 if (!trapResult.isObject() && !trapResult.isNull()) {
1172 throwVMTypeError(exec, scope, "Proxy handler's 'getPrototypeOf' trap should either return an object or null"_s);
1173 return { };
1174 }
1175
1176 bool targetIsExtensible = target->isExtensible(exec);
1177 RETURN_IF_EXCEPTION(scope, { });
1178 if (targetIsExtensible)
1179 return trapResult;
1180
1181 JSValue targetPrototype = target->getPrototype(vm, exec);
1182 RETURN_IF_EXCEPTION(scope, { });
1183 if (!sameValue(exec, targetPrototype, trapResult)) {
1184 throwVMTypeError(exec, scope, "Proxy's 'getPrototypeOf' trap for a non-extensible target should return the same value as the target's prototype"_s);
1185 return { };
1186 }
1187
1188 return trapResult;
1189}
1190
1191JSValue ProxyObject::getPrototype(JSObject* object, ExecState* exec)
1192{
1193 return jsCast<ProxyObject*>(object)->performGetPrototype(exec);
1194}
1195
1196void ProxyObject::revoke(VM& vm)
1197{
1198 // This should only ever be called once and we should strictly transition from Object to null.
1199 RELEASE_ASSERT(!m_handler.get().isNull() && m_handler.get().isObject());
1200 m_handler.set(vm, this, jsNull());
1201}
1202
1203bool ProxyObject::isRevoked() const
1204{
1205 return handler().isNull();
1206}
1207
1208void ProxyObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
1209{
1210 ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
1211 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
1212 Base::visitChildren(thisObject, visitor);
1213
1214 visitor.append(thisObject->m_target);
1215 visitor.append(thisObject->m_handler);
1216}
1217
1218} // namespace JSC
1219