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