1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "proxy/ScriptedProxyHandler.h"
11 #include "js/CharacterEncoding.h"
12 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
13 #include "js/PropertyDescriptor.h" // JS::FromPropertyDescriptor
14 #include "vm/EqualityOperations.h" // js::SameValue
15 #include "vm/JSFunction.h"
16 #include "vm/JSObject.h"
17 #include "vm/PlainObject.h" // js::PlainObject
19 #include "vm/JSObject-inl.h"
20 #include "vm/NativeObject-inl.h"
24 using JS::IsArrayAnswer
;
26 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
27 // 9.1.6.2 IsCompatiblePropertyDescriptor. BUT that method just calls
28 // 9.1.6.3 ValidateAndApplyPropertyDescriptor with two additional constant
29 // arguments. Therefore step numbering is from the latter method, and
30 // resulting dead code has been removed.
32 // If an exception should be thrown, we will set errorDetails.
33 static bool IsCompatiblePropertyDescriptor(JSContext
* cx
, bool extensible
,
34 Handle
<PropertyDescriptor
> desc
,
35 Handle
<PropertyDescriptor
> current
,
36 const char** errorDetails
) {
37 // precondition: we won't set details if checks pass, so it must be null
39 MOZ_ASSERT(*errorDetails
== nullptr);
42 if (!current
.object()) {
43 // Step 2a-b,e. As |O| is always undefined, steps 2c-d fall away.
45 static const char DETAILS_NOT_EXTENSIBLE
[] =
46 "proxy can't report an extensible object as non-extensible";
47 *errorDetails
= DETAILS_NOT_EXTENSIBLE
;
53 if (!desc
.hasValue() && !desc
.hasWritable() && !desc
.hasGetterObject() &&
54 !desc
.hasSetterObject() && !desc
.hasEnumerable() &&
55 !desc
.hasConfigurable()) {
60 if ((!desc
.hasWritable() ||
61 (current
.hasWritable() && desc
.writable() == current
.writable())) &&
62 (!desc
.hasGetterObject() || desc
.getter() == current
.getter()) &&
63 (!desc
.hasSetterObject() || desc
.setter() == current
.setter()) &&
64 (!desc
.hasEnumerable() || desc
.enumerable() == current
.enumerable()) &&
65 (!desc
.hasConfigurable() ||
66 desc
.configurable() == current
.configurable())) {
67 if (!desc
.hasValue()) {
72 if (!SameValue(cx
, desc
.value(), current
.value(), &same
)) {
82 if (!current
.configurable()) {
84 if (desc
.hasConfigurable() && desc
.configurable()) {
85 static const char DETAILS_CANT_REPORT_NC_AS_C
[] =
86 "proxy can't report an existing non-configurable property as "
88 *errorDetails
= DETAILS_CANT_REPORT_NC_AS_C
;
93 if (desc
.hasEnumerable() && desc
.enumerable() != current
.enumerable()) {
94 static const char DETAILS_ENUM_DIFFERENT
[] =
95 "proxy can't report a different 'enumerable' from target when target "
96 "is not configurable";
97 *errorDetails
= DETAILS_ENUM_DIFFERENT
;
103 if (desc
.isGenericDescriptor()) {
108 if (current
.isDataDescriptor() != desc
.isDataDescriptor()) {
109 // Steps 7a, 11. As |O| is always undefined, steps 2b-c fall away.
110 if (!current
.configurable()) {
111 static const char DETAILS_CURRENT_NC_DIFF_TYPE
[] =
112 "proxy can't report a different descriptor type when target is not "
114 *errorDetails
= DETAILS_CURRENT_NC_DIFF_TYPE
;
120 if (current
.isDataDescriptor()) {
121 MOZ_ASSERT(desc
.isDataDescriptor()); // by step 7
122 if (!current
.configurable() && !current
.writable()) {
123 if (desc
.hasWritable() && desc
.writable()) {
124 static const char DETAILS_CANT_REPORT_NW_AS_W
[] =
125 "proxy can't report a non-configurable, non-writable property as "
127 *errorDetails
= DETAILS_CANT_REPORT_NW_AS_W
;
131 if (desc
.hasValue()) {
133 if (!SameValue(cx
, desc
.value(), current
.value(), &same
)) {
137 static const char DETAILS_DIFFERENT_VALUE
[] =
138 "proxy must report the same value for the non-writable, "
139 "non-configurable property";
140 *errorDetails
= DETAILS_DIFFERENT_VALUE
;
150 MOZ_ASSERT(current
.isAccessorDescriptor()); // by step 8
151 MOZ_ASSERT(desc
.isAccessorDescriptor()); // by step 7
153 if (current
.configurable()) {
156 if (desc
.hasSetterObject() && (desc
.setter() != current
.setter())) {
157 static const char DETAILS_SETTERS_DIFFERENT
[] =
158 "proxy can't report different setters for a currently non-configurable "
160 *errorDetails
= DETAILS_SETTERS_DIFFERENT
;
161 } else if (desc
.hasGetterObject() && (desc
.getter() != current
.getter())) {
162 static const char DETAILS_GETTERS_DIFFERENT
[] =
163 "proxy can't report different getters for a currently non-configurable "
165 *errorDetails
= DETAILS_GETTERS_DIFFERENT
;
170 // Get the [[ProxyHandler]] of a scripted proxy.
172 JSObject
* ScriptedProxyHandler::handlerObject(const JSObject
* proxy
) {
173 MOZ_ASSERT(proxy
->as
<ProxyObject
>().handler() ==
174 &ScriptedProxyHandler::singleton
);
175 return proxy
->as
<ProxyObject
>()
176 .reservedSlot(ScriptedProxyHandler::HANDLER_EXTRA
)
180 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
181 // 7.3.9 GetMethod, reimplemented for proxy handler trap-getting to produce
182 // better error messages.
183 static bool GetProxyTrap(JSContext
* cx
, HandleObject handler
,
184 HandlePropertyName name
, MutableHandleValue func
) {
186 if (!GetProperty(cx
, handler
, handler
, name
, func
)) {
191 if (func
.isUndefined()) {
201 if (!IsCallable(func
)) {
202 UniqueChars bytes
= EncodeAscii(cx
, name
);
207 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_BAD_TRAP
,
215 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
216 // 9.5.1 Proxy.[[GetPrototypeOf]].
217 bool ScriptedProxyHandler::getPrototype(JSContext
* cx
, HandleObject proxy
,
218 MutableHandleObject protop
) const {
220 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
222 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
223 JSMSG_PROXY_REVOKED
);
228 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
232 RootedValue
trap(cx
);
233 if (!GetProxyTrap(cx
, handler
, cx
->names().getPrototypeOf
, &trap
)) {
238 if (trap
.isUndefined()) {
239 return GetPrototype(cx
, target
, protop
);
243 RootedValue
handlerProto(cx
);
245 FixedInvokeArgs
<1> args(cx
);
247 args
[0].setObject(*target
);
249 handlerProto
.setObject(*handler
);
251 if (!js::Call(cx
, trap
, handlerProto
, args
, &handlerProto
)) {
257 if (!handlerProto
.isObjectOrNull()) {
258 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
259 JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN
);
264 bool extensibleTarget
;
265 if (!IsExtensible(cx
, target
, &extensibleTarget
)) {
270 if (extensibleTarget
) {
271 protop
.set(handlerProto
.toObjectOrNull());
276 RootedObject
targetProto(cx
);
277 if (!GetPrototype(cx
, target
, &targetProto
)) {
282 if (handlerProto
.toObjectOrNull() != targetProto
) {
283 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
284 JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP
);
289 protop
.set(handlerProto
.toObjectOrNull());
293 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
294 // 9.5.2 Proxy.[[SetPrototypeOf]].
295 bool ScriptedProxyHandler::setPrototype(JSContext
* cx
, HandleObject proxy
,
297 ObjectOpResult
& result
) const {
299 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
301 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
302 JSMSG_PROXY_REVOKED
);
307 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
311 RootedValue
trap(cx
);
312 if (!GetProxyTrap(cx
, handler
, cx
->names().setPrototypeOf
, &trap
)) {
317 if (trap
.isUndefined()) {
318 return SetPrototype(cx
, target
, proto
, result
);
322 bool booleanTrapResult
;
324 FixedInvokeArgs
<2> args(cx
);
326 args
[0].setObject(*target
);
327 args
[1].setObjectOrNull(proto
);
329 RootedValue
hval(cx
, ObjectValue(*handler
));
330 if (!js::Call(cx
, trap
, hval
, args
, &hval
)) {
334 booleanTrapResult
= ToBoolean(hval
);
338 if (!booleanTrapResult
) {
339 return result
.fail(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE
);
343 bool extensibleTarget
;
344 if (!IsExtensible(cx
, target
, &extensibleTarget
)) {
349 if (extensibleTarget
) {
350 return result
.succeed();
354 RootedObject
targetProto(cx
);
355 if (!GetPrototype(cx
, target
, &targetProto
)) {
360 if (proto
!= targetProto
) {
361 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
362 JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP
);
367 return result
.succeed();
370 bool ScriptedProxyHandler::getPrototypeIfOrdinary(
371 JSContext
* cx
, HandleObject proxy
, bool* isOrdinary
,
372 MutableHandleObject protop
) const {
377 // Not yet part of ES6, but hopefully to be standards-tracked -- and needed to
378 // handle revoked proxies in any event.
379 bool ScriptedProxyHandler::setImmutablePrototype(JSContext
* cx
,
381 bool* succeeded
) const {
382 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
384 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
385 JSMSG_PROXY_REVOKED
);
389 return SetImmutablePrototype(cx
, target
, succeeded
);
392 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
393 // 9.5.4 Proxy.[[PreventExtensions]]()
394 bool ScriptedProxyHandler::preventExtensions(JSContext
* cx
, HandleObject proxy
,
395 ObjectOpResult
& result
) const {
397 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
399 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
400 JSMSG_PROXY_REVOKED
);
405 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
409 RootedValue
trap(cx
);
410 if (!GetProxyTrap(cx
, handler
, cx
->names().preventExtensions
, &trap
)) {
415 if (trap
.isUndefined()) {
416 return PreventExtensions(cx
, target
, result
);
420 bool booleanTrapResult
;
422 RootedValue
arg(cx
, ObjectValue(*target
));
423 RootedValue
trapResult(cx
);
424 if (!Call(cx
, trap
, handler
, arg
, &trapResult
)) {
428 booleanTrapResult
= ToBoolean(trapResult
);
432 if (booleanTrapResult
) {
434 bool targetIsExtensible
;
435 if (!IsExtensible(cx
, target
, &targetIsExtensible
)) {
439 if (targetIsExtensible
) {
440 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
441 JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE
);
446 return result
.succeed();
450 return result
.fail(JSMSG_PROXY_PREVENTEXTENSIONS_RETURNED_FALSE
);
453 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
454 // 9.5.3 Proxy.[[IsExtensible]]()
455 bool ScriptedProxyHandler::isExtensible(JSContext
* cx
, HandleObject proxy
,
456 bool* extensible
) const {
458 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
460 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
461 JSMSG_PROXY_REVOKED
);
466 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
470 RootedValue
trap(cx
);
471 if (!GetProxyTrap(cx
, handler
, cx
->names().isExtensible
, &trap
)) {
476 if (trap
.isUndefined()) {
477 return IsExtensible(cx
, target
, extensible
);
481 bool booleanTrapResult
;
483 RootedValue
arg(cx
, ObjectValue(*target
));
484 RootedValue
trapResult(cx
);
485 if (!Call(cx
, trap
, handler
, arg
, &trapResult
)) {
489 booleanTrapResult
= ToBoolean(trapResult
);
494 if (!IsExtensible(cx
, target
, &targetResult
)) {
499 if (targetResult
!= booleanTrapResult
) {
500 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
501 JSMSG_PROXY_EXTENSIBILITY
);
506 *extensible
= booleanTrapResult
;
510 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
511 // 9.5.5 Proxy.[[GetOwnProperty]](P)
512 bool ScriptedProxyHandler::getOwnPropertyDescriptor(
513 JSContext
* cx
, HandleObject proxy
, HandleId id
,
514 MutableHandle
<PropertyDescriptor
> desc
) const {
516 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
518 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
519 JSMSG_PROXY_REVOKED
);
524 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
528 RootedValue
trap(cx
);
529 if (!GetProxyTrap(cx
, handler
, cx
->names().getOwnPropertyDescriptor
, &trap
)) {
534 if (trap
.isUndefined()) {
535 return GetOwnPropertyDescriptor(cx
, target
, id
, desc
);
539 RootedValue
propKey(cx
);
540 if (!IdToStringOrSymbol(cx
, id
, &propKey
)) {
544 RootedValue
trapResult(cx
);
545 RootedValue
targetVal(cx
, ObjectValue(*target
));
546 if (!Call(cx
, trap
, handler
, targetVal
, propKey
, &trapResult
)) {
551 if (!trapResult
.isUndefined() && !trapResult
.isObject()) {
552 return js::Throw(cx
, id
, JSMSG_PROXY_GETOWN_OBJORUNDEF
);
556 Rooted
<PropertyDescriptor
> targetDesc(cx
);
557 if (!GetOwnPropertyDescriptor(cx
, target
, id
, &targetDesc
)) {
562 if (trapResult
.isUndefined()) {
564 if (!targetDesc
.object()) {
565 desc
.object().set(nullptr);
570 if (!targetDesc
.configurable()) {
571 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_NC_AS_NE
);
575 bool extensibleTarget
;
576 if (!IsExtensible(cx
, target
, &extensibleTarget
)) {
581 if (!extensibleTarget
) {
582 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_E_AS_NE
);
586 desc
.object().set(nullptr);
591 bool extensibleTarget
;
592 if (!IsExtensible(cx
, target
, &extensibleTarget
)) {
597 Rooted
<PropertyDescriptor
> resultDesc(cx
);
598 if (!ToPropertyDescriptor(cx
, trapResult
, true, &resultDesc
)) {
603 CompletePropertyDescriptor(&resultDesc
);
606 const char* errorDetails
= nullptr;
607 if (!IsCompatiblePropertyDescriptor(cx
, extensibleTarget
, resultDesc
,
608 targetDesc
, &errorDetails
))
613 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_INVALID
, errorDetails
);
617 if (!resultDesc
.configurable()) {
618 if (!targetDesc
.object()) {
619 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_NE_AS_NC
);
622 if (targetDesc
.configurable()) {
623 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_C_AS_NC
);
626 if (resultDesc
.hasWritable() && !resultDesc
.writable()) {
627 if (targetDesc
.writable()) {
628 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_W_AS_NW
);
634 desc
.set(resultDesc
);
635 desc
.object().set(proxy
);
639 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
640 // 9.5.6 Proxy.[[DefineOwnProperty]](P, Desc)
641 bool ScriptedProxyHandler::defineProperty(JSContext
* cx
, HandleObject proxy
,
643 Handle
<PropertyDescriptor
> desc
,
644 ObjectOpResult
& result
) const {
646 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
648 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
649 JSMSG_PROXY_REVOKED
);
654 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
658 RootedValue
trap(cx
);
659 if (!GetProxyTrap(cx
, handler
, cx
->names().defineProperty
, &trap
)) {
664 if (trap
.isUndefined()) {
665 return DefineProperty(cx
, target
, id
, desc
, result
);
669 RootedValue
descObj(cx
);
670 if (!FromPropertyDescriptorToObject(cx
, desc
, &descObj
)) {
675 RootedValue
propKey(cx
);
676 if (!IdToStringOrSymbol(cx
, id
, &propKey
)) {
680 RootedValue
trapResult(cx
);
682 FixedInvokeArgs
<3> args(cx
);
684 args
[0].setObject(*target
);
685 args
[1].set(propKey
);
686 args
[2].set(descObj
);
688 RootedValue
thisv(cx
, ObjectValue(*handler
));
689 if (!Call(cx
, trap
, thisv
, args
, &trapResult
)) {
695 if (!ToBoolean(trapResult
)) {
696 return result
.fail(JSMSG_PROXY_DEFINE_RETURNED_FALSE
);
700 Rooted
<PropertyDescriptor
> targetDesc(cx
);
701 if (!GetOwnPropertyDescriptor(cx
, target
, id
, &targetDesc
)) {
706 bool extensibleTarget
;
707 if (!IsExtensible(cx
, target
, &extensibleTarget
)) {
712 bool settingConfigFalse
= desc
.hasConfigurable() && !desc
.configurable();
715 if (!targetDesc
.object()) {
717 if (!extensibleTarget
) {
718 return js::Throw(cx
, id
, JSMSG_CANT_DEFINE_NEW
);
722 if (settingConfigFalse
) {
723 return js::Throw(cx
, id
, JSMSG_CANT_DEFINE_NE_AS_NC
);
727 const char* errorDetails
= nullptr;
728 if (!IsCompatiblePropertyDescriptor(cx
, extensibleTarget
, desc
, targetDesc
,
733 return js::Throw(cx
, id
, JSMSG_CANT_DEFINE_INVALID
, errorDetails
);
737 if (settingConfigFalse
&& targetDesc
.configurable()) {
738 static const char DETAILS_CANT_REPORT_C_AS_NC
[] =
739 "proxy can't define an existing configurable property as "
741 return js::Throw(cx
, id
, JSMSG_CANT_DEFINE_INVALID
,
742 DETAILS_CANT_REPORT_C_AS_NC
);
745 if (targetDesc
.isDataDescriptor() && !targetDesc
.configurable() &&
746 targetDesc
.writable()) {
747 if (desc
.hasWritable() && !desc
.writable()) {
748 static const char DETAILS_CANT_DEFINE_NW
[] =
749 "proxy can't define an existing non-configurable writable property "
751 return js::Throw(cx
, id
, JSMSG_CANT_DEFINE_INVALID
,
752 DETAILS_CANT_DEFINE_NW
);
758 return result
.succeed();
761 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
762 // 7.3.17 CreateListFromArrayLike with elementTypes fixed to symbol/string.
763 static bool CreateFilteredListFromArrayLike(JSContext
* cx
, HandleValue v
,
764 MutableHandleIdVector props
) {
766 RootedObject
obj(cx
, RequireObject(cx
, JSMSG_OBJECT_REQUIRED_RET_OWNKEYS
,
767 JSDVG_IGNORE_STACK
, v
));
774 if (!GetLengthProperty(cx
, obj
, &len
)) {
779 RootedValue
next(cx
);
782 while (index
< len
) {
784 if (!GetElement(cx
, obj
, obj
, index
, &next
)) {
789 if (!next
.isString() && !next
.isSymbol()) {
790 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
791 JSMSG_OWNKEYS_STR_SYM
);
795 if (!PrimitiveValueToId
<CanGC
>(cx
, next
, &id
)) {
800 if (!props
.append(id
)) {
812 // ES2018 draft rev aab1ea3bd4d03c85d6f4a91503b4169346ab7271
813 // 9.5.11 Proxy.[[OwnPropertyKeys]]()
814 bool ScriptedProxyHandler::ownPropertyKeys(JSContext
* cx
, HandleObject proxy
,
815 MutableHandleIdVector props
) const {
817 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
819 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
820 JSMSG_PROXY_REVOKED
);
825 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
829 RootedValue
trap(cx
);
830 if (!GetProxyTrap(cx
, handler
, cx
->names().ownKeys
, &trap
)) {
835 if (trap
.isUndefined()) {
836 return GetPropertyKeys(
837 cx
, target
, JSITER_OWNONLY
| JSITER_HIDDEN
| JSITER_SYMBOLS
, props
);
841 RootedValue
trapResultArray(cx
);
842 RootedValue
targetVal(cx
, ObjectValue(*target
));
843 if (!Call(cx
, trap
, handler
, targetVal
, &trapResultArray
)) {
848 RootedIdVector
trapResult(cx
);
849 if (!CreateFilteredListFromArrayLike(cx
, trapResultArray
, &trapResult
)) {
854 Rooted
<GCHashSet
<jsid
>> uncheckedResultKeys(
855 cx
, GCHashSet
<jsid
>(cx
, trapResult
.length()));
857 for (size_t i
= 0, len
= trapResult
.length(); i
< len
; i
++) {
858 MOZ_ASSERT(!JSID_IS_VOID(trapResult
[i
]));
860 auto ptr
= uncheckedResultKeys
.lookupForAdd(trapResult
[i
]);
862 return js::Throw(cx
, trapResult
[i
], JSMSG_OWNKEYS_DUPLICATE
);
865 if (!uncheckedResultKeys
.add(ptr
, trapResult
[i
])) {
871 bool extensibleTarget
;
872 if (!IsExtensible(cx
, target
, &extensibleTarget
)) {
877 RootedIdVector
targetKeys(cx
);
878 if (!GetPropertyKeys(cx
, target
,
879 JSITER_OWNONLY
| JSITER_HIDDEN
| JSITER_SYMBOLS
,
885 RootedIdVector
targetConfigurableKeys(cx
);
886 RootedIdVector
targetNonconfigurableKeys(cx
);
889 Rooted
<PropertyDescriptor
> desc(cx
);
890 for (size_t i
= 0; i
< targetKeys
.length(); ++i
) {
892 if (!GetOwnPropertyDescriptor(cx
, target
, targetKeys
[i
], &desc
)) {
897 if (desc
.object() && !desc
.configurable()) {
898 if (!targetNonconfigurableKeys
.append(targetKeys
[i
])) {
902 if (!targetConfigurableKeys
.append(targetKeys
[i
])) {
909 if (extensibleTarget
&& targetNonconfigurableKeys
.empty()) {
910 return props
.appendAll(std::move(trapResult
));
914 for (size_t i
= 0; i
< targetNonconfigurableKeys
.length(); ++i
) {
915 MOZ_ASSERT(!JSID_IS_VOID(targetNonconfigurableKeys
[i
]));
917 auto ptr
= uncheckedResultKeys
.lookup(targetNonconfigurableKeys
[i
]);
921 return js::Throw(cx
, targetNonconfigurableKeys
[i
], JSMSG_CANT_SKIP_NC
);
925 uncheckedResultKeys
.remove(ptr
);
929 if (extensibleTarget
) {
930 return props
.appendAll(std::move(trapResult
));
934 for (size_t i
= 0; i
< targetConfigurableKeys
.length(); ++i
) {
935 MOZ_ASSERT(!JSID_IS_VOID(targetConfigurableKeys
[i
]));
937 auto ptr
= uncheckedResultKeys
.lookup(targetConfigurableKeys
[i
]);
941 return js::Throw(cx
, targetConfigurableKeys
[i
],
942 JSMSG_CANT_REPORT_E_AS_NE
);
946 uncheckedResultKeys
.remove(ptr
);
950 if (!uncheckedResultKeys
.empty()) {
951 RootedId
id(cx
, uncheckedResultKeys
.all().front());
952 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_NEW
);
956 return props
.appendAll(std::move(trapResult
));
959 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
960 // 9.5.10 Proxy.[[Delete]](P)
961 bool ScriptedProxyHandler::delete_(JSContext
* cx
, HandleObject proxy
,
962 HandleId id
, ObjectOpResult
& result
) const {
964 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
966 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
967 JSMSG_PROXY_REVOKED
);
972 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
976 RootedValue
trap(cx
);
977 if (!GetProxyTrap(cx
, handler
, cx
->names().deleteProperty
, &trap
)) {
982 if (trap
.isUndefined()) {
983 return DeleteProperty(cx
, target
, id
, result
);
987 bool booleanTrapResult
;
989 RootedValue
value(cx
);
990 if (!IdToStringOrSymbol(cx
, id
, &value
)) {
994 RootedValue
targetVal(cx
, ObjectValue(*target
));
995 RootedValue
trapResult(cx
);
996 if (!Call(cx
, trap
, handler
, targetVal
, value
, &trapResult
)) {
1000 booleanTrapResult
= ToBoolean(trapResult
);
1004 if (!booleanTrapResult
) {
1005 return result
.fail(JSMSG_PROXY_DELETE_RETURNED_FALSE
);
1009 Rooted
<PropertyDescriptor
> desc(cx
);
1010 if (!GetOwnPropertyDescriptor(cx
, target
, id
, &desc
)) {
1015 if (!desc
.object()) {
1016 return result
.succeed();
1020 if (!desc
.configurable()) {
1021 return Throw(cx
, id
, JSMSG_CANT_DELETE
);
1025 if (!IsExtensible(cx
, target
, &extensible
)) {
1030 return Throw(cx
, id
, JSMSG_CANT_DELETE_NON_EXTENSIBLE
);
1034 return result
.succeed();
1037 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
1038 // 9.5.7 Proxy.[[HasProperty]](P)
1039 bool ScriptedProxyHandler::has(JSContext
* cx
, HandleObject proxy
, HandleId id
,
1042 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
1044 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1045 JSMSG_PROXY_REVOKED
);
1050 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
1054 RootedValue
trap(cx
);
1055 if (!GetProxyTrap(cx
, handler
, cx
->names().has
, &trap
)) {
1060 if (trap
.isUndefined()) {
1061 return HasProperty(cx
, target
, id
, bp
);
1065 RootedValue
value(cx
);
1066 if (!IdToStringOrSymbol(cx
, id
, &value
)) {
1070 RootedValue
trapResult(cx
);
1071 RootedValue
targetVal(cx
, ObjectValue(*target
));
1072 if (!Call(cx
, trap
, handler
, targetVal
, value
, &trapResult
)) {
1076 bool booleanTrapResult
= ToBoolean(trapResult
);
1079 if (!booleanTrapResult
) {
1081 Rooted
<PropertyDescriptor
> desc(cx
);
1082 if (!GetOwnPropertyDescriptor(cx
, target
, id
, &desc
)) {
1087 if (desc
.object()) {
1089 if (!desc
.configurable()) {
1090 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_NC_AS_NE
);
1095 if (!IsExtensible(cx
, target
, &extensible
)) {
1101 return js::Throw(cx
, id
, JSMSG_CANT_REPORT_E_AS_NE
);
1107 *bp
= booleanTrapResult
;
1111 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
1112 // 9.5.8 Proxy.[[GetP]](P, Receiver)
1113 bool ScriptedProxyHandler::get(JSContext
* cx
, HandleObject proxy
,
1114 HandleValue receiver
, HandleId id
,
1115 MutableHandleValue vp
) const {
1117 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
1119 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1120 JSMSG_PROXY_REVOKED
);
1125 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
1129 RootedValue
trap(cx
);
1130 if (!GetProxyTrap(cx
, handler
, cx
->names().get
, &trap
)) {
1135 if (trap
.isUndefined()) {
1136 return GetProperty(cx
, target
, receiver
, id
, vp
);
1140 RootedValue
value(cx
);
1141 if (!IdToStringOrSymbol(cx
, id
, &value
)) {
1145 RootedValue
trapResult(cx
);
1147 FixedInvokeArgs
<3> args(cx
);
1149 args
[0].setObject(*target
);
1151 args
[2].set(receiver
);
1153 RootedValue
thisv(cx
, ObjectValue(*handler
));
1154 if (!Call(cx
, trap
, thisv
, args
, &trapResult
)) {
1160 Rooted
<PropertyDescriptor
> desc(cx
);
1161 if (!GetOwnPropertyDescriptor(cx
, target
, id
, &desc
)) {
1166 if (desc
.object()) {
1168 if (desc
.isDataDescriptor() && !desc
.configurable() && !desc
.writable()) {
1170 if (!SameValue(cx
, trapResult
, desc
.value(), &same
)) {
1174 return js::Throw(cx
, id
, JSMSG_MUST_REPORT_SAME_VALUE
);
1179 if (desc
.isAccessorDescriptor() && !desc
.configurable() &&
1180 (desc
.getterObject() == nullptr) && !trapResult
.isUndefined()) {
1181 return js::Throw(cx
, id
, JSMSG_MUST_REPORT_UNDEFINED
);
1190 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
1191 // 9.5.9 Proxy.[[Set]](P, V, Receiver)
1192 bool ScriptedProxyHandler::set(JSContext
* cx
, HandleObject proxy
, HandleId id
,
1193 HandleValue v
, HandleValue receiver
,
1194 ObjectOpResult
& result
) const {
1196 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
1198 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1199 JSMSG_PROXY_REVOKED
);
1204 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
1208 RootedValue
trap(cx
);
1209 if (!GetProxyTrap(cx
, handler
, cx
->names().set
, &trap
)) {
1214 if (trap
.isUndefined()) {
1215 return SetProperty(cx
, target
, id
, v
, receiver
, result
);
1219 RootedValue
value(cx
);
1220 if (!IdToStringOrSymbol(cx
, id
, &value
)) {
1224 RootedValue
trapResult(cx
);
1226 FixedInvokeArgs
<4> args(cx
);
1228 args
[0].setObject(*target
);
1231 args
[3].set(receiver
);
1233 RootedValue
thisv(cx
, ObjectValue(*handler
));
1234 if (!Call(cx
, trap
, thisv
, args
, &trapResult
)) {
1240 if (!ToBoolean(trapResult
)) {
1241 return result
.fail(JSMSG_PROXY_SET_RETURNED_FALSE
);
1245 Rooted
<PropertyDescriptor
> desc(cx
);
1246 if (!GetOwnPropertyDescriptor(cx
, target
, id
, &desc
)) {
1251 if (desc
.object()) {
1253 if (desc
.isDataDescriptor() && !desc
.configurable() && !desc
.writable()) {
1255 if (!SameValue(cx
, v
, desc
.value(), &same
)) {
1259 return js::Throw(cx
, id
, JSMSG_CANT_SET_NW_NC
);
1264 if (desc
.isAccessorDescriptor() && !desc
.configurable() &&
1265 desc
.setterObject() == nullptr) {
1266 return js::Throw(cx
, id
, JSMSG_CANT_SET_WO_SETTER
);
1271 return result
.succeed();
1274 // ES7 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.13 Proxy.[[Call]]
1275 bool ScriptedProxyHandler::call(JSContext
* cx
, HandleObject proxy
,
1276 const CallArgs
& args
) const {
1278 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
1280 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1281 JSMSG_PROXY_REVOKED
);
1286 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
1288 MOZ_ASSERT(target
->isCallable());
1291 RootedValue
trap(cx
);
1292 if (!GetProxyTrap(cx
, handler
, cx
->names().apply
, &trap
)) {
1297 if (trap
.isUndefined()) {
1298 InvokeArgs
iargs(cx
);
1299 if (!FillArgumentsFromArraylike(cx
, iargs
, args
)) {
1303 RootedValue
fval(cx
, ObjectValue(*target
));
1304 return js::Call(cx
, fval
, args
.thisv(), iargs
, args
.rval());
1308 RootedObject
argArray(cx
,
1309 NewDenseCopiedArray(cx
, args
.length(), args
.array()));
1315 FixedInvokeArgs
<3> iargs(cx
);
1317 iargs
[0].setObject(*target
);
1318 iargs
[1].set(args
.thisv());
1319 iargs
[2].setObject(*argArray
);
1321 RootedValue
thisv(cx
, ObjectValue(*handler
));
1322 return js::Call(cx
, trap
, thisv
, iargs
, args
.rval());
1325 // ES7 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.14 Proxy.[[Construct]]
1326 bool ScriptedProxyHandler::construct(JSContext
* cx
, HandleObject proxy
,
1327 const CallArgs
& args
) const {
1329 RootedObject
handler(cx
, ScriptedProxyHandler::handlerObject(proxy
));
1331 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1332 JSMSG_PROXY_REVOKED
);
1337 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
1339 MOZ_ASSERT(target
->isConstructor());
1342 RootedValue
trap(cx
);
1343 if (!GetProxyTrap(cx
, handler
, cx
->names().construct
, &trap
)) {
1348 if (trap
.isUndefined()) {
1349 ConstructArgs
cargs(cx
);
1350 if (!FillArgumentsFromArraylike(cx
, cargs
, args
)) {
1354 RootedValue
targetv(cx
, ObjectValue(*target
));
1355 RootedObject
obj(cx
);
1356 if (!Construct(cx
, targetv
, cargs
, args
.newTarget(), &obj
)) {
1360 args
.rval().setObject(*obj
);
1365 RootedObject
argArray(cx
,
1366 NewDenseCopiedArray(cx
, args
.length(), args
.array()));
1373 FixedInvokeArgs
<3> iargs(cx
);
1375 iargs
[0].setObject(*target
);
1376 iargs
[1].setObject(*argArray
);
1377 iargs
[2].set(args
.newTarget());
1379 RootedValue
thisv(cx
, ObjectValue(*handler
));
1380 if (!Call(cx
, trap
, thisv
, iargs
, args
.rval())) {
1386 if (!args
.rval().isObject()) {
1387 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1388 JSMSG_PROXY_CONSTRUCT_OBJECT
);
1395 bool ScriptedProxyHandler::nativeCall(JSContext
* cx
, IsAcceptableThis test
,
1397 const CallArgs
& args
) const {
1398 ReportIncompatible(cx
, args
);
1402 bool ScriptedProxyHandler::hasInstance(JSContext
* cx
, HandleObject proxy
,
1403 MutableHandleValue v
, bool* bp
) const {
1404 return InstanceofOperator(cx
, proxy
, v
, bp
);
1407 bool ScriptedProxyHandler::getBuiltinClass(JSContext
* cx
, HandleObject proxy
,
1408 ESClass
* cls
) const {
1409 *cls
= ESClass::Other
;
1413 bool ScriptedProxyHandler::isArray(JSContext
* cx
, HandleObject proxy
,
1414 IsArrayAnswer
* answer
) const {
1415 RootedObject
target(cx
, proxy
->as
<ProxyObject
>().target());
1417 return JS::IsArray(cx
, target
, answer
);
1420 *answer
= IsArrayAnswer::RevokedProxy
;
1424 const char* ScriptedProxyHandler::className(JSContext
* cx
,
1425 HandleObject proxy
) const {
1426 // Right now the caller is not prepared to handle failures.
1427 return BaseProxyHandler::className(cx
, proxy
);
1430 JSString
* ScriptedProxyHandler::fun_toString(JSContext
* cx
, HandleObject proxy
,
1431 bool isToSource
) const {
1432 // The BaseProxyHandler has the desired behavior: Throw for non-callable,
1433 // otherwise return [native code].
1434 return BaseProxyHandler::fun_toString(cx
, proxy
, isToSource
);
1437 RegExpShared
* ScriptedProxyHandler::regexp_toShared(JSContext
* cx
,
1438 HandleObject proxy
) const {
1439 MOZ_CRASH("Should not end up in ScriptedProxyHandler::regexp_toShared");
1442 bool ScriptedProxyHandler::boxedValue_unbox(JSContext
* cx
, HandleObject proxy
,
1443 MutableHandleValue vp
) const {
1444 MOZ_CRASH("Should not end up in ScriptedProxyHandler::boxedValue_unbox");
1448 bool ScriptedProxyHandler::isCallable(JSObject
* obj
) const {
1449 MOZ_ASSERT(obj
->as
<ProxyObject
>().handler() ==
1450 &ScriptedProxyHandler::singleton
);
1451 uint32_t callConstruct
= obj
->as
<ProxyObject
>()
1452 .reservedSlot(IS_CALLCONSTRUCT_EXTRA
)
1454 return !!(callConstruct
& IS_CALLABLE
);
1457 bool ScriptedProxyHandler::isConstructor(JSObject
* obj
) const {
1458 MOZ_ASSERT(obj
->as
<ProxyObject
>().handler() ==
1459 &ScriptedProxyHandler::singleton
);
1460 uint32_t callConstruct
= obj
->as
<ProxyObject
>()
1461 .reservedSlot(IS_CALLCONSTRUCT_EXTRA
)
1463 return !!(callConstruct
& IS_CONSTRUCTOR
);
1466 const char ScriptedProxyHandler::family
= 0;
1467 const ScriptedProxyHandler
ScriptedProxyHandler::singleton
;
1469 // ES2021 rev c21b280a2c46e92decf3efeca9e9da35d5b9f622
1470 // Including the changes from: https://github.com/tc39/ecma262/pull/1814
1471 // 9.5.14 ProxyCreate.
1472 static bool ProxyCreate(JSContext
* cx
, CallArgs
& args
, const char* callerName
) {
1473 if (!args
.requireAtLeast(cx
, callerName
, 2)) {
1478 RootedObject
target(cx
,
1479 RequireObjectArg(cx
, "`target`", callerName
, args
[0]));
1485 RootedObject
handler(cx
,
1486 RequireObjectArg(cx
, "`handler`", callerName
, args
[1]));
1492 RootedValue
priv(cx
, ObjectValue(*target
));
1493 JSObject
* proxy_
= NewProxyObject(cx
, &ScriptedProxyHandler::singleton
, priv
,
1494 TaggedProto::LazyProto
);
1499 // Step 7 (reordered).
1500 Rooted
<ProxyObject
*> proxy(cx
, &proxy_
->as
<ProxyObject
>());
1501 proxy
->setReservedSlot(ScriptedProxyHandler::HANDLER_EXTRA
,
1502 ObjectValue(*handler
));
1506 target
->isCallable() ? ScriptedProxyHandler::IS_CALLABLE
: 0;
1507 uint32_t constructor
=
1508 target
->isConstructor() ? ScriptedProxyHandler::IS_CONSTRUCTOR
: 0;
1509 proxy
->setReservedSlot(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA
,
1510 PrivateUint32Value(callable
| constructor
));
1513 args
.rval().setObject(*proxy
);
1517 bool js::proxy(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1518 CallArgs args
= CallArgsFromVp(argc
, vp
);
1520 if (!ThrowIfNotConstructing(cx
, args
, "Proxy")) {
1524 return ProxyCreate(cx
, args
, "Proxy");
1527 static bool RevokeProxy(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1528 CallArgs args
= CallArgsFromVp(argc
, vp
);
1530 RootedFunction
func(cx
, &args
.callee().as
<JSFunction
>());
1531 RootedObject
p(cx
, func
->getExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT
)
1535 func
->setExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT
, NullValue());
1537 MOZ_ASSERT(p
->is
<ProxyObject
>());
1539 p
->as
<ProxyObject
>().setSameCompartmentPrivate(NullValue());
1540 p
->as
<ProxyObject
>().setReservedSlot(ScriptedProxyHandler::HANDLER_EXTRA
,
1544 args
.rval().setUndefined();
1548 bool js::proxy_revocable(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1549 CallArgs args
= CallArgsFromVp(argc
, vp
);
1551 if (!ProxyCreate(cx
, args
, "Proxy.revocable")) {
1555 RootedValue
proxyVal(cx
, args
.rval());
1556 MOZ_ASSERT(proxyVal
.toObject().is
<ProxyObject
>());
1558 RootedFunction
revoker(
1559 cx
, NewNativeFunction(cx
, RevokeProxy
, 0, nullptr,
1560 gc::AllocKind::FUNCTION_EXTENDED
, GenericObject
));
1565 revoker
->initExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT
, proxyVal
);
1567 RootedPlainObject
result(cx
, NewBuiltinClassInstance
<PlainObject
>(cx
));
1572 RootedValue
revokeVal(cx
, ObjectValue(*revoker
));
1573 if (!DefineDataProperty(cx
, result
, cx
->names().proxy
, proxyVal
) ||
1574 !DefineDataProperty(cx
, result
, cx
->names().revoke
, revokeVal
)) {
1578 args
.rval().setObject(*result
);