Bug 1885489 - Part 5: Add SnapshotIterator::readInt32(). r=iain
[gecko.git] / js / src / builtin / Object.cpp
blob3b875d9daeeec63c0cf960c27a025a21b3177a84
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 "builtin/Object.h"
8 #include "js/Object.h" // JS::GetBuiltinClass
10 #include "mozilla/Maybe.h"
11 #include "mozilla/Range.h"
12 #include "mozilla/RangedPtr.h"
14 #include <algorithm>
15 #include <string_view>
17 #include "jsapi.h"
19 #include "builtin/Eval.h"
20 #include "builtin/SelfHostingDefines.h"
21 #include "gc/GC.h"
22 #include "jit/InlinableNatives.h"
23 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
24 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
25 #include "js/PropertySpec.h"
26 #include "js/UniquePtr.h"
27 #include "util/Identifier.h" // js::IsIdentifier
28 #include "util/StringBuffer.h"
29 #include "util/Text.h"
30 #include "vm/BooleanObject.h"
31 #include "vm/DateObject.h"
32 #include "vm/EqualityOperations.h" // js::SameValue
33 #include "vm/ErrorObject.h"
34 #include "vm/Iteration.h"
35 #include "vm/JSContext.h"
36 #include "vm/NumberObject.h"
37 #include "vm/PlainObject.h" // js::PlainObject
38 #include "vm/RegExpObject.h"
39 #include "vm/StringObject.h"
40 #include "vm/StringType.h"
41 #include "vm/ToSource.h" // js::ValueToSource
42 #include "vm/Watchtower.h"
44 #ifdef ENABLE_RECORD_TUPLE
45 # include "builtin/RecordObject.h"
46 # include "builtin/TupleObject.h"
47 #endif
49 #include "vm/GeckoProfiler-inl.h"
50 #include "vm/JSObject-inl.h"
51 #include "vm/NativeObject-inl.h"
52 #include "vm/Shape-inl.h"
54 #ifdef FUZZING
55 # include "builtin/TestingFunctions.h"
56 #endif
58 using namespace js;
60 using mozilla::Maybe;
61 using mozilla::Range;
62 using mozilla::RangedPtr;
64 static PlainObject* CreateThis(JSContext* cx, HandleObject newTarget) {
65 RootedObject proto(cx);
66 if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) {
67 return nullptr;
70 gc::AllocKind allocKind = NewObjectGCKind();
72 if (proto) {
73 return NewPlainObjectWithProtoAndAllocKind(cx, proto, allocKind);
75 return NewPlainObjectWithAllocKind(cx, allocKind);
78 bool js::obj_construct(JSContext* cx, unsigned argc, Value* vp) {
79 CallArgs args = CallArgsFromVp(argc, vp);
81 JSObject* obj;
82 if (args.isConstructing() &&
83 (&args.newTarget().toObject() != &args.callee())) {
84 RootedObject newTarget(cx, &args.newTarget().toObject());
85 obj = CreateThis(cx, newTarget);
86 } else if (args.length() > 0 && !args[0].isNullOrUndefined()) {
87 obj = ToObject(cx, args[0]);
88 } else {
89 /* Make an object whether this was called with 'new' or not. */
90 gc::AllocKind allocKind = NewObjectGCKind();
91 obj = NewPlainObjectWithAllocKind(cx, allocKind);
93 if (!obj) {
94 return false;
97 args.rval().setObject(*obj);
98 return true;
101 /* ES5 15.2.4.7. */
102 bool js::obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp) {
103 CallArgs args = CallArgsFromVp(argc, vp);
105 HandleValue idValue = args.get(0);
107 // As an optimization, provide a fast path when rooting is not necessary and
108 // we can safely retrieve the attributes from the object's shape.
110 /* Steps 1-2. */
111 jsid id;
112 if (args.thisv().isObject() && idValue.isPrimitive() &&
113 PrimitiveValueToId<NoGC>(cx, idValue, &id)) {
114 JSObject* obj = &args.thisv().toObject();
116 /* Step 3. */
117 PropertyResult prop;
118 if (obj->is<NativeObject>() &&
119 NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id,
120 &prop)) {
121 /* Step 4. */
122 if (prop.isNotFound()) {
123 args.rval().setBoolean(false);
124 return true;
127 /* Step 5. */
128 JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
129 args.rval().setBoolean(attrs.enumerable());
130 return true;
134 /* Step 1. */
135 RootedId idRoot(cx);
136 if (!ToPropertyKey(cx, idValue, &idRoot)) {
137 return false;
140 /* Step 2. */
141 RootedObject obj(cx, ToObject(cx, args.thisv()));
142 if (!obj) {
143 return false;
146 /* Step 3. */
147 Rooted<Maybe<PropertyDescriptor>> desc(cx);
148 if (!GetOwnPropertyDescriptor(cx, obj, idRoot, &desc)) {
149 return false;
152 /* Step 4. */
153 if (desc.isNothing()) {
154 args.rval().setBoolean(false);
155 return true;
158 /* Step 5. */
159 args.rval().setBoolean(desc->enumerable());
160 return true;
163 static bool obj_toSource(JSContext* cx, unsigned argc, Value* vp) {
164 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object.prototype", "toSource");
165 CallArgs args = CallArgsFromVp(argc, vp);
167 AutoCheckRecursionLimit recursion(cx);
168 if (!recursion.check(cx)) {
169 return false;
172 RootedObject obj(cx, ToObject(cx, args.thisv()));
173 if (!obj) {
174 return false;
177 JSString* str = ObjectToSource(cx, obj);
178 if (!str) {
179 return false;
182 args.rval().setString(str);
183 return true;
186 template <typename CharT>
187 static bool Consume(RangedPtr<const CharT>& s, RangedPtr<const CharT> e,
188 std::string_view chars) {
189 MOZ_ASSERT(s <= e);
190 size_t len = chars.length();
191 if (e - s < len) {
192 return false;
194 if (!EqualChars(s.get(), chars.data(), len)) {
195 return false;
197 s += len;
198 return true;
201 template <typename CharT>
202 static bool ConsumeUntil(RangedPtr<const CharT>& s, RangedPtr<const CharT> e,
203 char16_t ch) {
204 MOZ_ASSERT(s <= e);
205 const CharT* result = js_strchr_limit(s.get(), ch, e.get());
206 if (!result) {
207 return false;
209 s += result - s.get();
210 MOZ_ASSERT(*s == ch);
211 return true;
214 template <typename CharT>
215 static void ConsumeSpaces(RangedPtr<const CharT>& s, RangedPtr<const CharT> e) {
216 while (s < e && *s == ' ') {
217 s++;
222 * Given a function source string, return the offset and length of the part
223 * between '(function $name' and ')'.
225 template <typename CharT>
226 static bool ArgsAndBodySubstring(Range<const CharT> chars, size_t* outOffset,
227 size_t* outLen) {
228 const RangedPtr<const CharT> start = chars.begin();
229 RangedPtr<const CharT> s = start;
230 RangedPtr<const CharT> e = chars.end();
232 if (s == e) {
233 return false;
236 // Remove enclosing parentheses.
237 if (*s == '(' && *(e - 1) == ')') {
238 s++;
239 e--;
242 // Support the following cases, with spaces between tokens:
244 // -+---------+-+------------+-+-----+-+- [ - <any> - ] - ( -+-
245 // | | | | | | | |
246 // +- async -+ +- function -+ +- * -+ +- <any> - ( ---------+
247 // | |
248 // +- get ------+
249 // | |
250 // +- set ------+
252 // This accepts some invalid syntax, but we don't care, since it's only
253 // used by the non-standard toSource, and we're doing a best-effort attempt
254 // here.
256 (void)Consume(s, e, "async");
257 ConsumeSpaces(s, e);
258 (void)(Consume(s, e, "function") || Consume(s, e, "get") ||
259 Consume(s, e, "set"));
260 ConsumeSpaces(s, e);
261 (void)Consume(s, e, "*");
262 ConsumeSpaces(s, e);
264 // Jump over the function's name.
265 if (Consume(s, e, "[")) {
266 if (!ConsumeUntil(s, e, ']')) {
267 return false;
269 s++; // Skip ']'.
270 ConsumeSpaces(s, e);
271 if (s >= e || *s != '(') {
272 return false;
274 } else {
275 if (!ConsumeUntil(s, e, '(')) {
276 return false;
280 MOZ_ASSERT(*s == '(');
282 *outOffset = s - start;
283 *outLen = e - s;
284 MOZ_ASSERT(*outOffset + *outLen <= chars.length());
285 return true;
288 enum class PropertyKind { Getter, Setter, Method, Normal };
290 JSString* js::ObjectToSource(JSContext* cx, HandleObject obj) {
291 /* If outermost, we need parentheses to be an expression, not a block. */
292 bool outermost = cx->cycleDetectorVector().empty();
294 AutoCycleDetector detector(cx, obj);
295 if (!detector.init()) {
296 return nullptr;
298 if (detector.foundCycle()) {
299 return NewStringCopyZ<CanGC>(cx, "{}");
302 JSStringBuilder buf(cx);
303 if (outermost && !buf.append('(')) {
304 return nullptr;
306 if (!buf.append('{')) {
307 return nullptr;
310 RootedIdVector idv(cx);
311 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv)) {
312 return nullptr;
315 #ifdef ENABLE_RECORD_TUPLE
316 if (IsExtendedPrimitiveWrapper(*obj)) {
317 if (obj->is<TupleObject>()) {
318 Rooted<TupleType*> tup(cx, &obj->as<TupleObject>().unbox());
319 return TupleToSource(cx, tup);
321 MOZ_ASSERT(obj->is<RecordObject>());
322 return RecordToSource(cx, obj->as<RecordObject>().unbox());
324 #endif
326 bool comma = false;
328 auto AddProperty = [cx, &comma, &buf](HandleId id, HandleValue val,
329 PropertyKind kind) -> bool {
330 /* Convert id to a string. */
331 RootedString idstr(cx);
332 if (id.isSymbol()) {
333 RootedValue v(cx, SymbolValue(id.toSymbol()));
334 idstr = ValueToSource(cx, v);
335 if (!idstr) {
336 return false;
338 } else {
339 RootedValue idv(cx, IdToValue(id));
340 idstr = ToString<CanGC>(cx, idv);
341 if (!idstr) {
342 return false;
346 * If id is a string that's not an identifier, or if it's a
347 * negative integer, then it must be quoted.
349 if (id.isAtom() ? !IsIdentifier(id.toAtom()) : id.toInt() < 0) {
350 UniqueChars quotedId = QuoteString(cx, idstr, '\'');
351 if (!quotedId) {
352 return false;
354 idstr = NewStringCopyZ<CanGC>(cx, quotedId.get());
355 if (!idstr) {
356 return false;
361 RootedString valsource(cx, ValueToSource(cx, val));
362 if (!valsource) {
363 return false;
366 Rooted<JSLinearString*> valstr(cx, valsource->ensureLinear(cx));
367 if (!valstr) {
368 return false;
371 if (comma && !buf.append(", ")) {
372 return false;
374 comma = true;
376 size_t voffset, vlength;
378 // Methods and accessors can return exact syntax of source, that fits
379 // into property without adding property name or "get"/"set" prefix.
380 // Use the exact syntax when the following conditions are met:
382 // * It's a function object
383 // (exclude proxies)
384 // * Function's kind and property's kind are same
385 // (this can be false for dynamically defined properties)
386 // * Function has explicit name
387 // (this can be false for computed property and dynamically defined
388 // properties)
389 // * Function's name and property's name are same
390 // (this can be false for dynamically defined properties)
391 if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
392 kind == PropertyKind::Method) {
393 RootedFunction fun(cx);
394 if (val.toObject().is<JSFunction>()) {
395 fun = &val.toObject().as<JSFunction>();
396 // Method's case should be checked on caller.
397 if (((fun->isGetter() && kind == PropertyKind::Getter &&
398 !fun->isAccessorWithLazyName()) ||
399 (fun->isSetter() && kind == PropertyKind::Setter &&
400 !fun->isAccessorWithLazyName()) ||
401 kind == PropertyKind::Method) &&
402 fun->fullExplicitName()) {
403 bool result;
404 if (!EqualStrings(cx, fun->fullExplicitName(), idstr, &result)) {
405 return false;
408 if (result) {
409 if (!buf.append(valstr)) {
410 return false;
412 return true;
418 // When falling back try to generate a better string
419 // representation by skipping the prelude, and also removing
420 // the enclosing parentheses.
421 bool success;
422 JS::AutoCheckCannotGC nogc;
423 if (valstr->hasLatin1Chars()) {
424 success = ArgsAndBodySubstring(valstr->latin1Range(nogc), &voffset,
425 &vlength);
426 } else {
427 success = ArgsAndBodySubstring(valstr->twoByteRange(nogc), &voffset,
428 &vlength);
430 if (!success) {
431 kind = PropertyKind::Normal;
435 if (kind == PropertyKind::Getter) {
436 if (!buf.append("get ")) {
437 return false;
439 } else if (kind == PropertyKind::Setter) {
440 if (!buf.append("set ")) {
441 return false;
443 } else if (kind == PropertyKind::Method && fun) {
444 if (fun->isAsync()) {
445 if (!buf.append("async ")) {
446 return false;
450 if (fun->isGenerator()) {
451 if (!buf.append('*')) {
452 return false;
458 bool needsBracket = id.isSymbol();
459 if (needsBracket && !buf.append('[')) {
460 return false;
462 if (!buf.append(idstr)) {
463 return false;
465 if (needsBracket && !buf.append(']')) {
466 return false;
469 if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
470 kind == PropertyKind::Method) {
471 if (!buf.appendSubstring(valstr, voffset, vlength)) {
472 return false;
474 } else {
475 if (!buf.append(':')) {
476 return false;
478 if (!buf.append(valstr)) {
479 return false;
482 return true;
485 RootedId id(cx);
486 Rooted<Maybe<PropertyDescriptor>> desc(cx);
487 RootedValue val(cx);
488 for (size_t i = 0; i < idv.length(); ++i) {
489 id = idv[i];
490 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) {
491 return nullptr;
494 if (desc.isNothing()) {
495 continue;
498 if (desc->isAccessorDescriptor()) {
499 if (desc->hasGetter() && desc->getter()) {
500 val.setObject(*desc->getter());
501 if (!AddProperty(id, val, PropertyKind::Getter)) {
502 return nullptr;
505 if (desc->hasSetter() && desc->setter()) {
506 val.setObject(*desc->setter());
507 if (!AddProperty(id, val, PropertyKind::Setter)) {
508 return nullptr;
511 continue;
514 val.set(desc->value());
516 JSFunction* fun = nullptr;
517 if (IsFunctionObject(val, &fun) && fun->isMethod()) {
518 if (!AddProperty(id, val, PropertyKind::Method)) {
519 return nullptr;
521 continue;
524 if (!AddProperty(id, val, PropertyKind::Normal)) {
525 return nullptr;
529 if (!buf.append('}')) {
530 return nullptr;
532 if (outermost && !buf.append(')')) {
533 return nullptr;
536 return buf.finishString();
539 static JSString* GetBuiltinTagSlow(JSContext* cx, HandleObject obj) {
540 // Step 4.
541 bool isArray;
542 if (!IsArray(cx, obj, &isArray)) {
543 return nullptr;
546 // Step 5.
547 if (isArray) {
548 return cx->names().object_Array_;
551 // Steps 6-14.
552 ESClass cls;
553 if (!JS::GetBuiltinClass(cx, obj, &cls)) {
554 return nullptr;
557 switch (cls) {
558 case ESClass::String:
559 return cx->names().object_String_;
560 case ESClass::Arguments:
561 return cx->names().object_Arguments_;
562 case ESClass::Error:
563 return cx->names().object_Error_;
564 case ESClass::Boolean:
565 return cx->names().object_Boolean_;
566 case ESClass::Number:
567 return cx->names().object_Number_;
568 case ESClass::Date:
569 return cx->names().object_Date_;
570 case ESClass::RegExp:
571 return cx->names().object_RegExp_;
572 default:
573 if (obj->isCallable()) {
574 // Non-standard: Prevent <object> from showing up as Function.
575 JSObject* unwrapped = CheckedUnwrapDynamic(obj, cx);
576 if (!unwrapped || !unwrapped->getClass()->isDOMClass()) {
577 return cx->names().object_Function_;
580 return cx->names().object_Object_;
584 static MOZ_ALWAYS_INLINE JSString* GetBuiltinTagFast(JSObject* obj,
585 JSContext* cx) {
586 const JSClass* clasp = obj->getClass();
587 MOZ_ASSERT(!clasp->isProxyObject());
589 // Optimize the non-proxy case to bypass GetBuiltinClass.
590 if (clasp == &PlainObject::class_) {
591 // This case is by far the most common so we handle it first.
592 return cx->names().object_Object_;
595 if (clasp == &ArrayObject::class_) {
596 return cx->names().object_Array_;
599 if (clasp->isJSFunction()) {
600 return cx->names().object_Function_;
603 if (clasp == &StringObject::class_) {
604 return cx->names().object_String_;
607 if (clasp == &NumberObject::class_) {
608 return cx->names().object_Number_;
611 if (clasp == &BooleanObject::class_) {
612 return cx->names().object_Boolean_;
615 if (clasp == &DateObject::class_) {
616 return cx->names().object_Date_;
619 if (clasp == &RegExpObject::class_) {
620 return cx->names().object_RegExp_;
623 if (obj->is<ArgumentsObject>()) {
624 return cx->names().object_Arguments_;
627 if (obj->is<ErrorObject>()) {
628 return cx->names().object_Error_;
631 if (obj->isCallable() && !obj->getClass()->isDOMClass()) {
632 // Non-standard: Prevent <object> from showing up as Function.
633 return cx->names().object_Function_;
636 return cx->names().object_Object_;
639 // For primitive values we try to avoid allocating the object if we can
640 // determine that the prototype it would use does not define Symbol.toStringTag.
641 static JSAtom* MaybeObjectToStringPrimitive(JSContext* cx, const Value& v) {
642 JSProtoKey protoKey = js::PrimitiveToProtoKey(cx, v);
644 // If prototype doesn't exist yet, just fall through.
645 JSObject* proto = cx->global()->maybeGetPrototype(protoKey);
646 if (!proto) {
647 return nullptr;
650 // If determining this may have side-effects, we must instead create the
651 // object normally since it is the receiver while looking up
652 // Symbol.toStringTag.
653 if (MaybeHasInterestingSymbolProperty(
654 cx, proto, cx->wellKnownSymbols().toStringTag, nullptr)) {
655 return nullptr;
658 // Return the direct result.
659 switch (protoKey) {
660 case JSProto_String:
661 return cx->names().object_String_;
662 case JSProto_Number:
663 return cx->names().object_Number_;
664 case JSProto_Boolean:
665 return cx->names().object_Boolean_;
666 case JSProto_Symbol:
667 return cx->names().object_Symbol_;
668 case JSProto_BigInt:
669 return cx->names().object_BigInt_;
670 default:
671 break;
674 return nullptr;
677 // ES6 19.1.3.6
678 bool js::obj_toString(JSContext* cx, unsigned argc, Value* vp) {
679 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object.prototype", "toString");
680 CallArgs args = CallArgsFromVp(argc, vp);
681 RootedObject obj(cx);
683 if (args.thisv().isPrimitive()) {
684 // Step 1.
685 if (args.thisv().isUndefined()) {
686 args.rval().setString(cx->names().object_Undefined_);
687 return true;
690 // Step 2.
691 if (args.thisv().isNull()) {
692 args.rval().setString(cx->names().object_Null_);
693 return true;
696 // Try fast-path for primitives. This is unusual but we encounter code like
697 // this in the wild.
698 JSAtom* result = MaybeObjectToStringPrimitive(cx, args.thisv());
699 if (result) {
700 args.rval().setString(result);
701 return true;
704 // Step 3.
705 obj = ToObject(cx, args.thisv());
706 if (!obj) {
707 return false;
709 } else {
710 obj = &args.thisv().toObject();
713 // When |obj| is a non-proxy object, compute |builtinTag| only when needed.
714 RootedString builtinTag(cx);
715 if (MOZ_UNLIKELY(obj->is<ProxyObject>())) {
716 builtinTag = GetBuiltinTagSlow(cx, obj);
717 if (!builtinTag) {
718 return false;
722 // Step 15.
723 RootedValue tag(cx);
724 if (!GetInterestingSymbolProperty(cx, obj, cx->wellKnownSymbols().toStringTag,
725 &tag)) {
726 return false;
729 // Step 16.
730 if (!tag.isString()) {
731 if (!builtinTag) {
732 builtinTag = GetBuiltinTagFast(obj, cx);
733 #ifdef DEBUG
734 // Assert this fast path is correct and matches BuiltinTagSlow.
735 JSString* builtinTagSlow = GetBuiltinTagSlow(cx, obj);
736 if (!builtinTagSlow) {
737 return false;
739 MOZ_ASSERT(builtinTagSlow == builtinTag);
740 #endif
743 args.rval().setString(builtinTag);
744 return true;
747 // Step 17.
748 StringBuffer sb(cx);
749 if (!sb.append("[object ") || !sb.append(tag.toString()) || !sb.append(']')) {
750 return false;
753 JSString* str = sb.finishAtom();
754 if (!str) {
755 return false;
758 args.rval().setString(str);
759 return true;
762 JSString* js::ObjectClassToString(JSContext* cx, JSObject* obj) {
763 AutoUnsafeCallWithABI unsafe;
765 if (MaybeHasInterestingSymbolProperty(cx, obj,
766 cx->wellKnownSymbols().toStringTag)) {
767 return nullptr;
769 return GetBuiltinTagFast(obj, cx);
772 static bool obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp) {
773 CallArgs args = CallArgsFromVp(argc, vp);
775 if (!args.requireAtLeast(cx, "Object.setPrototypeOf", 2)) {
776 return false;
779 /* Step 1-2. */
780 if (args[0].isNullOrUndefined()) {
781 JS_ReportErrorNumberASCII(
782 cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
783 args[0].isNull() ? "null" : "undefined", "object");
784 return false;
787 /* Step 3. */
788 if (!args[1].isObjectOrNull()) {
789 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
790 JSMSG_NOT_EXPECTED_TYPE, "Object.setPrototypeOf",
791 "an object or null",
792 InformalValueTypeName(args[1]));
793 return false;
796 /* Step 4. */
797 if (!args[0].isObject()) {
798 args.rval().set(args[0]);
799 return true;
802 /* Step 5-7. */
803 RootedObject obj(cx, &args[0].toObject());
804 RootedObject newProto(cx, args[1].toObjectOrNull());
805 if (!SetPrototype(cx, obj, newProto)) {
806 return false;
809 /* Step 8. */
810 args.rval().set(args[0]);
811 return true;
814 static bool PropertyIsEnumerable(JSContext* cx, HandleObject obj, HandleId id,
815 bool* enumerable) {
816 PropertyResult prop;
817 if (obj->is<NativeObject>() &&
818 NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &prop)) {
819 if (prop.isNotFound()) {
820 *enumerable = false;
821 return true;
824 JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
825 *enumerable = attrs.enumerable();
826 return true;
829 Rooted<Maybe<PropertyDescriptor>> desc(cx);
830 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) {
831 return false;
834 *enumerable = desc.isSome() && desc->enumerable();
835 return true;
838 // Returns true if properties not named "__proto__" can be added to |obj|
839 // with a fast path that doesn't check any properties on the prototype chain.
840 static bool CanAddNewPropertyExcludingProtoFast(PlainObject* obj) {
841 if (!obj->isExtensible() || obj->isUsedAsPrototype()) {
842 return false;
845 // Don't fastpath assign if we're watching for property modification.
846 if (Watchtower::watchesPropertyModification(obj)) {
847 return false;
850 // Ensure the object has no non-writable properties or getters/setters.
851 // For now only support PlainObjects so that we don't have to worry about
852 // resolve hooks and other JSClass hooks.
853 while (true) {
854 if (obj->hasNonWritableOrAccessorPropExclProto()) {
855 return false;
858 JSObject* proto = obj->staticPrototype();
859 if (!proto) {
860 return true;
862 if (!proto->is<PlainObject>()) {
863 return false;
865 obj = &proto->as<PlainObject>();
869 #ifdef DEBUG
870 void PlainObjectAssignCache::assertValid() const {
871 MOZ_ASSERT(emptyToShape_);
872 MOZ_ASSERT(fromShape_);
873 MOZ_ASSERT(newToShape_);
875 MOZ_ASSERT(emptyToShape_->propMapLength() == 0);
876 MOZ_ASSERT(emptyToShape_->base() == newToShape_->base());
877 MOZ_ASSERT(emptyToShape_->numFixedSlots() == newToShape_->numFixedSlots());
879 MOZ_ASSERT(emptyToShape_->getObjectClass() == &PlainObject::class_);
880 MOZ_ASSERT(fromShape_->getObjectClass() == &PlainObject::class_);
882 MOZ_ASSERT(fromShape_->slotSpan() == newToShape_->slotSpan());
884 #endif
886 [[nodiscard]] static bool TryAssignPlain(JSContext* cx, HandleObject to,
887 HandleObject from, bool* optimized) {
888 // Object.assign is used with PlainObjects most of the time. This is a fast
889 // path to optimize that case. This lets us avoid checks that are only
890 // relevant for other JSClasses.
892 MOZ_ASSERT(*optimized == false);
894 if (!from->is<PlainObject>() || !to->is<PlainObject>()) {
895 return true;
898 // Don't use the fast path if |from| may have extra indexed properties.
899 Handle<PlainObject*> fromPlain = from.as<PlainObject>();
900 if (fromPlain->getDenseInitializedLength() > 0 || fromPlain->isIndexed()) {
901 return true;
903 MOZ_ASSERT(!fromPlain->getClass()->getNewEnumerate());
904 MOZ_ASSERT(!fromPlain->getClass()->getEnumerate());
906 // Empty |from| objects are common, so check for this first.
907 if (fromPlain->empty()) {
908 *optimized = true;
909 return true;
912 Handle<PlainObject*> toPlain = to.as<PlainObject>();
913 if (!CanAddNewPropertyExcludingProtoFast(toPlain)) {
914 return true;
917 const bool toWasEmpty = toPlain->empty();
918 if (toWasEmpty) {
919 const PlainObjectAssignCache& cache = cx->realm()->plainObjectAssignCache;
920 SharedShape* newShape = cache.lookup(toPlain->shape(), fromPlain->shape());
921 if (newShape) {
922 *optimized = true;
923 uint32_t oldSpan = 0;
924 uint32_t newSpan = newShape->slotSpan();
925 if (!toPlain->setShapeAndAddNewSlots(cx, newShape, oldSpan, newSpan)) {
926 return false;
928 MOZ_ASSERT(fromPlain->slotSpan() == newSpan);
929 for (size_t i = 0; i < newSpan; i++) {
930 toPlain->initSlot(i, fromPlain->getSlot(i));
932 return true;
936 // Get a list of all enumerable |from| properties.
938 Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx));
940 #ifdef DEBUG
941 Rooted<Shape*> fromShape(cx, fromPlain->shape());
942 #endif
944 bool hasPropsWithNonDefaultAttrs = false;
945 bool hasOnlyEnumerableProps = true;
946 for (ShapePropertyIter<NoGC> iter(fromPlain->shape()); !iter.done(); iter++) {
947 // Symbol properties need to be assigned last. For now fall back to the
948 // slow path if we see a symbol property.
949 jsid id = iter->key();
950 if (MOZ_UNLIKELY(id.isSymbol())) {
951 return true;
953 // __proto__ is not supported by CanAddNewPropertyExcludingProtoFast.
954 if (MOZ_UNLIKELY(id.isAtom(cx->names().proto_))) {
955 return true;
957 if (MOZ_UNLIKELY(!iter->isDataProperty())) {
958 return true;
960 if (iter->flags() != PropertyFlags::defaultDataPropFlags) {
961 hasPropsWithNonDefaultAttrs = true;
962 if (!iter->enumerable()) {
963 hasOnlyEnumerableProps = false;
964 continue;
967 if (MOZ_UNLIKELY(!props.append(*iter))) {
968 return false;
972 MOZ_ASSERT_IF(hasOnlyEnumerableProps && !fromPlain->inDictionaryMode(),
973 fromPlain->slotSpan() == props.length());
975 *optimized = true;
977 Rooted<Shape*> origToShape(cx, toPlain->shape());
979 // If the |to| object has no properties and the |from| object only has plain
980 // enumerable/writable/configurable data properties, try to use its shape or
981 // property map.
982 if (toWasEmpty && !hasPropsWithNonDefaultAttrs) {
983 CanReuseShape canReuse =
984 toPlain->canReuseShapeForNewProperties(fromPlain->shape());
985 if (canReuse != CanReuseShape::NoReuse) {
986 SharedShape* newShape;
987 if (canReuse == CanReuseShape::CanReuseShape) {
988 newShape = fromPlain->sharedShape();
989 } else {
990 // Get a shape with fromPlain's PropMap and ObjectFlags (because we need
991 // the HasEnumerable flag checked in canReuseShapeForNewProperties) and
992 // the other fields (BaseShape, numFixedSlots) unchanged.
993 MOZ_ASSERT(canReuse == CanReuseShape::CanReusePropMap);
994 ObjectFlags objectFlags = fromPlain->sharedShape()->objectFlags();
995 Rooted<SharedPropMap*> map(cx, fromPlain->sharedShape()->propMap());
996 uint32_t mapLength = fromPlain->sharedShape()->propMapLength();
997 BaseShape* base = toPlain->sharedShape()->base();
998 uint32_t nfixed = toPlain->sharedShape()->numFixedSlots();
999 newShape = SharedShape::getPropMapShape(cx, base, nfixed, map,
1000 mapLength, objectFlags);
1001 if (!newShape) {
1002 return false;
1005 uint32_t oldSpan = 0;
1006 uint32_t newSpan = props.length();
1007 if (!toPlain->setShapeAndAddNewSlots(cx, newShape, oldSpan, newSpan)) {
1008 return false;
1010 MOZ_ASSERT(fromPlain->slotSpan() == newSpan);
1011 MOZ_ASSERT(toPlain->slotSpan() == newSpan);
1012 for (size_t i = 0; i < newSpan; i++) {
1013 toPlain->initSlot(i, fromPlain->getSlot(i));
1015 PlainObjectAssignCache& cache = cx->realm()->plainObjectAssignCache;
1016 cache.fill(&origToShape->asShared(), fromPlain->sharedShape(), newShape);
1017 return true;
1021 RootedValue propValue(cx);
1022 RootedId nextKey(cx);
1024 for (size_t i = props.length(); i > 0; i--) {
1025 // Assert |from| still has the same properties.
1026 MOZ_ASSERT(fromPlain->shape() == fromShape);
1028 PropertyInfoWithKey fromProp = props[i - 1];
1029 MOZ_ASSERT(fromProp.isDataProperty());
1030 MOZ_ASSERT(fromProp.enumerable());
1032 nextKey = fromProp.key();
1033 propValue = fromPlain->getSlot(fromProp.slot());
1035 if (!toWasEmpty) {
1036 if (Maybe<PropertyInfo> toProp = toPlain->lookup(cx, nextKey)) {
1037 MOZ_ASSERT(toProp->isDataProperty());
1038 MOZ_ASSERT(toProp->writable());
1039 toPlain->setSlot(toProp->slot(), propValue);
1040 continue;
1044 MOZ_ASSERT(!toPlain->containsPure(nextKey));
1046 if (!AddDataPropertyToPlainObject(cx, toPlain, nextKey, propValue)) {
1047 return false;
1051 // Note: dictionary shapes are not supported by the cache because they have a
1052 // more complicated slot layout (the slot numbers may not match the property
1053 // definition order and the slots may contain holes).
1054 if (toWasEmpty && hasOnlyEnumerableProps && !fromPlain->inDictionaryMode() &&
1055 !toPlain->inDictionaryMode()) {
1056 PlainObjectAssignCache& cache = cx->realm()->plainObjectAssignCache;
1057 cache.fill(&origToShape->asShared(), fromPlain->sharedShape(),
1058 toPlain->sharedShape());
1061 return true;
1064 static bool TryAssignNative(JSContext* cx, HandleObject to, HandleObject from,
1065 bool* optimized) {
1066 MOZ_ASSERT(*optimized == false);
1068 if (!from->is<NativeObject>() || !to->is<NativeObject>()) {
1069 return true;
1072 // Don't use the fast path if |from| may have extra indexed or lazy
1073 // properties.
1074 NativeObject* fromNative = &from->as<NativeObject>();
1075 if (fromNative->getDenseInitializedLength() > 0 || fromNative->isIndexed() ||
1076 fromNative->is<TypedArrayObject>() ||
1077 fromNative->getClass()->getNewEnumerate() ||
1078 fromNative->getClass()->getEnumerate()) {
1079 return true;
1082 // Get a list of |from| properties. As long as from->shape() == fromShape
1083 // we can use this to speed up both the enumerability check and the GetProp.
1085 Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx));
1087 Rooted<NativeShape*> fromShape(cx, fromNative->shape());
1088 for (ShapePropertyIter<NoGC> iter(fromShape); !iter.done(); iter++) {
1089 // Symbol properties need to be assigned last. For now fall back to the
1090 // slow path if we see a symbol property.
1091 if (MOZ_UNLIKELY(iter->key().isSymbol())) {
1092 return true;
1094 if (MOZ_UNLIKELY(!props.append(*iter))) {
1095 return false;
1099 *optimized = true;
1101 RootedValue propValue(cx);
1102 RootedId nextKey(cx);
1103 RootedValue toReceiver(cx, ObjectValue(*to));
1105 for (size_t i = props.length(); i > 0; i--) {
1106 PropertyInfoWithKey prop = props[i - 1];
1107 nextKey = prop.key();
1109 // If |from| still has the same shape, it must still be a NativeObject with
1110 // the properties in |props|.
1111 if (MOZ_LIKELY(from->shape() == fromShape && prop.isDataProperty())) {
1112 if (!prop.enumerable()) {
1113 continue;
1115 propValue = from->as<NativeObject>().getSlot(prop.slot());
1116 } else {
1117 // |from| changed shape or the property is not a data property, so
1118 // we have to do the slower enumerability check and GetProp.
1119 bool enumerable;
1120 if (!PropertyIsEnumerable(cx, from, nextKey, &enumerable)) {
1121 return false;
1123 if (!enumerable) {
1124 continue;
1126 if (!GetProperty(cx, from, from, nextKey, &propValue)) {
1127 return false;
1131 ObjectOpResult result;
1132 if (MOZ_UNLIKELY(
1133 !SetProperty(cx, to, nextKey, propValue, toReceiver, result))) {
1134 return false;
1136 if (MOZ_UNLIKELY(!result.checkStrict(cx, to, nextKey))) {
1137 return false;
1141 return true;
1144 static bool AssignSlow(JSContext* cx, HandleObject to, HandleObject from) {
1145 // Step 4.b.ii.
1146 RootedIdVector keys(cx);
1147 if (!GetPropertyKeys(
1148 cx, from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys)) {
1149 return false;
1152 // Step 4.c.
1153 RootedId nextKey(cx);
1154 RootedValue propValue(cx);
1155 for (size_t i = 0, len = keys.length(); i < len; i++) {
1156 nextKey = keys[i];
1158 // Step 4.c.i.
1159 bool enumerable;
1160 if (MOZ_UNLIKELY(!PropertyIsEnumerable(cx, from, nextKey, &enumerable))) {
1161 return false;
1163 if (!enumerable) {
1164 continue;
1167 // Step 4.c.ii.1.
1168 if (MOZ_UNLIKELY(!GetProperty(cx, from, from, nextKey, &propValue))) {
1169 return false;
1172 // Step 4.c.ii.2.
1173 if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue))) {
1174 return false;
1178 return true;
1181 JS_PUBLIC_API bool JS_AssignObject(JSContext* cx, JS::HandleObject target,
1182 JS::HandleObject src) {
1183 bool optimized = false;
1185 if (!TryAssignPlain(cx, target, src, &optimized)) {
1186 return false;
1188 if (optimized) {
1189 return true;
1192 if (!TryAssignNative(cx, target, src, &optimized)) {
1193 return false;
1195 if (optimized) {
1196 return true;
1199 return AssignSlow(cx, target, src);
1202 // ES2018 draft rev 48ad2688d8f964da3ea8c11163ef20eb126fb8a4
1203 // 19.1.2.1 Object.assign(target, ...sources)
1204 static bool obj_assign(JSContext* cx, unsigned argc, Value* vp) {
1205 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "assign");
1206 CallArgs args = CallArgsFromVp(argc, vp);
1208 // Step 1.
1209 RootedObject to(cx, ToObject(cx, args.get(0)));
1210 if (!to) {
1211 return false;
1214 // Note: step 2 is implicit. If there are 0 arguments, ToObject throws. If
1215 // there's 1 argument, the loop below is a no-op.
1217 // Step 4.
1218 RootedObject from(cx);
1219 for (size_t i = 1; i < args.length(); i++) {
1220 // Step 4.a.
1221 if (args[i].isNullOrUndefined()) {
1222 continue;
1225 // Step 4.b.i.
1226 from = ToObject(cx, args[i]);
1227 if (!from) {
1228 return false;
1231 // Steps 4.b.ii, 4.c.
1232 if (!JS_AssignObject(cx, to, from)) {
1233 return false;
1237 // Step 5.
1238 args.rval().setObject(*to);
1239 return true;
1242 /* ES5 15.2.4.6. */
1243 bool js::obj_isPrototypeOf(JSContext* cx, unsigned argc, Value* vp) {
1244 CallArgs args = CallArgsFromVp(argc, vp);
1246 /* Step 1. */
1247 if (args.length() < 1 || !args[0].isObject()) {
1248 args.rval().setBoolean(false);
1249 return true;
1252 /* Step 2. */
1253 RootedObject obj(cx, ToObject(cx, args.thisv()));
1254 if (!obj) {
1255 return false;
1258 /* Step 3. */
1259 bool isPrototype;
1260 if (!IsPrototypeOf(cx, obj, &args[0].toObject(), &isPrototype)) {
1261 return false;
1263 args.rval().setBoolean(isPrototype);
1264 return true;
1267 PlainObject* js::ObjectCreateImpl(JSContext* cx, HandleObject proto,
1268 NewObjectKind newKind) {
1269 // Give the new object a small number of fixed slots, like we do for empty
1270 // object literals ({}).
1271 gc::AllocKind allocKind = NewObjectGCKind();
1272 return NewPlainObjectWithProtoAndAllocKind(cx, proto, allocKind, newKind);
1275 PlainObject* js::ObjectCreateWithTemplate(JSContext* cx,
1276 Handle<PlainObject*> templateObj) {
1277 RootedObject proto(cx, templateObj->staticPrototype());
1278 return ObjectCreateImpl(cx, proto, GenericObject);
1281 // ES 2017 draft 19.1.2.3.1
1282 static bool ObjectDefineProperties(JSContext* cx, HandleObject obj,
1283 HandleValue properties,
1284 bool* failedOnWindowProxy) {
1285 // Step 1. implicit
1286 // Step 2.
1287 RootedObject props(cx, ToObject(cx, properties));
1288 if (!props) {
1289 return false;
1292 // Step 3.
1293 RootedIdVector keys(cx);
1294 if (!GetPropertyKeys(
1295 cx, props, JSITER_OWNONLY | JSITER_SYMBOLS | JSITER_HIDDEN, &keys)) {
1296 return false;
1299 RootedId nextKey(cx);
1300 Rooted<Maybe<PropertyDescriptor>> keyDesc(cx);
1301 Rooted<PropertyDescriptor> desc(cx);
1302 RootedValue descObj(cx);
1304 // Step 4.
1305 Rooted<PropertyDescriptorVector> descriptors(cx,
1306 PropertyDescriptorVector(cx));
1307 RootedIdVector descriptorKeys(cx);
1309 // Step 5.
1310 for (size_t i = 0, len = keys.length(); i < len; i++) {
1311 nextKey = keys[i];
1313 // Step 5.a.
1314 if (!GetOwnPropertyDescriptor(cx, props, nextKey, &keyDesc)) {
1315 return false;
1318 // Step 5.b.
1319 if (keyDesc.isSome() && keyDesc->enumerable()) {
1320 if (!GetProperty(cx, props, props, nextKey, &descObj) ||
1321 !ToPropertyDescriptor(cx, descObj, true, &desc) ||
1322 !descriptors.append(desc) || !descriptorKeys.append(nextKey)) {
1323 return false;
1328 // Step 6.
1329 *failedOnWindowProxy = false;
1330 for (size_t i = 0, len = descriptors.length(); i < len; i++) {
1331 ObjectOpResult result;
1332 if (!DefineProperty(cx, obj, descriptorKeys[i], descriptors[i], result)) {
1333 return false;
1336 if (!result.ok()) {
1337 if (result.failureCode() == JSMSG_CANT_DEFINE_WINDOW_NC) {
1338 *failedOnWindowProxy = true;
1339 } else if (!result.checkStrict(cx, obj, descriptorKeys[i])) {
1340 return false;
1345 return true;
1348 // ES6 draft rev34 (2015/02/20) 19.1.2.2 Object.create(O [, Properties])
1349 bool js::obj_create(JSContext* cx, unsigned argc, Value* vp) {
1350 CallArgs args = CallArgsFromVp(argc, vp);
1352 // Step 1.
1353 if (!args.requireAtLeast(cx, "Object.create", 1)) {
1354 return false;
1357 if (!args[0].isObjectOrNull()) {
1358 UniqueChars bytes =
1359 DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args[0], nullptr);
1360 if (!bytes) {
1361 return false;
1364 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1365 JSMSG_UNEXPECTED_TYPE, bytes.get(),
1366 "not an object or null");
1367 return false;
1370 // Step 2.
1371 RootedObject proto(cx, args[0].toObjectOrNull());
1372 Rooted<PlainObject*> obj(cx, ObjectCreateImpl(cx, proto));
1373 if (!obj) {
1374 return false;
1377 // Step 3.
1378 if (args.hasDefined(1)) {
1379 // we can't ever end up with failures to define on a WindowProxy
1380 // here, because "obj" is never a WindowProxy.
1381 bool failedOnWindowProxy = false;
1382 if (!ObjectDefineProperties(cx, obj, args[1], &failedOnWindowProxy)) {
1383 return false;
1385 MOZ_ASSERT(!failedOnWindowProxy, "How did we get a WindowProxy here?");
1388 // Step 4.
1389 args.rval().setObject(*obj);
1390 return true;
1393 // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
1394 // 6.2.4.4 FromPropertyDescriptor ( Desc )
1395 static bool FromPropertyDescriptorToArray(
1396 JSContext* cx, Handle<Maybe<PropertyDescriptor>> desc,
1397 MutableHandleValue vp) {
1398 // Step 1.
1399 if (desc.isNothing()) {
1400 vp.setUndefined();
1401 return true;
1404 // Steps 2-11.
1405 // Retrieve all property descriptor fields and place them into the result
1406 // array. The actual return object is created in self-hosted code for
1407 // performance reasons.
1409 int32_t attrsAndKind = 0;
1410 if (desc->enumerable()) {
1411 attrsAndKind |= ATTR_ENUMERABLE;
1413 if (desc->configurable()) {
1414 attrsAndKind |= ATTR_CONFIGURABLE;
1416 if (!desc->isAccessorDescriptor()) {
1417 if (desc->writable()) {
1418 attrsAndKind |= ATTR_WRITABLE;
1420 attrsAndKind |= DATA_DESCRIPTOR_KIND;
1421 } else {
1422 attrsAndKind |= ACCESSOR_DESCRIPTOR_KIND;
1425 Rooted<ArrayObject*> result(cx);
1426 if (!desc->isAccessorDescriptor()) {
1427 result = NewDenseFullyAllocatedArray(cx, 2);
1428 if (!result) {
1429 return false;
1431 result->setDenseInitializedLength(2);
1433 result->initDenseElement(PROP_DESC_ATTRS_AND_KIND_INDEX,
1434 Int32Value(attrsAndKind));
1435 result->initDenseElement(PROP_DESC_VALUE_INDEX, desc->value());
1436 } else {
1437 result = NewDenseFullyAllocatedArray(cx, 3);
1438 if (!result) {
1439 return false;
1441 result->setDenseInitializedLength(3);
1443 result->initDenseElement(PROP_DESC_ATTRS_AND_KIND_INDEX,
1444 Int32Value(attrsAndKind));
1446 if (JSObject* get = desc->getter()) {
1447 result->initDenseElement(PROP_DESC_GETTER_INDEX, ObjectValue(*get));
1448 } else {
1449 result->initDenseElement(PROP_DESC_GETTER_INDEX, UndefinedValue());
1452 if (JSObject* set = desc->setter()) {
1453 result->initDenseElement(PROP_DESC_SETTER_INDEX, ObjectValue(*set));
1454 } else {
1455 result->initDenseElement(PROP_DESC_SETTER_INDEX, UndefinedValue());
1459 vp.setObject(*result);
1460 return true;
1463 // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
1464 // 19.1.2.6 Object.getOwnPropertyDescriptor ( O, P )
1465 bool js::GetOwnPropertyDescriptorToArray(JSContext* cx, unsigned argc,
1466 Value* vp) {
1467 CallArgs args = CallArgsFromVp(argc, vp);
1468 MOZ_ASSERT(args.length() == 2);
1470 // Step 1.
1471 RootedObject obj(cx, ToObject(cx, args[0]));
1472 if (!obj) {
1473 return false;
1476 // Step 2.
1477 RootedId id(cx);
1478 if (!ToPropertyKey(cx, args[1], &id)) {
1479 return false;
1482 // Step 3.
1483 Rooted<Maybe<PropertyDescriptor>> desc(cx);
1484 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) {
1485 return false;
1488 // Step 4.
1489 return FromPropertyDescriptorToArray(cx, desc, args.rval());
1492 static bool NewValuePair(JSContext* cx, HandleValue val1, HandleValue val2,
1493 MutableHandleValue rval,
1494 gc::Heap heap = gc::Heap::Default) {
1495 NewObjectKind kind =
1496 heap == gc::Heap::Tenured ? TenuredObject : GenericObject;
1497 ArrayObject* array = NewDenseFullyAllocatedArray(cx, 2, kind);
1498 if (!array) {
1499 return false;
1502 array->setDenseInitializedLength(2);
1503 array->initDenseElement(0, val1);
1504 array->initDenseElement(1, val2);
1506 rval.setObject(*array);
1507 return true;
1510 enum class EnumerableOwnPropertiesKind { Keys, Values, KeysAndValues, Names };
1512 static bool HasEnumerableStringNonDataProperties(NativeObject* obj) {
1513 // We also check for enumerability and symbol properties, so uninteresting
1514 // non-data properties like |array.length| don't let us fall into the slow
1515 // path.
1516 if (!obj->hasEnumerableProperty()) {
1517 return false;
1519 for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
1520 if (!iter->isDataProperty() && iter->enumerable() &&
1521 !iter->key().isSymbol()) {
1522 return true;
1525 return false;
1528 template <EnumerableOwnPropertiesKind kind>
1529 static bool TryEnumerableOwnPropertiesNative(JSContext* cx, HandleObject obj,
1530 MutableHandleValue rval,
1531 bool* optimized) {
1532 *optimized = false;
1534 // Use the fast path if |obj| has neither extra indexed properties nor a
1535 // newEnumerate hook. String objects need to be special-cased, because
1536 // they're only marked as indexed after their enumerate hook ran. And
1537 // because their enumerate hook is slowish, it's more performant to
1538 // exclude them directly instead of executing the hook first.
1539 if (!obj->is<NativeObject>() || obj->as<NativeObject>().isIndexed() ||
1540 obj->getClass()->getNewEnumerate() || obj->is<StringObject>()) {
1541 return true;
1544 #ifdef ENABLE_RECORD_TUPLE
1545 if (obj->is<TupleObject>()) {
1546 Rooted<TupleType*> tup(cx, &obj->as<TupleObject>().unbox());
1547 return TryEnumerableOwnPropertiesNative<kind>(cx, tup, rval, optimized);
1548 } else if (obj->is<RecordObject>()) {
1549 Rooted<RecordType*> tup(cx, obj->as<RecordObject>().unbox());
1550 return TryEnumerableOwnPropertiesNative<kind>(cx, tup, rval, optimized);
1552 #endif
1554 Handle<NativeObject*> nobj = obj.as<NativeObject>();
1556 // Resolve lazy properties on |nobj|.
1557 if (JSEnumerateOp enumerate = nobj->getClass()->getEnumerate()) {
1558 if (!enumerate(cx, nobj)) {
1559 return false;
1562 // Ensure no extra indexed properties were added through enumerate().
1563 if (nobj->isIndexed()) {
1564 return true;
1568 *optimized = true;
1570 RootedValueVector properties(cx);
1571 RootedValue key(cx);
1572 RootedValue value(cx);
1574 if (kind == EnumerableOwnPropertiesKind::Keys) {
1575 // If possible, attempt to use the shape's iterator cache.
1576 Rooted<PropertyIteratorObject*> piter(cx,
1577 LookupInShapeIteratorCache(cx, nobj));
1578 if (piter) {
1579 do {
1580 NativeIterator* ni = piter->getNativeIterator();
1581 MOZ_ASSERT(ni->isReusable());
1583 // Guard against indexes.
1584 if (ni->mayHavePrototypeProperties()) {
1585 break;
1588 JSLinearString** properties =
1589 ni->propertiesBegin()->unbarrieredAddress();
1590 JSObject* array = NewDenseCopiedArray(cx, ni->numKeys(), properties);
1591 if (!array) {
1592 return false;
1595 rval.setObject(*array);
1596 return true;
1598 } while (false);
1602 // Switch to allocating in the tenured heap if necessary to avoid possible
1603 // quadratic behaviour marking stack rooted |properties| vector.
1604 AutoSelectGCHeap gcHeap(cx, 1);
1606 // We have ensured |nobj| contains no extra indexed properties, so the
1607 // only indexed properties we need to handle here are dense and typed
1608 // array elements.
1610 // Pre-reserve to avoid reallocating the properties vector frequently.
1611 if (nobj->getDenseInitializedLength() > 0 &&
1612 !properties.reserve(nobj->getDenseInitializedLength())) {
1613 return false;
1615 for (uint32_t i = 0, len = nobj->getDenseInitializedLength(); i < len; i++) {
1616 value.set(nobj->getDenseElement(i));
1617 if (value.isMagic(JS_ELEMENTS_HOLE)) {
1618 continue;
1621 JSString* str;
1622 if (kind != EnumerableOwnPropertiesKind::Values) {
1623 static_assert(
1624 NativeObject::MAX_DENSE_ELEMENTS_COUNT <= PropertyKey::IntMax,
1625 "dense elements don't exceed PropertyKey::IntMax");
1626 str = Int32ToStringWithHeap<CanGC>(cx, i, gcHeap);
1627 if (!str) {
1628 return false;
1632 if (kind == EnumerableOwnPropertiesKind::Keys ||
1633 kind == EnumerableOwnPropertiesKind::Names) {
1634 value.setString(str);
1635 } else if (kind == EnumerableOwnPropertiesKind::KeysAndValues) {
1636 key.setString(str);
1637 if (!NewValuePair(cx, key, value, &value, gcHeap)) {
1638 return false;
1642 if (!properties.append(value)) {
1643 return false;
1647 if (obj->is<TypedArrayObject>()) {
1648 Handle<TypedArrayObject*> tobj = obj.as<TypedArrayObject>();
1649 size_t len = tobj->length().valueOr(0);
1651 // Fail early if the typed array contains too many elements for a
1652 // dense array, because we likely OOM anyway when trying to allocate
1653 // more than 2GB for the properties vector. This also means we don't
1654 // need to handle indices greater than MAX_INT32 in the loop below.
1655 if (len > NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
1656 ReportOversizedAllocation(cx, JSMSG_ALLOC_OVERFLOW);
1657 return false;
1660 MOZ_ASSERT(properties.empty(), "typed arrays cannot have dense elements");
1661 if (!properties.resize(len)) {
1662 return false;
1665 for (uint32_t i = 0; i < len; i++) {
1666 JSString* str;
1667 if (kind != EnumerableOwnPropertiesKind::Values) {
1668 static_assert(
1669 NativeObject::MAX_DENSE_ELEMENTS_COUNT <= PropertyKey::IntMax,
1670 "dense elements don't exceed PropertyKey::IntMax");
1671 str = Int32ToStringWithHeap<CanGC>(cx, i, gcHeap);
1672 if (!str) {
1673 return false;
1677 if (kind == EnumerableOwnPropertiesKind::Keys ||
1678 kind == EnumerableOwnPropertiesKind::Names) {
1679 value.setString(str);
1680 } else if (kind == EnumerableOwnPropertiesKind::Values) {
1681 if (!tobj->getElement<CanGC>(cx, i, &value)) {
1682 return false;
1684 } else {
1685 key.setString(str);
1686 if (!tobj->getElement<CanGC>(cx, i, &value)) {
1687 return false;
1689 if (!NewValuePair(cx, key, value, &value, gcHeap)) {
1690 return false;
1694 properties[i].set(value);
1697 #ifdef ENABLE_RECORD_TUPLE
1698 else if (obj->is<RecordType>()) {
1699 RecordType* rec = &obj->as<RecordType>();
1700 Rooted<ArrayObject*> keys(cx, rec->keys());
1701 RootedId keyId(cx);
1702 RootedString keyStr(cx);
1704 MOZ_ASSERT(properties.empty(), "records cannot have dense elements");
1705 if (!properties.resize(keys->length())) {
1706 return false;
1709 for (size_t i = 0; i < keys->length(); i++) {
1710 MOZ_ASSERT(keys->getDenseElement(i).isString());
1711 if (kind == EnumerableOwnPropertiesKind::Keys ||
1712 kind == EnumerableOwnPropertiesKind::Names) {
1713 value.set(keys->getDenseElement(i));
1714 } else if (kind == EnumerableOwnPropertiesKind::Values) {
1715 keyStr.set(keys->getDenseElement(i).toString());
1717 if (!JS_StringToId(cx, keyStr, &keyId)) {
1718 return false;
1720 MOZ_ALWAYS_TRUE(rec->getOwnProperty(cx, keyId, &value));
1721 } else {
1722 MOZ_ASSERT(kind == EnumerableOwnPropertiesKind::KeysAndValues);
1724 key.set(keys->getDenseElement(i));
1725 keyStr.set(key.toString());
1727 if (!JS_StringToId(cx, keyStr, &keyId)) {
1728 return false;
1730 MOZ_ALWAYS_TRUE(rec->getOwnProperty(cx, keyId, &value));
1732 if (!NewValuePair(cx, key, value, &value, gcHeap)) {
1733 return false;
1737 properties[i].set(value);
1740 // Uh, goto... When using records, we already get the (sorted) properties
1741 // from its sorted keys, so we don't read them again as "own properties".
1742 // We could use an `if` or some refactoring to skip the next logic, but
1743 // goto makes it easer to keep the logic separated in
1744 // "#ifdef ENABLE_RECORD_TUPLE" blocks.
1745 // This should be refactored when the #ifdefs are removed.
1746 goto end;
1748 #endif
1750 // Up to this point no side-effects through accessor properties are
1751 // possible which could have replaced |obj| with a non-native object.
1752 MOZ_ASSERT(obj->is<NativeObject>());
1753 MOZ_ASSERT(obj.as<NativeObject>() == nobj);
1756 // This new scope exists to support the goto end used by
1757 // ENABLE_RECORD_TUPLE builds, and can be removed when said goto goes away.
1758 size_t approximatePropertyCount =
1759 nobj->shape()->propMap()
1760 ? nobj->shape()->propMap()->approximateEntryCount()
1761 : 0;
1762 if (!properties.reserve(properties.length() + approximatePropertyCount)) {
1763 return false;
1767 if (kind == EnumerableOwnPropertiesKind::Keys ||
1768 kind == EnumerableOwnPropertiesKind::Names ||
1769 !HasEnumerableStringNonDataProperties(nobj)) {
1770 // If |kind == Values| or |kind == KeysAndValues|:
1771 // All enumerable properties with string property keys are data
1772 // properties. This allows us to collect the property values while
1773 // iterating over the shape hierarchy without worrying over accessors
1774 // modifying any state.
1776 constexpr bool onlyEnumerable = kind != EnumerableOwnPropertiesKind::Names;
1777 if (!onlyEnumerable || nobj->hasEnumerableProperty()) {
1778 size_t elements = properties.length();
1779 constexpr AllowGC allowGC =
1780 kind != EnumerableOwnPropertiesKind::KeysAndValues ? AllowGC::NoGC
1781 : AllowGC::CanGC;
1782 mozilla::Maybe<ShapePropertyIter<allowGC>> m;
1783 if constexpr (allowGC == AllowGC::NoGC) {
1784 m.emplace(nobj->shape());
1785 } else {
1786 m.emplace(cx, nobj->shape());
1788 for (auto& iter = m.ref(); !iter.done(); iter++) {
1789 jsid id = iter->key();
1790 if ((onlyEnumerable && !iter->enumerable()) || id.isSymbol()) {
1791 continue;
1793 MOZ_ASSERT(!id.isInt(), "Unexpected indexed property");
1794 MOZ_ASSERT_IF(kind == EnumerableOwnPropertiesKind::Values ||
1795 kind == EnumerableOwnPropertiesKind::KeysAndValues,
1796 iter->isDataProperty());
1798 if constexpr (kind == EnumerableOwnPropertiesKind::Keys ||
1799 kind == EnumerableOwnPropertiesKind::Names) {
1800 value.setString(id.toString());
1801 } else if constexpr (kind == EnumerableOwnPropertiesKind::Values) {
1802 value.set(nobj->getSlot(iter->slot()));
1803 } else {
1804 key.setString(id.toString());
1805 value.set(nobj->getSlot(iter->slot()));
1806 if (!NewValuePair(cx, key, value, &value, gcHeap)) {
1807 return false;
1811 if (!properties.append(value)) {
1812 return false;
1816 // The (non-indexed) properties were visited in reverse iteration order,
1817 // call std::reverse() to ensure they appear in iteration order.
1818 std::reverse(properties.begin() + elements, properties.end());
1820 } else {
1821 MOZ_ASSERT(kind == EnumerableOwnPropertiesKind::Values ||
1822 kind == EnumerableOwnPropertiesKind::KeysAndValues);
1824 // Get a list of all |obj| properties. As long as obj->shape()
1825 // is equal to |objShape|, we can use this to speed up both the
1826 // enumerability check and GetProperty.
1827 Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx));
1829 // Collect all non-symbol properties.
1830 Rooted<NativeShape*> objShape(cx, nobj->shape());
1831 for (ShapePropertyIter<NoGC> iter(objShape); !iter.done(); iter++) {
1832 if (iter->key().isSymbol()) {
1833 continue;
1835 MOZ_ASSERT(!iter->key().isInt(), "Unexpected indexed property");
1837 if (!props.append(*iter)) {
1838 return false;
1842 RootedId id(cx);
1843 for (size_t i = props.length(); i > 0; i--) {
1844 PropertyInfoWithKey prop = props[i - 1];
1845 id = prop.key();
1847 // If |obj| still has the same shape, it must still be a NativeObject with
1848 // the properties in |props|.
1849 if (obj->shape() == objShape && prop.isDataProperty()) {
1850 if (!prop.enumerable()) {
1851 continue;
1853 value = obj->as<NativeObject>().getSlot(prop.slot());
1854 } else {
1855 // |obj| changed shape or the property is not a data property,
1856 // so we have to do the slower enumerability check and
1857 // GetProperty.
1858 bool enumerable;
1859 if (!PropertyIsEnumerable(cx, obj, id, &enumerable)) {
1860 return false;
1862 if (!enumerable) {
1863 continue;
1865 if (!GetProperty(cx, obj, obj, id, &value)) {
1866 return false;
1870 if (kind == EnumerableOwnPropertiesKind::KeysAndValues) {
1871 key.setString(id.toString());
1872 if (!NewValuePair(cx, key, value, &value, gcHeap)) {
1873 return false;
1877 if (!properties.append(value)) {
1878 return false;
1883 #ifdef ENABLE_RECORD_TUPLE
1884 end:
1885 #endif
1887 JSObject* array =
1888 NewDenseCopiedArray(cx, properties.length(), properties.begin());
1889 if (!array) {
1890 return false;
1893 rval.setObject(*array);
1894 return true;
1897 // Optimization dedicated for `Object.keys(..).length` JS pattern. This function
1898 // replicates TryEnumerableOwnPropertiesNative code, except that instead of
1899 // generating an array we only return the length of the array that would have
1900 // been generated.
1902 // As opposed to TryEnumerableOwnPropertiesNative, this function only support
1903 // EnumerableOwnPropertiesKind::Keys variant.
1904 static bool CountEnumerableOwnPropertiesNative(JSContext* cx, HandleObject obj,
1905 int32_t& rval, bool* optimized) {
1906 *optimized = false;
1908 // Use the fast path if |obj| has neither extra indexed properties nor a
1909 // newEnumerate hook. String objects need to be special-cased, because
1910 // they're only marked as indexed after their enumerate hook ran. And
1911 // because their enumerate hook is slowish, it's more performant to
1912 // exclude them directly instead of executing the hook first.
1913 if (!obj->is<NativeObject>() || obj->as<NativeObject>().isIndexed() ||
1914 obj->getClass()->getNewEnumerate() || obj->is<StringObject>()) {
1915 return true;
1918 #ifdef ENABLE_RECORD_TUPLE
1919 // Skip the optimized path in case of record and tuples.
1920 if (obj->is<TupleObject>() || obj->is<RecordObject>()) {
1921 return true;
1923 #endif
1925 Handle<NativeObject*> nobj = obj.as<NativeObject>();
1927 // Resolve lazy properties on |nobj|.
1928 if (JSEnumerateOp enumerate = nobj->getClass()->getEnumerate()) {
1929 if (!enumerate(cx, nobj)) {
1930 return false;
1933 // Ensure no extra indexed properties were added through enumerate().
1934 if (nobj->isIndexed()) {
1935 return true;
1939 *optimized = true;
1941 int32_t num_properties = 0;
1943 // If possible, attempt to use the shape's iterator cache.
1944 Rooted<PropertyIteratorObject*> piter(cx,
1945 LookupInShapeIteratorCache(cx, nobj));
1946 if (piter) {
1947 NativeIterator* ni = piter->getNativeIterator();
1948 MOZ_ASSERT(ni->isReusable());
1950 // Guard against indexes.
1951 if (!ni->mayHavePrototypeProperties()) {
1952 rval = ni->numKeys();
1953 return true;
1957 for (uint32_t i = 0, len = nobj->getDenseInitializedLength(); i < len; i++) {
1958 if (nobj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
1959 continue;
1962 num_properties += 1;
1965 if (obj->is<TypedArrayObject>()) {
1966 Handle<TypedArrayObject*> tobj = obj.as<TypedArrayObject>();
1967 size_t len = tobj->length().valueOr(0);
1969 // Fail early if the typed array contains too many elements for a
1970 // dense array, because we likely OOM anyway when trying to allocate
1971 // more than 2GB for the properties vector. This also means we don't
1972 // need to handle indices greater than MAX_INT32 in the loop below.
1973 if (len > NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
1974 ReportOversizedAllocation(cx, JSMSG_ALLOC_OVERFLOW);
1975 return false;
1978 MOZ_ASSERT(num_properties == 0, "typed arrays cannot have dense elements");
1979 num_properties = len;
1982 // All enumerable properties with string property keys are data
1983 // properties. This allows us to collect the property values while
1984 // iterating over the shape hierarchy without worrying over accessors
1985 // modifying any state.
1987 if (nobj->hasEnumerableProperty()) {
1988 for (ShapePropertyIter<AllowGC::NoGC> iter(obj.as<NativeObject>()->shape());
1989 !iter.done(); iter++) {
1990 jsid id = iter->key();
1991 if (!iter->enumerable() || id.isSymbol()) {
1992 continue;
1994 MOZ_ASSERT(!id.isInt(), "Unexpected indexed property");
1995 num_properties += 1;
1999 rval = num_properties;
2000 return true;
2003 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2004 // 7.3.21 EnumerableOwnProperties ( O, kind )
2005 template <EnumerableOwnPropertiesKind kind>
2006 static bool EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args) {
2007 static_assert(kind == EnumerableOwnPropertiesKind::Values ||
2008 kind == EnumerableOwnPropertiesKind::KeysAndValues,
2009 "Only implemented for Object.keys and Object.entries");
2011 // Step 1. (Step 1 of Object.{keys,values,entries}, really.)
2012 RootedObject obj(cx, IF_RECORD_TUPLE(ToObjectOrGetObjectPayload, ToObject)(
2013 cx, args.get(0)));
2014 if (!obj) {
2015 return false;
2018 bool optimized;
2019 if (!TryEnumerableOwnPropertiesNative<kind>(cx, obj, args.rval(),
2020 &optimized)) {
2021 return false;
2023 if (optimized) {
2024 return true;
2027 // Typed arrays are always handled in the fast path.
2028 MOZ_ASSERT(!obj->is<TypedArrayObject>());
2030 // Step 2.
2031 RootedIdVector ids(cx);
2032 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids)) {
2033 return false;
2036 // Step 3.
2037 RootedValueVector properties(cx);
2038 size_t len = ids.length();
2039 if (!properties.resize(len)) {
2040 return false;
2043 RootedId id(cx);
2044 RootedValue key(cx);
2045 RootedValue value(cx);
2046 Rooted<Shape*> shape(cx);
2047 Rooted<Maybe<PropertyDescriptor>> desc(cx);
2048 // Step 4.
2049 size_t out = 0;
2050 for (size_t i = 0; i < len; i++) {
2051 id = ids[i];
2053 // Step 4.a. (Symbols were filtered out in step 2.)
2054 MOZ_ASSERT(!id.isSymbol());
2056 if (kind != EnumerableOwnPropertiesKind::Values) {
2057 if (!IdToStringOrSymbol(cx, id, &key)) {
2058 return false;
2062 // Step 4.a.i.
2063 if (obj->is<NativeObject>()) {
2064 Handle<NativeObject*> nobj = obj.as<NativeObject>();
2065 if (id.isInt() && nobj->containsDenseElement(id.toInt())) {
2066 value.set(nobj->getDenseElement(id.toInt()));
2067 } else {
2068 Maybe<PropertyInfo> prop = nobj->lookup(cx, id);
2069 if (prop.isNothing() || !prop->enumerable()) {
2070 continue;
2072 if (prop->isDataProperty()) {
2073 value = nobj->getSlot(prop->slot());
2074 } else if (!GetProperty(cx, obj, obj, id, &value)) {
2075 return false;
2078 } else {
2079 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) {
2080 return false;
2083 // Step 4.a.ii. (inverted.)
2084 if (desc.isNothing() || !desc->enumerable()) {
2085 continue;
2088 // Step 4.a.ii.1.
2089 // (Omitted because Object.keys doesn't use this implementation.)
2091 // Step 4.a.ii.2.a.
2092 if (!GetProperty(cx, obj, obj, id, &value)) {
2093 return false;
2097 // Steps 4.a.ii.2.b-c.
2098 if (kind == EnumerableOwnPropertiesKind::Values) {
2099 properties[out++].set(value);
2100 } else if (!NewValuePair(cx, key, value, properties[out++])) {
2101 return false;
2105 // Step 5.
2106 // (Implemented in step 2.)
2108 // Step 3 of Object.{keys,values,entries}
2109 JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin());
2110 if (!aobj) {
2111 return false;
2114 args.rval().setObject(*aobj);
2115 return true;
2118 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2119 // 19.1.2.16 Object.keys ( O )
2120 bool js::obj_keys(JSContext* cx, unsigned argc, Value* vp) {
2121 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "keys");
2122 CallArgs args = CallArgsFromVp(argc, vp);
2124 // Step 1.
2125 RootedObject obj(cx, IF_RECORD_TUPLE(ToObjectOrGetObjectPayload, ToObject)(
2126 cx, args.get(0)));
2127 if (!obj) {
2128 return false;
2131 bool optimized;
2132 static constexpr EnumerableOwnPropertiesKind kind =
2133 EnumerableOwnPropertiesKind::Keys;
2134 if (!TryEnumerableOwnPropertiesNative<kind>(cx, obj, args.rval(),
2135 &optimized)) {
2136 return false;
2138 if (optimized) {
2139 return true;
2142 // Steps 2-3.
2143 return GetOwnPropertyKeys(cx, obj, JSITER_OWNONLY, args.rval());
2146 bool js::obj_keys_length(JSContext* cx, HandleObject obj, int32_t& length) {
2147 bool optimized;
2148 if (!CountEnumerableOwnPropertiesNative(cx, obj, length, &optimized)) {
2149 return false;
2151 if (optimized) {
2152 return true;
2155 // Object.keys: Steps 2-3.
2156 // (GetOwnPropertyKeys / CountOwnPropertyKeys)
2157 RootedIdVector keys(cx);
2158 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys)) {
2159 return false;
2162 length = keys.length();
2163 return true;
2166 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2167 // 19.1.2.21 Object.values ( O )
2168 static bool obj_values(JSContext* cx, unsigned argc, Value* vp) {
2169 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "values");
2170 CallArgs args = CallArgsFromVp(argc, vp);
2172 // Steps 1-3.
2173 return EnumerableOwnProperties<EnumerableOwnPropertiesKind::Values>(cx, args);
2176 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2177 // 19.1.2.5 Object.entries ( O )
2178 static bool obj_entries(JSContext* cx, unsigned argc, Value* vp) {
2179 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "entries");
2180 CallArgs args = CallArgsFromVp(argc, vp);
2182 // Steps 1-3.
2183 return EnumerableOwnProperties<EnumerableOwnPropertiesKind::KeysAndValues>(
2184 cx, args);
2187 /* ES6 draft 15.2.3.16 */
2188 bool js::obj_is(JSContext* cx, unsigned argc, Value* vp) {
2189 CallArgs args = CallArgsFromVp(argc, vp);
2191 bool same;
2192 if (!SameValue(cx, args.get(0), args.get(1), &same)) {
2193 return false;
2196 args.rval().setBoolean(same);
2197 return true;
2200 bool js::IdToStringOrSymbol(JSContext* cx, HandleId id,
2201 MutableHandleValue result) {
2202 if (id.isInt()) {
2203 JSString* str = Int32ToString<CanGC>(cx, id.toInt());
2204 if (!str) {
2205 return false;
2207 result.setString(str);
2208 } else if (id.isAtom()) {
2209 result.setString(id.toAtom());
2210 } else {
2211 result.setSymbol(id.toSymbol());
2213 return true;
2216 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2217 // 19.1.2.10.1 Runtime Semantics: GetOwnPropertyKeys ( O, Type )
2218 bool js::GetOwnPropertyKeys(JSContext* cx, HandleObject obj, unsigned flags,
2219 MutableHandleValue rval) {
2220 // Step 1 (Performed in caller).
2222 // Steps 2-4.
2223 RootedIdVector keys(cx);
2224 if (!GetPropertyKeys(cx, obj, flags, &keys)) {
2225 return false;
2228 // Step 5 (Inlined CreateArrayFromList).
2229 Rooted<ArrayObject*> array(cx,
2230 NewDenseFullyAllocatedArray(cx, keys.length()));
2231 if (!array) {
2232 return false;
2235 array->ensureDenseInitializedLength(0, keys.length());
2237 RootedValue val(cx);
2238 for (size_t i = 0, len = keys.length(); i < len; i++) {
2239 MOZ_ASSERT_IF(keys[i].isSymbol(), flags & JSITER_SYMBOLS);
2240 MOZ_ASSERT_IF(!keys[i].isSymbol(), !(flags & JSITER_SYMBOLSONLY));
2241 if (!IdToStringOrSymbol(cx, keys[i], &val)) {
2242 return false;
2244 array->initDenseElement(i, val);
2247 rval.setObject(*array);
2248 return true;
2251 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2252 // 19.1.2.9 Object.getOwnPropertyNames ( O )
2253 static bool obj_getOwnPropertyNames(JSContext* cx, unsigned argc, Value* vp) {
2254 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "getOwnPropertyNames");
2255 CallArgs args = CallArgsFromVp(argc, vp);
2257 RootedObject obj(cx, ToObject(cx, args.get(0)));
2258 if (!obj) {
2259 return false;
2262 bool optimized;
2263 static constexpr EnumerableOwnPropertiesKind kind =
2264 EnumerableOwnPropertiesKind::Names;
2265 if (!TryEnumerableOwnPropertiesNative<kind>(cx, obj, args.rval(),
2266 &optimized)) {
2267 return false;
2269 if (optimized) {
2270 return true;
2273 return GetOwnPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN,
2274 args.rval());
2277 // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f
2278 // 19.1.2.10 Object.getOwnPropertySymbols ( O )
2279 static bool obj_getOwnPropertySymbols(JSContext* cx, unsigned argc, Value* vp) {
2280 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "getOwnPropertySymbols");
2281 CallArgs args = CallArgsFromVp(argc, vp);
2283 RootedObject obj(cx, ToObject(cx, args.get(0)));
2284 if (!obj) {
2285 return false;
2288 return GetOwnPropertyKeys(
2289 cx, obj,
2290 JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY,
2291 args.rval());
2294 /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */
2295 static bool obj_defineProperties(JSContext* cx, unsigned argc, Value* vp) {
2296 AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "defineProperties");
2297 CallArgs args = CallArgsFromVp(argc, vp);
2299 /* Step 1. */
2300 RootedObject obj(cx);
2301 if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperties", &obj)) {
2302 return false;
2305 /* Step 2. */
2306 if (!args.requireAtLeast(cx, "Object.defineProperties", 2)) {
2307 return false;
2310 /* Steps 3-6. */
2311 bool failedOnWindowProxy = false;
2312 if (!ObjectDefineProperties(cx, obj, args[1], &failedOnWindowProxy)) {
2313 return false;
2316 /* Step 7, but modified to deal with WindowProxy mess */
2317 if (failedOnWindowProxy) {
2318 args.rval().setNull();
2319 } else {
2320 args.rval().setObject(*obj);
2322 return true;
2325 // ES6 20141014 draft 19.1.2.15 Object.preventExtensions(O)
2326 static bool obj_preventExtensions(JSContext* cx, unsigned argc, Value* vp) {
2327 CallArgs args = CallArgsFromVp(argc, vp);
2328 args.rval().set(args.get(0));
2330 // Step 1.
2331 if (!args.get(0).isObject()) {
2332 return true;
2335 // Steps 2-5.
2336 RootedObject obj(cx, &args.get(0).toObject());
2337 return PreventExtensions(cx, obj);
2340 // ES6 draft rev27 (2014/08/24) 19.1.2.5 Object.freeze(O)
2341 static bool obj_freeze(JSContext* cx, unsigned argc, Value* vp) {
2342 CallArgs args = CallArgsFromVp(argc, vp);
2343 args.rval().set(args.get(0));
2345 // Step 1.
2346 if (!args.get(0).isObject()) {
2347 return true;
2350 // Steps 2-5.
2351 RootedObject obj(cx, &args.get(0).toObject());
2352 return SetIntegrityLevel(cx, obj, IntegrityLevel::Frozen);
2355 // ES6 draft rev27 (2014/08/24) 19.1.2.12 Object.isFrozen(O)
2356 static bool obj_isFrozen(JSContext* cx, unsigned argc, Value* vp) {
2357 CallArgs args = CallArgsFromVp(argc, vp);
2359 // Step 1.
2360 bool frozen = true;
2362 // Step 2.
2363 if (args.get(0).isObject()) {
2364 RootedObject obj(cx, &args.get(0).toObject());
2365 if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &frozen)) {
2366 return false;
2369 args.rval().setBoolean(frozen);
2370 return true;
2373 // ES6 draft rev27 (2014/08/24) 19.1.2.17 Object.seal(O)
2374 static bool obj_seal(JSContext* cx, unsigned argc, Value* vp) {
2375 CallArgs args = CallArgsFromVp(argc, vp);
2376 args.rval().set(args.get(0));
2378 // Step 1.
2379 if (!args.get(0).isObject()) {
2380 return true;
2383 // Steps 2-5.
2384 RootedObject obj(cx, &args.get(0).toObject());
2385 return SetIntegrityLevel(cx, obj, IntegrityLevel::Sealed);
2388 // ES6 draft rev27 (2014/08/24) 19.1.2.13 Object.isSealed(O)
2389 static bool obj_isSealed(JSContext* cx, unsigned argc, Value* vp) {
2390 CallArgs args = CallArgsFromVp(argc, vp);
2392 // Step 1.
2393 bool sealed = true;
2395 // Step 2.
2396 if (args.get(0).isObject()) {
2397 RootedObject obj(cx, &args.get(0).toObject());
2398 if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Sealed, &sealed)) {
2399 return false;
2402 args.rval().setBoolean(sealed);
2403 return true;
2406 bool js::obj_setProto(JSContext* cx, unsigned argc, Value* vp) {
2407 CallArgs args = CallArgsFromVp(argc, vp);
2408 MOZ_ASSERT(args.length() == 1);
2410 HandleValue thisv = args.thisv();
2411 if (thisv.isNullOrUndefined()) {
2412 ReportIncompatible(cx, args);
2413 return false;
2415 if (thisv.isPrimitive()) {
2416 // Mutating a boxed primitive's [[Prototype]] has no side effects.
2417 args.rval().setUndefined();
2418 return true;
2421 /* Do nothing if __proto__ isn't being set to an object or null. */
2422 if (!args[0].isObjectOrNull()) {
2423 args.rval().setUndefined();
2424 return true;
2427 Rooted<JSObject*> obj(cx, &args.thisv().toObject());
2428 Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull());
2429 if (!SetPrototype(cx, obj, newProto)) {
2430 return false;
2433 args.rval().setUndefined();
2434 return true;
2437 static const JSFunctionSpec object_methods[] = {
2438 JS_FN("toSource", obj_toSource, 0, 0),
2439 JS_INLINABLE_FN("toString", obj_toString, 0, 0, ObjectToString),
2440 JS_SELF_HOSTED_FN("toLocaleString", "Object_toLocaleString", 0, 0),
2441 JS_SELF_HOSTED_FN("valueOf", "Object_valueOf", 0, 0),
2442 JS_SELF_HOSTED_FN("hasOwnProperty", "Object_hasOwnProperty", 1, 0),
2443 JS_INLINABLE_FN("isPrototypeOf", obj_isPrototypeOf, 1, 0,
2444 ObjectIsPrototypeOf),
2445 JS_FN("propertyIsEnumerable", obj_propertyIsEnumerable, 1, 0),
2446 JS_SELF_HOSTED_FN("__defineGetter__", "ObjectDefineGetter", 2, 0),
2447 JS_SELF_HOSTED_FN("__defineSetter__", "ObjectDefineSetter", 2, 0),
2448 JS_SELF_HOSTED_FN("__lookupGetter__", "ObjectLookupGetter", 1, 0),
2449 JS_SELF_HOSTED_FN("__lookupSetter__", "ObjectLookupSetter", 1, 0),
2450 JS_FS_END};
2452 static const JSPropertySpec object_properties[] = {
2453 JS_SELF_HOSTED_GETSET("__proto__", "$ObjectProtoGetter",
2454 "$ObjectProtoSetter", 0),
2455 JS_PS_END};
2457 static const JSFunctionSpec object_static_methods[] = {
2458 JS_FN("assign", obj_assign, 2, 0),
2459 JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0),
2460 JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0),
2461 JS_SELF_HOSTED_FN("getOwnPropertyDescriptor",
2462 "ObjectGetOwnPropertyDescriptor", 2, 0),
2463 JS_SELF_HOSTED_FN("getOwnPropertyDescriptors",
2464 "ObjectGetOwnPropertyDescriptors", 1, 0),
2465 JS_INLINABLE_FN("keys", obj_keys, 1, 0, ObjectKeys),
2466 JS_FN("values", obj_values, 1, 0),
2467 JS_FN("entries", obj_entries, 1, 0),
2468 JS_INLINABLE_FN("is", obj_is, 2, 0, ObjectIs),
2469 JS_SELF_HOSTED_FN("defineProperty", "ObjectDefineProperty", 3, 0),
2470 JS_FN("defineProperties", obj_defineProperties, 2, 0),
2471 JS_INLINABLE_FN("create", obj_create, 2, 0, ObjectCreate),
2472 JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1, 0),
2473 JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 1, 0),
2474 JS_SELF_HOSTED_FN("isExtensible", "ObjectIsExtensible", 1, 0),
2475 JS_FN("preventExtensions", obj_preventExtensions, 1, 0),
2476 JS_FN("freeze", obj_freeze, 1, 0),
2477 JS_FN("isFrozen", obj_isFrozen, 1, 0),
2478 JS_FN("seal", obj_seal, 1, 0),
2479 JS_FN("isSealed", obj_isSealed, 1, 0),
2480 JS_SELF_HOSTED_FN("fromEntries", "ObjectFromEntries", 1, 0),
2481 JS_SELF_HOSTED_FN("hasOwn", "ObjectHasOwn", 2, 0),
2482 JS_SELF_HOSTED_FN("groupBy", "ObjectGroupBy", 2, 0),
2483 JS_FS_END};
2485 static JSObject* CreateObjectConstructor(JSContext* cx, JSProtoKey key) {
2486 Rooted<GlobalObject*> self(cx, cx->global());
2487 if (!GlobalObject::ensureConstructor(cx, self, JSProto_Function)) {
2488 return nullptr;
2491 /* Create the Object function now that we have a [[Prototype]] for it. */
2492 JSFunction* fun = NewNativeConstructor(
2493 cx, obj_construct, 1, Handle<PropertyName*>(cx->names().Object),
2494 gc::AllocKind::FUNCTION, TenuredObject);
2495 if (!fun) {
2496 return nullptr;
2499 fun->setJitInfo(&jit::JitInfo_Object);
2500 return fun;
2503 static JSObject* CreateObjectPrototype(JSContext* cx, JSProtoKey key) {
2504 MOZ_ASSERT(!cx->zone()->isAtomsZone());
2505 MOZ_ASSERT(cx->global()->is<NativeObject>());
2508 * Create |Object.prototype| first, mirroring CreateBlankProto but for the
2509 * prototype of the created object.
2511 Rooted<PlainObject*> objectProto(
2512 cx, NewPlainObjectWithProto(cx, nullptr, TenuredObject));
2513 if (!objectProto) {
2514 return nullptr;
2517 bool succeeded;
2518 if (!SetImmutablePrototype(cx, objectProto, &succeeded)) {
2519 return nullptr;
2521 MOZ_ASSERT(succeeded,
2522 "should have been able to make a fresh Object.prototype's "
2523 "[[Prototype]] immutable");
2525 return objectProto;
2528 static bool FinishObjectClassInit(JSContext* cx, JS::HandleObject ctor,
2529 JS::HandleObject proto) {
2530 Rooted<GlobalObject*> global(cx, cx->global());
2532 // ES5 15.1.2.1.
2533 RootedId evalId(cx, NameToId(cx->names().eval));
2534 JSFunction* evalobj =
2535 DefineFunction(cx, global, evalId, IndirectEval, 1, JSPROP_RESOLVING);
2536 if (!evalobj) {
2537 return false;
2539 global->setOriginalEval(evalobj);
2541 #ifdef FUZZING
2542 if (cx->options().fuzzing()) {
2543 if (!DefineTestingFunctions(cx, global, /* fuzzingSafe = */ true,
2544 /* disableOOMFunctions = */ false)) {
2545 return false;
2548 #endif
2550 // The global object should have |Object.prototype| as its [[Prototype]].
2551 MOZ_ASSERT(global->staticPrototype() == nullptr);
2552 MOZ_ASSERT(!global->staticPrototypeIsImmutable());
2553 return SetPrototype(cx, global, proto);
2556 static const ClassSpec PlainObjectClassSpec = {
2557 CreateObjectConstructor, CreateObjectPrototype,
2558 object_static_methods, nullptr,
2559 object_methods, object_properties,
2560 FinishObjectClassInit};
2562 const JSClass PlainObject::class_ = {"Object",
2563 JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
2564 JS_NULL_CLASS_OPS, &PlainObjectClassSpec};
2566 const JSClass* const js::ObjectClassPtr = &PlainObject::class_;