diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /js/src/builtin/Object.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/Object.cpp')
-rw-r--r-- | js/src/builtin/Object.cpp | 2308 |
1 files changed, 2308 insertions, 0 deletions
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp new file mode 100644 index 0000000000..ece27e5534 --- /dev/null +++ b/js/src/builtin/Object.cpp @@ -0,0 +1,2308 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/Object.h" +#include "js/Object.h" // JS::GetBuiltinClass + +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/RangedPtr.h" + +#include <algorithm> +#include <string_view> + +#include "jsapi.h" + +#include "builtin/Eval.h" +#include "builtin/SelfHostingDefines.h" +#include "frontend/BytecodeCompiler.h" +#include "jit/InlinableNatives.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit +#include "js/PropertySpec.h" +#include "js/UniquePtr.h" +#include "util/StringBuffer.h" +#include "util/Text.h" +#include "vm/BooleanObject.h" +#include "vm/DateObject.h" +#include "vm/EqualityOperations.h" // js::SameValue +#include "vm/ErrorObject.h" +#include "vm/JSContext.h" +#include "vm/NumberObject.h" +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/RegExpObject.h" +#include "vm/StringObject.h" +#include "vm/ToSource.h" // js::ValueToSource +#include "vm/Watchtower.h" +#include "vm/WellKnownAtom.h" // js_*_str + +#ifdef ENABLE_RECORD_TUPLE +# include "builtin/RecordObject.h" +# include "builtin/TupleObject.h" +#endif + +#include "vm/GeckoProfiler-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/Shape-inl.h" + +#ifdef FUZZING +# include "builtin/TestingFunctions.h" +#endif + +using namespace js; + +using js::frontend::IsIdentifier; + +using mozilla::Maybe; +using mozilla::Range; +using mozilla::RangedPtr; + +static PlainObject* CreateThis(JSContext* cx, HandleObject newTarget) { + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) { + return nullptr; + } + + gc::AllocKind allocKind = NewObjectGCKind(); + + if (proto) { + return NewPlainObjectWithProtoAndAllocKind(cx, proto, allocKind); + } + return NewPlainObjectWithAllocKind(cx, allocKind); +} + +bool js::obj_construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + JSObject* obj; + if (args.isConstructing() && + (&args.newTarget().toObject() != &args.callee())) { + RootedObject newTarget(cx, &args.newTarget().toObject()); + obj = CreateThis(cx, newTarget); + } else if (args.length() > 0 && !args[0].isNullOrUndefined()) { + obj = ToObject(cx, args[0]); + } else { + /* Make an object whether this was called with 'new' or not. */ + gc::AllocKind allocKind = NewObjectGCKind(); + obj = NewPlainObjectWithAllocKind(cx, allocKind); + } + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/* ES5 15.2.4.7. */ +bool js::obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + HandleValue idValue = args.get(0); + + // As an optimization, provide a fast path when rooting is not necessary and + // we can safely retrieve the attributes from the object's shape. + + /* Steps 1-2. */ + jsid id; + if (args.thisv().isObject() && idValue.isPrimitive() && + PrimitiveValueToId<NoGC>(cx, idValue, &id)) { + JSObject* obj = &args.thisv().toObject(); + + /* Step 3. */ + PropertyResult prop; + if (obj->is<NativeObject>() && + NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, + &prop)) { + /* Step 4. */ + if (prop.isNotFound()) { + args.rval().setBoolean(false); + return true; + } + + /* Step 5. */ + JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop); + args.rval().setBoolean(attrs.enumerable()); + return true; + } + } + + /* Step 1. */ + RootedId idRoot(cx); + if (!ToPropertyKey(cx, idValue, &idRoot)) { + return false; + } + + /* Step 2. */ + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) { + return false; + } + + /* Step 3. */ + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!GetOwnPropertyDescriptor(cx, obj, idRoot, &desc)) { + return false; + } + + /* Step 4. */ + if (desc.isNothing()) { + args.rval().setBoolean(false); + return true; + } + + /* Step 5. */ + args.rval().setBoolean(desc->enumerable()); + return true; +} + +static bool obj_toSource(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object.prototype", "toSource"); + CallArgs args = CallArgsFromVp(argc, vp); + + AutoCheckRecursionLimit recursion(cx); + if (!recursion.check(cx)) { + return false; + } + + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) { + return false; + } + + JSString* str = ObjectToSource(cx, obj); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +template <typename CharT> +static bool Consume(RangedPtr<const CharT>& s, RangedPtr<const CharT> e, + std::string_view chars) { + MOZ_ASSERT(s <= e); + size_t len = chars.length(); + if (e - s < len) { + return false; + } + if (!EqualChars(s.get(), chars.data(), len)) { + return false; + } + s += len; + return true; +} + +template <typename CharT> +static bool ConsumeUntil(RangedPtr<const CharT>& s, RangedPtr<const CharT> e, + char16_t ch) { + MOZ_ASSERT(s <= e); + const CharT* result = js_strchr_limit(s.get(), ch, e.get()); + if (!result) { + return false; + } + s += result - s.get(); + MOZ_ASSERT(*s == ch); + return true; +} + +template <typename CharT> +static void ConsumeSpaces(RangedPtr<const CharT>& s, RangedPtr<const CharT> e) { + while (s < e && *s == ' ') { + s++; + } +} + +/* + * Given a function source string, return the offset and length of the part + * between '(function $name' and ')'. + */ +template <typename CharT> +static bool ArgsAndBodySubstring(Range<const CharT> chars, size_t* outOffset, + size_t* outLen) { + const RangedPtr<const CharT> start = chars.begin(); + RangedPtr<const CharT> s = start; + RangedPtr<const CharT> e = chars.end(); + + if (s == e) { + return false; + } + + // Remove enclosing parentheses. + if (*s == '(' && *(e - 1) == ')') { + s++; + e--; + } + + // Support the following cases, with spaces between tokens: + // + // -+---------+-+------------+-+-----+-+- [ - <any> - ] - ( -+- + // | | | | | | | | + // +- async -+ +- function -+ +- * -+ +- <any> - ( ---------+ + // | | + // +- get ------+ + // | | + // +- set ------+ + // + // This accepts some invalid syntax, but we don't care, since it's only + // used by the non-standard toSource, and we're doing a best-effort attempt + // here. + + (void)Consume(s, e, "async"); + ConsumeSpaces(s, e); + (void)(Consume(s, e, "function") || Consume(s, e, "get") || + Consume(s, e, "set")); + ConsumeSpaces(s, e); + (void)Consume(s, e, "*"); + ConsumeSpaces(s, e); + + // Jump over the function's name. + if (Consume(s, e, "[")) { + if (!ConsumeUntil(s, e, ']')) { + return false; + } + s++; // Skip ']'. + ConsumeSpaces(s, e); + if (s >= e || *s != '(') { + return false; + } + } else { + if (!ConsumeUntil(s, e, '(')) { + return false; + } + } + + MOZ_ASSERT(*s == '('); + + *outOffset = s - start; + *outLen = e - s; + MOZ_ASSERT(*outOffset + *outLen <= chars.length()); + return true; +} + +enum class PropertyKind { Getter, Setter, Method, Normal }; + +JSString* js::ObjectToSource(JSContext* cx, HandleObject obj) { + /* If outermost, we need parentheses to be an expression, not a block. */ + bool outermost = cx->cycleDetectorVector().empty(); + + AutoCycleDetector detector(cx, obj); + if (!detector.init()) { + return nullptr; + } + if (detector.foundCycle()) { + return NewStringCopyZ<CanGC>(cx, "{}"); + } + + JSStringBuilder buf(cx); + if (outermost && !buf.append('(')) { + return nullptr; + } + if (!buf.append('{')) { + return nullptr; + } + + RootedIdVector idv(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv)) { + return nullptr; + } + +#ifdef ENABLE_RECORD_TUPLE + if (IsExtendedPrimitiveWrapper(*obj)) { + if (obj->is<TupleObject>()) { + Rooted<TupleType*> tup(cx, &obj->as<TupleObject>().unbox()); + return TupleToSource(cx, tup); + } + MOZ_ASSERT(obj->is<RecordObject>()); + return RecordToSource(cx, obj->as<RecordObject>().unbox()); + } +#endif + + bool comma = false; + + auto AddProperty = [cx, &comma, &buf](HandleId id, HandleValue val, + PropertyKind kind) -> bool { + /* Convert id to a string. */ + RootedString idstr(cx); + if (id.isSymbol()) { + RootedValue v(cx, SymbolValue(id.toSymbol())); + idstr = ValueToSource(cx, v); + if (!idstr) { + return false; + } + } else { + RootedValue idv(cx, IdToValue(id)); + idstr = ToString<CanGC>(cx, idv); + if (!idstr) { + return false; + } + + /* + * If id is a string that's not an identifier, or if it's a + * negative integer, then it must be quoted. + */ + if (id.isAtom() ? !IsIdentifier(id.toAtom()) : id.toInt() < 0) { + UniqueChars quotedId = QuoteString(cx, idstr, '\''); + if (!quotedId) { + return false; + } + idstr = NewStringCopyZ<CanGC>(cx, quotedId.get()); + if (!idstr) { + return false; + } + } + } + + RootedString valsource(cx, ValueToSource(cx, val)); + if (!valsource) { + return false; + } + + Rooted<JSLinearString*> valstr(cx, valsource->ensureLinear(cx)); + if (!valstr) { + return false; + } + + if (comma && !buf.append(", ")) { + return false; + } + comma = true; + + size_t voffset, vlength; + + // Methods and accessors can return exact syntax of source, that fits + // into property without adding property name or "get"/"set" prefix. + // Use the exact syntax when the following conditions are met: + // + // * It's a function object + // (exclude proxies) + // * Function's kind and property's kind are same + // (this can be false for dynamically defined properties) + // * Function has explicit name + // (this can be false for computed property and dynamically defined + // properties) + // * Function's name and property's name are same + // (this can be false for dynamically defined properties) + if (kind == PropertyKind::Getter || kind == PropertyKind::Setter || + kind == PropertyKind::Method) { + RootedFunction fun(cx); + if (val.toObject().is<JSFunction>()) { + fun = &val.toObject().as<JSFunction>(); + // Method's case should be checked on caller. + if (((fun->isGetter() && kind == PropertyKind::Getter) || + (fun->isSetter() && kind == PropertyKind::Setter) || + kind == PropertyKind::Method) && + fun->explicitName()) { + bool result; + if (!EqualStrings(cx, fun->explicitName(), idstr, &result)) { + return false; + } + + if (result) { + if (!buf.append(valstr)) { + return false; + } + return true; + } + } + } + + { + // When falling back try to generate a better string + // representation by skipping the prelude, and also removing + // the enclosing parentheses. + bool success; + JS::AutoCheckCannotGC nogc; + if (valstr->hasLatin1Chars()) { + success = ArgsAndBodySubstring(valstr->latin1Range(nogc), &voffset, + &vlength); + } else { + success = ArgsAndBodySubstring(valstr->twoByteRange(nogc), &voffset, + &vlength); + } + if (!success) { + kind = PropertyKind::Normal; + } + } + + if (kind == PropertyKind::Getter) { + if (!buf.append("get ")) { + return false; + } + } else if (kind == PropertyKind::Setter) { + if (!buf.append("set ")) { + return false; + } + } else if (kind == PropertyKind::Method && fun) { + if (fun->isAsync()) { + if (!buf.append("async ")) { + return false; + } + } + + if (fun->isGenerator()) { + if (!buf.append('*')) { + return false; + } + } + } + } + + bool needsBracket = id.isSymbol(); + if (needsBracket && !buf.append('[')) { + return false; + } + if (!buf.append(idstr)) { + return false; + } + if (needsBracket && !buf.append(']')) { + return false; + } + + if (kind == PropertyKind::Getter || kind == PropertyKind::Setter || + kind == PropertyKind::Method) { + if (!buf.appendSubstring(valstr, voffset, vlength)) { + return false; + } + } else { + if (!buf.append(':')) { + return false; + } + if (!buf.append(valstr)) { + return false; + } + } + return true; + }; + + RootedId id(cx); + Rooted<Maybe<PropertyDescriptor>> desc(cx); + RootedValue val(cx); + for (size_t i = 0; i < idv.length(); ++i) { + id = idv[i]; + if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) { + return nullptr; + } + + if (desc.isNothing()) { + continue; + } + + if (desc->isAccessorDescriptor()) { + if (desc->hasGetter() && desc->getter()) { + val.setObject(*desc->getter()); + if (!AddProperty(id, val, PropertyKind::Getter)) { + return nullptr; + } + } + if (desc->hasSetter() && desc->setter()) { + val.setObject(*desc->setter()); + if (!AddProperty(id, val, PropertyKind::Setter)) { + return nullptr; + } + } + continue; + } + + val.set(desc->value()); + + JSFunction* fun = nullptr; + if (IsFunctionObject(val, &fun) && fun->isMethod()) { + if (!AddProperty(id, val, PropertyKind::Method)) { + return nullptr; + } + continue; + } + + if (!AddProperty(id, val, PropertyKind::Normal)) { + return nullptr; + } + } + + if (!buf.append('}')) { + return nullptr; + } + if (outermost && !buf.append(')')) { + return nullptr; + } + + return buf.finishString(); +} + +static JSString* GetBuiltinTagSlow(JSContext* cx, HandleObject obj) { + // Step 4. + bool isArray; + if (!IsArray(cx, obj, &isArray)) { + return nullptr; + } + + // Step 5. + if (isArray) { + return cx->names().objectArray; + } + + // Steps 6-14. + ESClass cls; + if (!JS::GetBuiltinClass(cx, obj, &cls)) { + return nullptr; + } + + switch (cls) { + case ESClass::String: + return cx->names().objectString; + case ESClass::Arguments: + return cx->names().objectArguments; + case ESClass::Error: + return cx->names().objectError; + case ESClass::Boolean: + return cx->names().objectBoolean; + case ESClass::Number: + return cx->names().objectNumber; + case ESClass::Date: + return cx->names().objectDate; + case ESClass::RegExp: + return cx->names().objectRegExp; + default: + if (obj->isCallable()) { + // Non-standard: Prevent <object> from showing up as Function. + JSObject* unwrapped = CheckedUnwrapDynamic(obj, cx); + if (!unwrapped || !unwrapped->getClass()->isDOMClass()) { + return cx->names().objectFunction; + } + } + return cx->names().objectObject; + } +} + +static MOZ_ALWAYS_INLINE JSString* GetBuiltinTagFast(JSObject* obj, + JSContext* cx) { + const JSClass* clasp = obj->getClass(); + MOZ_ASSERT(!clasp->isProxyObject()); + + // Optimize the non-proxy case to bypass GetBuiltinClass. + if (clasp == &PlainObject::class_) { + // This case is by far the most common so we handle it first. + return cx->names().objectObject; + } + + if (clasp == &ArrayObject::class_) { + return cx->names().objectArray; + } + + if (clasp->isJSFunction()) { + return cx->names().objectFunction; + } + + if (clasp == &StringObject::class_) { + return cx->names().objectString; + } + + if (clasp == &NumberObject::class_) { + return cx->names().objectNumber; + } + + if (clasp == &BooleanObject::class_) { + return cx->names().objectBoolean; + } + + if (clasp == &DateObject::class_) { + return cx->names().objectDate; + } + + if (clasp == &RegExpObject::class_) { + return cx->names().objectRegExp; + } + + if (obj->is<ArgumentsObject>()) { + return cx->names().objectArguments; + } + + if (obj->is<ErrorObject>()) { + return cx->names().objectError; + } + + if (obj->isCallable() && !obj->getClass()->isDOMClass()) { + // Non-standard: Prevent <object> from showing up as Function. + return cx->names().objectFunction; + } + + return cx->names().objectObject; +} + +// For primitive values we try to avoid allocating the object if we can +// determine that the prototype it would use does not define Symbol.toStringTag. +static JSAtom* MaybeObjectToStringPrimitive(JSContext* cx, const Value& v) { + JSProtoKey protoKey = js::PrimitiveToProtoKey(cx, v); + + // If prototype doesn't exist yet, just fall through. + JSObject* proto = cx->global()->maybeGetPrototype(protoKey); + if (!proto) { + return nullptr; + } + + // If determining this may have side-effects, we must instead create the + // object normally since it is the receiver while looking up + // Symbol.toStringTag. + if (MaybeHasInterestingSymbolProperty( + cx, proto, cx->wellKnownSymbols().toStringTag, nullptr)) { + return nullptr; + } + + // Return the direct result. + switch (protoKey) { + case JSProto_String: + return cx->names().objectString; + case JSProto_Number: + return cx->names().objectNumber; + case JSProto_Boolean: + return cx->names().objectBoolean; + case JSProto_Symbol: + return cx->names().objectSymbol; + case JSProto_BigInt: + return cx->names().objectBigInt; + default: + break; + } + + return nullptr; +} + +// ES6 19.1.3.6 +bool js::obj_toString(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object.prototype", "toString"); + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject obj(cx); + + if (args.thisv().isPrimitive()) { + // Step 1. + if (args.thisv().isUndefined()) { + args.rval().setString(cx->names().objectUndefined); + return true; + } + + // Step 2. + if (args.thisv().isNull()) { + args.rval().setString(cx->names().objectNull); + return true; + } + + // Try fast-path for primitives. This is unusual but we encounter code like + // this in the wild. + JSAtom* result = MaybeObjectToStringPrimitive(cx, args.thisv()); + if (result) { + args.rval().setString(result); + return true; + } + + // Step 3. + obj = ToObject(cx, args.thisv()); + if (!obj) { + return false; + } + } else { + obj = &args.thisv().toObject(); + } + + // When |obj| is a non-proxy object, compute |builtinTag| only when needed. + RootedString builtinTag(cx); + if (MOZ_UNLIKELY(obj->is<ProxyObject>())) { + builtinTag = GetBuiltinTagSlow(cx, obj); + if (!builtinTag) { + return false; + } + } + + // Step 15. + RootedValue tag(cx); + if (!GetInterestingSymbolProperty(cx, obj, cx->wellKnownSymbols().toStringTag, + &tag)) { + return false; + } + + // Step 16. + if (!tag.isString()) { + if (!builtinTag) { + builtinTag = GetBuiltinTagFast(obj, cx); +#ifdef DEBUG + // Assert this fast path is correct and matches BuiltinTagSlow. + JSString* builtinTagSlow = GetBuiltinTagSlow(cx, obj); + if (!builtinTagSlow) { + return false; + } + MOZ_ASSERT(builtinTagSlow == builtinTag); +#endif + } + + args.rval().setString(builtinTag); + return true; + } + + // Step 17. + StringBuffer sb(cx); + if (!sb.append("[object ") || !sb.append(tag.toString()) || !sb.append(']')) { + return false; + } + + JSString* str = sb.finishAtom(); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +JSString* js::ObjectClassToString(JSContext* cx, JSObject* obj) { + AutoUnsafeCallWithABI unsafe; + + if (MaybeHasInterestingSymbolProperty(cx, obj, + cx->wellKnownSymbols().toStringTag)) { + return nullptr; + } + return GetBuiltinTagFast(obj, cx); +} + +static bool obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "Object.setPrototypeOf", 2)) { + return false; + } + + /* Step 1-2. */ + if (args[0].isNullOrUndefined()) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, + args[0].isNull() ? "null" : "undefined", "object"); + return false; + } + + /* Step 3. */ + if (!args[1].isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NOT_EXPECTED_TYPE, "Object.setPrototypeOf", + "an object or null", + InformalValueTypeName(args[1])); + return false; + } + + /* Step 4. */ + if (!args[0].isObject()) { + args.rval().set(args[0]); + return true; + } + + /* Step 5-7. */ + RootedObject obj(cx, &args[0].toObject()); + RootedObject newProto(cx, args[1].toObjectOrNull()); + if (!SetPrototype(cx, obj, newProto)) { + return false; + } + + /* Step 8. */ + args.rval().set(args[0]); + return true; +} + +static bool PropertyIsEnumerable(JSContext* cx, HandleObject obj, HandleId id, + bool* enumerable) { + PropertyResult prop; + if (obj->is<NativeObject>() && + NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &prop)) { + if (prop.isNotFound()) { + *enumerable = false; + return true; + } + + JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop); + *enumerable = attrs.enumerable(); + return true; + } + + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) { + return false; + } + + *enumerable = desc.isSome() && desc->enumerable(); + return true; +} + +// Returns true if properties not named "__proto__" can be added to |obj| +// with a fast path that doesn't check any properties on the prototype chain. +static bool CanAddNewPropertyExcludingProtoFast(PlainObject* obj) { + if (!obj->isExtensible() || obj->isUsedAsPrototype()) { + return false; + } + + // Ensure the object has no non-writable properties or getters/setters. + // For now only support PlainObjects so that we don't have to worry about + // resolve hooks and other JSClass hooks. + while (true) { + if (obj->hasNonWritableOrAccessorPropExclProto()) { + return false; + } + + JSObject* proto = obj->staticPrototype(); + if (!proto) { + return true; + } + if (!proto->is<PlainObject>()) { + return false; + } + obj = &proto->as<PlainObject>(); + } +} + +[[nodiscard]] static bool TryAssignPlain(JSContext* cx, HandleObject to, + HandleObject from, bool* optimized) { + // Object.assign is used with PlainObjects most of the time. This is a fast + // path to optimize that case. This lets us avoid checks that are only + // relevant for other JSClasses. + + MOZ_ASSERT(*optimized == false); + + if (!from->is<PlainObject>() || !to->is<PlainObject>()) { + return true; + } + + // Don't use the fast path if |from| may have extra indexed properties. + Handle<PlainObject*> fromPlain = from.as<PlainObject>(); + if (fromPlain->getDenseInitializedLength() > 0 || fromPlain->isIndexed()) { + return true; + } + MOZ_ASSERT(!fromPlain->getClass()->getNewEnumerate()); + MOZ_ASSERT(!fromPlain->getClass()->getEnumerate()); + + // Empty |from| objects are common, so check for this first. + if (fromPlain->empty()) { + *optimized = true; + return true; + } + + Handle<PlainObject*> toPlain = to.as<PlainObject>(); + if (!CanAddNewPropertyExcludingProtoFast(toPlain)) { + return true; + } + + // Get a list of all enumerable |from| properties. + + Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx)); + +#ifdef DEBUG + Rooted<Shape*> fromShape(cx, fromPlain->shape()); +#endif + + bool hasPropsWithNonDefaultAttrs = false; + for (ShapePropertyIter<NoGC> iter(fromPlain->shape()); !iter.done(); iter++) { + // Symbol properties need to be assigned last. For now fall back to the + // slow path if we see a symbol property. + jsid id = iter->key(); + if (MOZ_UNLIKELY(id.isSymbol())) { + return true; + } + // __proto__ is not supported by CanAddNewPropertyExcludingProtoFast. + if (MOZ_UNLIKELY(id.isAtom(cx->names().proto))) { + return true; + } + if (MOZ_UNLIKELY(!iter->isDataProperty())) { + return true; + } + if (iter->flags() != PropertyFlags::defaultDataPropFlags) { + hasPropsWithNonDefaultAttrs = true; + } + if (!iter->enumerable()) { + continue; + } + if (MOZ_UNLIKELY(!props.append(*iter))) { + return false; + } + } + + *optimized = true; + + bool toWasEmpty = toPlain->empty(); + + // If the |to| object has no properties and the |from| object only has plain + // enumerable/writable/configurable data properties, try to use its shape. + if (toWasEmpty && !hasPropsWithNonDefaultAttrs && + toPlain->canReuseShapeForNewProperties(fromPlain->shape())) { + MOZ_ASSERT(!Watchtower::watchesPropertyAdd(toPlain), + "watched objects require Watchtower calls"); + SharedShape* newShape = fromPlain->sharedShape(); + uint32_t oldSpan = 0; + uint32_t newSpan = props.length(); + if (!toPlain->setShapeAndAddNewSlots(cx, newShape, oldSpan, newSpan)) { + return false; + } + for (size_t i = props.length(); i > 0; i--) { + size_t slot = props[i - 1].slot(); + toPlain->initSlot(slot, fromPlain->getSlot(slot)); + } + return true; + } + + RootedValue propValue(cx); + RootedId nextKey(cx); + + for (size_t i = props.length(); i > 0; i--) { + // Assert |from| still has the same properties. + MOZ_ASSERT(fromPlain->shape() == fromShape); + + PropertyInfoWithKey fromProp = props[i - 1]; + MOZ_ASSERT(fromProp.isDataProperty()); + MOZ_ASSERT(fromProp.enumerable()); + + nextKey = fromProp.key(); + propValue = fromPlain->getSlot(fromProp.slot()); + + Maybe<PropertyInfo> toProp; + if (toWasEmpty) { + MOZ_ASSERT(!toPlain->containsPure(nextKey)); + MOZ_ASSERT(toProp.isNothing()); + } else { + toProp = toPlain->lookup(cx, nextKey); + } + + if (toProp.isSome()) { + MOZ_ASSERT(toProp->isDataProperty()); + MOZ_ASSERT(toProp->writable()); + toPlain->setSlot(toProp->slot(), propValue); + } else { + if (!AddDataPropertyToPlainObject(cx, toPlain, nextKey, propValue)) { + return false; + } + } + } + + return true; +} + +static bool TryAssignNative(JSContext* cx, HandleObject to, HandleObject from, + bool* optimized) { + MOZ_ASSERT(*optimized == false); + + if (!from->is<NativeObject>() || !to->is<NativeObject>()) { + return true; + } + + // Don't use the fast path if |from| may have extra indexed or lazy + // properties. + NativeObject* fromNative = &from->as<NativeObject>(); + if (fromNative->getDenseInitializedLength() > 0 || fromNative->isIndexed() || + fromNative->is<TypedArrayObject>() || + fromNative->getClass()->getNewEnumerate() || + fromNative->getClass()->getEnumerate()) { + return true; + } + + // Get a list of |from| properties. As long as from->shape() == fromShape + // we can use this to speed up both the enumerability check and the GetProp. + + Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx)); + + Rooted<NativeShape*> fromShape(cx, fromNative->shape()); + for (ShapePropertyIter<NoGC> iter(fromShape); !iter.done(); iter++) { + // Symbol properties need to be assigned last. For now fall back to the + // slow path if we see a symbol property. + if (MOZ_UNLIKELY(iter->key().isSymbol())) { + return true; + } + if (MOZ_UNLIKELY(!props.append(*iter))) { + return false; + } + } + + *optimized = true; + + RootedValue propValue(cx); + RootedId nextKey(cx); + RootedValue toReceiver(cx, ObjectValue(*to)); + + for (size_t i = props.length(); i > 0; i--) { + PropertyInfoWithKey prop = props[i - 1]; + nextKey = prop.key(); + + // If |from| still has the same shape, it must still be a NativeObject with + // the properties in |props|. + if (MOZ_LIKELY(from->shape() == fromShape && prop.isDataProperty())) { + if (!prop.enumerable()) { + continue; + } + propValue = from->as<NativeObject>().getSlot(prop.slot()); + } else { + // |from| changed shape or the property is not a data property, so + // we have to do the slower enumerability check and GetProp. + bool enumerable; + if (!PropertyIsEnumerable(cx, from, nextKey, &enumerable)) { + return false; + } + if (!enumerable) { + continue; + } + if (!GetProperty(cx, from, from, nextKey, &propValue)) { + return false; + } + } + + ObjectOpResult result; + if (MOZ_UNLIKELY( + !SetProperty(cx, to, nextKey, propValue, toReceiver, result))) { + return false; + } + if (MOZ_UNLIKELY(!result.checkStrict(cx, to, nextKey))) { + return false; + } + } + + return true; +} + +static bool AssignSlow(JSContext* cx, HandleObject to, HandleObject from) { + // Step 4.b.ii. + RootedIdVector keys(cx); + if (!GetPropertyKeys( + cx, from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys)) { + return false; + } + + // Step 4.c. + RootedId nextKey(cx); + RootedValue propValue(cx); + for (size_t i = 0, len = keys.length(); i < len; i++) { + nextKey = keys[i]; + + // Step 4.c.i. + bool enumerable; + if (MOZ_UNLIKELY(!PropertyIsEnumerable(cx, from, nextKey, &enumerable))) { + return false; + } + if (!enumerable) { + continue; + } + + // Step 4.c.ii.1. + if (MOZ_UNLIKELY(!GetProperty(cx, from, from, nextKey, &propValue))) { + return false; + } + + // Step 4.c.ii.2. + if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue))) { + return false; + } + } + + return true; +} + +JS_PUBLIC_API bool JS_AssignObject(JSContext* cx, JS::HandleObject target, + JS::HandleObject src) { + bool optimized = false; + + if (!TryAssignPlain(cx, target, src, &optimized)) { + return false; + } + if (optimized) { + return true; + } + + if (!TryAssignNative(cx, target, src, &optimized)) { + return false; + } + if (optimized) { + return true; + } + + return AssignSlow(cx, target, src); +} + +// ES2018 draft rev 48ad2688d8f964da3ea8c11163ef20eb126fb8a4 +// 19.1.2.1 Object.assign(target, ...sources) +static bool obj_assign(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "assign"); + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + RootedObject to(cx, ToObject(cx, args.get(0))); + if (!to) { + return false; + } + + // Note: step 2 is implicit. If there are 0 arguments, ToObject throws. If + // there's 1 argument, the loop below is a no-op. + + // Step 4. + RootedObject from(cx); + for (size_t i = 1; i < args.length(); i++) { + // Step 4.a. + if (args[i].isNullOrUndefined()) { + continue; + } + + // Step 4.b.i. + from = ToObject(cx, args[i]); + if (!from) { + return false; + } + + // Steps 4.b.ii, 4.c. + if (!JS_AssignObject(cx, to, from)) { + return false; + } + } + + // Step 5. + args.rval().setObject(*to); + return true; +} + +/* ES5 15.2.4.6. */ +bool js::obj_isPrototypeOf(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + /* Step 1. */ + if (args.length() < 1 || !args[0].isObject()) { + args.rval().setBoolean(false); + return true; + } + + /* Step 2. */ + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) { + return false; + } + + /* Step 3. */ + bool isPrototype; + if (!IsPrototypeOf(cx, obj, &args[0].toObject(), &isPrototype)) { + return false; + } + args.rval().setBoolean(isPrototype); + return true; +} + +PlainObject* js::ObjectCreateImpl(JSContext* cx, HandleObject proto, + NewObjectKind newKind) { + // Give the new object a small number of fixed slots, like we do for empty + // object literals ({}). + gc::AllocKind allocKind = NewObjectGCKind(); + return NewPlainObjectWithProtoAndAllocKind(cx, proto, allocKind, newKind); +} + +PlainObject* js::ObjectCreateWithTemplate(JSContext* cx, + Handle<PlainObject*> templateObj) { + RootedObject proto(cx, templateObj->staticPrototype()); + return ObjectCreateImpl(cx, proto, GenericObject); +} + +// ES 2017 draft 19.1.2.3.1 +static bool ObjectDefineProperties(JSContext* cx, HandleObject obj, + HandleValue properties, + bool* failedOnWindowProxy) { + // Step 1. implicit + // Step 2. + RootedObject props(cx, ToObject(cx, properties)); + if (!props) { + return false; + } + + // Step 3. + RootedIdVector keys(cx); + if (!GetPropertyKeys( + cx, props, JSITER_OWNONLY | JSITER_SYMBOLS | JSITER_HIDDEN, &keys)) { + return false; + } + + RootedId nextKey(cx); + Rooted<Maybe<PropertyDescriptor>> keyDesc(cx); + Rooted<PropertyDescriptor> desc(cx); + RootedValue descObj(cx); + + // Step 4. + Rooted<PropertyDescriptorVector> descriptors(cx, + PropertyDescriptorVector(cx)); + RootedIdVector descriptorKeys(cx); + + // Step 5. + for (size_t i = 0, len = keys.length(); i < len; i++) { + nextKey = keys[i]; + + // Step 5.a. + if (!GetOwnPropertyDescriptor(cx, props, nextKey, &keyDesc)) { + return false; + } + + // Step 5.b. + if (keyDesc.isSome() && keyDesc->enumerable()) { + if (!GetProperty(cx, props, props, nextKey, &descObj) || + !ToPropertyDescriptor(cx, descObj, true, &desc) || + !descriptors.append(desc) || !descriptorKeys.append(nextKey)) { + return false; + } + } + } + + // Step 6. + *failedOnWindowProxy = false; + for (size_t i = 0, len = descriptors.length(); i < len; i++) { + ObjectOpResult result; + if (!DefineProperty(cx, obj, descriptorKeys[i], descriptors[i], result)) { + return false; + } + + if (!result.ok()) { + if (result.failureCode() == JSMSG_CANT_DEFINE_WINDOW_NC) { + *failedOnWindowProxy = true; + } else if (!result.checkStrict(cx, obj, descriptorKeys[i])) { + return false; + } + } + } + + return true; +} + +// ES6 draft rev34 (2015/02/20) 19.1.2.2 Object.create(O [, Properties]) +bool js::obj_create(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!args.requireAtLeast(cx, "Object.create", 1)) { + return false; + } + + if (!args[0].isObjectOrNull()) { + UniqueChars bytes = + DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args[0], nullptr); + if (!bytes) { + return false; + } + + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_UNEXPECTED_TYPE, bytes.get(), + "not an object or null"); + return false; + } + + // Step 2. + RootedObject proto(cx, args[0].toObjectOrNull()); + Rooted<PlainObject*> obj(cx, ObjectCreateImpl(cx, proto)); + if (!obj) { + return false; + } + + // Step 3. + if (args.hasDefined(1)) { + // we can't ever end up with failures to define on a WindowProxy + // here, because "obj" is never a WindowProxy. + bool failedOnWindowProxy = false; + if (!ObjectDefineProperties(cx, obj, args[1], &failedOnWindowProxy)) { + return false; + } + MOZ_ASSERT(!failedOnWindowProxy, "How did we get a WindowProxy here?"); + } + + // Step 4. + args.rval().setObject(*obj); + return true; +} + +// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e +// 6.2.4.4 FromPropertyDescriptor ( Desc ) +static bool FromPropertyDescriptorToArray( + JSContext* cx, Handle<Maybe<PropertyDescriptor>> desc, + MutableHandleValue vp) { + // Step 1. + if (desc.isNothing()) { + vp.setUndefined(); + return true; + } + + // Steps 2-11. + // Retrieve all property descriptor fields and place them into the result + // array. The actual return object is created in self-hosted code for + // performance reasons. + + int32_t attrsAndKind = 0; + if (desc->enumerable()) { + attrsAndKind |= ATTR_ENUMERABLE; + } + if (desc->configurable()) { + attrsAndKind |= ATTR_CONFIGURABLE; + } + if (!desc->isAccessorDescriptor()) { + if (desc->writable()) { + attrsAndKind |= ATTR_WRITABLE; + } + attrsAndKind |= DATA_DESCRIPTOR_KIND; + } else { + attrsAndKind |= ACCESSOR_DESCRIPTOR_KIND; + } + + Rooted<ArrayObject*> result(cx); + if (!desc->isAccessorDescriptor()) { + result = NewDenseFullyAllocatedArray(cx, 2); + if (!result) { + return false; + } + result->setDenseInitializedLength(2); + + result->initDenseElement(PROP_DESC_ATTRS_AND_KIND_INDEX, + Int32Value(attrsAndKind)); + result->initDenseElement(PROP_DESC_VALUE_INDEX, desc->value()); + } else { + result = NewDenseFullyAllocatedArray(cx, 3); + if (!result) { + return false; + } + result->setDenseInitializedLength(3); + + result->initDenseElement(PROP_DESC_ATTRS_AND_KIND_INDEX, + Int32Value(attrsAndKind)); + + if (JSObject* get = desc->getter()) { + result->initDenseElement(PROP_DESC_GETTER_INDEX, ObjectValue(*get)); + } else { + result->initDenseElement(PROP_DESC_GETTER_INDEX, UndefinedValue()); + } + + if (JSObject* set = desc->setter()) { + result->initDenseElement(PROP_DESC_SETTER_INDEX, ObjectValue(*set)); + } else { + result->initDenseElement(PROP_DESC_SETTER_INDEX, UndefinedValue()); + } + } + + vp.setObject(*result); + return true; +} + +// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e +// 19.1.2.6 Object.getOwnPropertyDescriptor ( O, P ) +bool js::GetOwnPropertyDescriptorToArray(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + + // Step 1. + RootedObject obj(cx, ToObject(cx, args[0])); + if (!obj) { + return false; + } + + // Step 2. + RootedId id(cx); + if (!ToPropertyKey(cx, args[1], &id)) { + return false; + } + + // Step 3. + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) { + return false; + } + + // Step 4. + return FromPropertyDescriptorToArray(cx, desc, args.rval()); +} + +static bool NewValuePair(JSContext* cx, HandleValue val1, HandleValue val2, + MutableHandleValue rval) { + ArrayObject* array = NewDenseFullyAllocatedArray(cx, 2); + if (!array) { + return false; + } + + array->setDenseInitializedLength(2); + array->initDenseElement(0, val1); + array->initDenseElement(1, val2); + + rval.setObject(*array); + return true; +} + +enum class EnumerableOwnPropertiesKind { Keys, Values, KeysAndValues, Names }; + +static bool HasEnumerableStringNonDataProperties(NativeObject* obj) { + // We also check for enumerability and symbol properties, so uninteresting + // non-data properties like |array.length| don't let us fall into the slow + // path. + if (!obj->hasEnumerableProperty()) { + return false; + } + for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) { + if (!iter->isDataProperty() && iter->enumerable() && + !iter->key().isSymbol()) { + return true; + } + } + return false; +} + +template <EnumerableOwnPropertiesKind kind> +static bool TryEnumerableOwnPropertiesNative(JSContext* cx, HandleObject obj, + MutableHandleValue rval, + bool* optimized) { + *optimized = false; + + // Use the fast path if |obj| has neither extra indexed properties nor a + // newEnumerate hook. String objects need to be special-cased, because + // they're only marked as indexed after their enumerate hook ran. And + // because their enumerate hook is slowish, it's more performant to + // exclude them directly instead of executing the hook first. + if (!obj->is<NativeObject>() || obj->as<NativeObject>().isIndexed() || + obj->getClass()->getNewEnumerate() || obj->is<StringObject>()) { + return true; + } + +#ifdef ENABLE_RECORD_TUPLE + if (obj->is<TupleObject>()) { + Rooted<TupleType*> tup(cx, &obj->as<TupleObject>().unbox()); + return TryEnumerableOwnPropertiesNative<kind>(cx, tup, rval, optimized); + } else if (obj->is<RecordObject>()) { + Rooted<RecordType*> tup(cx, obj->as<RecordObject>().unbox()); + return TryEnumerableOwnPropertiesNative<kind>(cx, tup, rval, optimized); + } +#endif + + Handle<NativeObject*> nobj = obj.as<NativeObject>(); + + // Resolve lazy properties on |nobj|. + if (JSEnumerateOp enumerate = nobj->getClass()->getEnumerate()) { + if (!enumerate(cx, nobj)) { + return false; + } + + // Ensure no extra indexed properties were added through enumerate(). + if (nobj->isIndexed()) { + return true; + } + } + + *optimized = true; + + RootedValueVector properties(cx); + RootedValue key(cx); + RootedValue value(cx); + + // We have ensured |nobj| contains no extra indexed properties, so the + // only indexed properties we need to handle here are dense and typed + // array elements. + + for (uint32_t i = 0, len = nobj->getDenseInitializedLength(); i < len; i++) { + value.set(nobj->getDenseElement(i)); + if (value.isMagic(JS_ELEMENTS_HOLE)) { + continue; + } + + JSString* str; + if (kind != EnumerableOwnPropertiesKind::Values) { + static_assert( + NativeObject::MAX_DENSE_ELEMENTS_COUNT <= PropertyKey::IntMax, + "dense elements don't exceed PropertyKey::IntMax"); + str = Int32ToString<CanGC>(cx, i); + if (!str) { + return false; + } + } + + if (kind == EnumerableOwnPropertiesKind::Keys || + kind == EnumerableOwnPropertiesKind::Names) { + value.setString(str); + } else if (kind == EnumerableOwnPropertiesKind::KeysAndValues) { + key.setString(str); + if (!NewValuePair(cx, key, value, &value)) { + return false; + } + } + + if (!properties.append(value)) { + return false; + } + } + + if (obj->is<TypedArrayObject>()) { + Handle<TypedArrayObject*> tobj = obj.as<TypedArrayObject>(); + size_t len = tobj->length(); + + // Fail early if the typed array contains too many elements for a + // dense array, because we likely OOM anyway when trying to allocate + // more than 2GB for the properties vector. This also means we don't + // need to handle indices greater than MAX_INT32 in the loop below. + if (len > NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(properties.empty(), "typed arrays cannot have dense elements"); + if (!properties.resize(len)) { + return false; + } + + for (uint32_t i = 0; i < len; i++) { + JSString* str; + if (kind != EnumerableOwnPropertiesKind::Values) { + static_assert( + NativeObject::MAX_DENSE_ELEMENTS_COUNT <= PropertyKey::IntMax, + "dense elements don't exceed PropertyKey::IntMax"); + str = Int32ToString<CanGC>(cx, i); + if (!str) { + return false; + } + } + + if (kind == EnumerableOwnPropertiesKind::Keys || + kind == EnumerableOwnPropertiesKind::Names) { + value.setString(str); + } else if (kind == EnumerableOwnPropertiesKind::Values) { + if (!tobj->getElement<CanGC>(cx, i, &value)) { + return false; + } + } else { + key.setString(str); + if (!tobj->getElement<CanGC>(cx, i, &value)) { + return false; + } + if (!NewValuePair(cx, key, value, &value)) { + return false; + } + } + + properties[i].set(value); + } + } +#ifdef ENABLE_RECORD_TUPLE + else if (obj->is<RecordType>()) { + RecordType* rec = &obj->as<RecordType>(); + Rooted<ArrayObject*> keys(cx, rec->keys()); + RootedId keyId(cx); + RootedString keyStr(cx); + + MOZ_ASSERT(properties.empty(), "records cannot have dense elements"); + if (!properties.resize(keys->length())) { + return false; + } + + for (size_t i = 0; i < keys->length(); i++) { + MOZ_ASSERT(keys->getDenseElement(i).isString()); + if (kind == EnumerableOwnPropertiesKind::Keys || + kind == EnumerableOwnPropertiesKind::Names) { + value.set(keys->getDenseElement(i)); + } else if (kind == EnumerableOwnPropertiesKind::Values) { + keyStr.set(keys->getDenseElement(i).toString()); + + if (!JS_StringToId(cx, keyStr, &keyId)) { + return false; + } + MOZ_ALWAYS_TRUE(rec->getOwnProperty(cx, keyId, &value)); + } else { + MOZ_ASSERT(kind == EnumerableOwnPropertiesKind::KeysAndValues); + + key.set(keys->getDenseElement(i)); + keyStr.set(key.toString()); + + if (!JS_StringToId(cx, keyStr, &keyId)) { + return false; + } + MOZ_ALWAYS_TRUE(rec->getOwnProperty(cx, keyId, &value)); + + if (!NewValuePair(cx, key, value, &value)) { + return false; + } + } + + properties[i].set(value); + } + + // Uh, goto... When using records, we already get the (sorted) properties + // from its sorted keys, so we don't read them again as "own properties". + // We could use an `if` or some refactoring to skip the next logic, but + // goto makes it easer to keep the logic separated in + // "#ifdef ENABLE_RECORD_TUPLE" blocks. + // This should be refactored when the #ifdefs are removed. + goto end; + } +#endif + + // Up to this point no side-effects through accessor properties are + // possible which could have replaced |obj| with a non-native object. + MOZ_ASSERT(obj->is<NativeObject>()); + + if (kind == EnumerableOwnPropertiesKind::Keys || + kind == EnumerableOwnPropertiesKind::Names || + !HasEnumerableStringNonDataProperties(nobj)) { + // If |kind == Values| or |kind == KeysAndValues|: + // All enumerable properties with string property keys are data + // properties. This allows us to collect the property values while + // iterating over the shape hierarchy without worrying over accessors + // modifying any state. + + constexpr bool onlyEnumerable = kind != EnumerableOwnPropertiesKind::Names; + if (!onlyEnumerable || nobj->hasEnumerableProperty()) { + size_t elements = properties.length(); + constexpr AllowGC allowGC = + kind != EnumerableOwnPropertiesKind::KeysAndValues ? AllowGC::NoGC + : AllowGC::CanGC; + mozilla::Maybe<ShapePropertyIter<allowGC>> m; + if constexpr (allowGC == AllowGC::NoGC) { + m.emplace(nobj->shape()); + } else { + m.emplace(cx, nobj->shape()); + } + for (auto& iter = m.ref(); !iter.done(); iter++) { + jsid id = iter->key(); + if ((onlyEnumerable && !iter->enumerable()) || id.isSymbol()) { + continue; + } + MOZ_ASSERT(!id.isInt(), "Unexpected indexed property"); + MOZ_ASSERT_IF(kind == EnumerableOwnPropertiesKind::Values || + kind == EnumerableOwnPropertiesKind::KeysAndValues, + iter->isDataProperty()); + + if constexpr (kind == EnumerableOwnPropertiesKind::Keys || + kind == EnumerableOwnPropertiesKind::Names) { + value.setString(id.toString()); + } else if constexpr (kind == EnumerableOwnPropertiesKind::Values) { + value.set(nobj->getSlot(iter->slot())); + } else { + key.setString(id.toString()); + value.set(nobj->getSlot(iter->slot())); + if (!NewValuePair(cx, key, value, &value)) { + return false; + } + } + + if (!properties.append(value)) { + return false; + } + } + + // The (non-indexed) properties were visited in reverse iteration order, + // call std::reverse() to ensure they appear in iteration order. + std::reverse(properties.begin() + elements, properties.end()); + } + } else { + MOZ_ASSERT(kind == EnumerableOwnPropertiesKind::Values || + kind == EnumerableOwnPropertiesKind::KeysAndValues); + + // Get a list of all |obj| properties. As long as obj->shape() + // is equal to |objShape|, we can use this to speed up both the + // enumerability check and GetProperty. + Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx)); + + // Collect all non-symbol properties. + Rooted<NativeShape*> objShape(cx, nobj->shape()); + for (ShapePropertyIter<NoGC> iter(objShape); !iter.done(); iter++) { + if (iter->key().isSymbol()) { + continue; + } + MOZ_ASSERT(!iter->key().isInt(), "Unexpected indexed property"); + + if (!props.append(*iter)) { + return false; + } + } + + RootedId id(cx); + for (size_t i = props.length(); i > 0; i--) { + PropertyInfoWithKey prop = props[i - 1]; + id = prop.key(); + + // If |obj| still has the same shape, it must still be a NativeObject with + // the properties in |props|. + if (obj->shape() == objShape && prop.isDataProperty()) { + if (!prop.enumerable()) { + continue; + } + value = obj->as<NativeObject>().getSlot(prop.slot()); + } else { + // |obj| changed shape or the property is not a data property, + // so we have to do the slower enumerability check and + // GetProperty. + bool enumerable; + if (!PropertyIsEnumerable(cx, obj, id, &enumerable)) { + return false; + } + if (!enumerable) { + continue; + } + if (!GetProperty(cx, obj, obj, id, &value)) { + return false; + } + } + + if (kind == EnumerableOwnPropertiesKind::KeysAndValues) { + key.setString(id.toString()); + if (!NewValuePair(cx, key, value, &value)) { + return false; + } + } + + if (!properties.append(value)) { + return false; + } + } + } + +#ifdef ENABLE_RECORD_TUPLE +end: +#endif + + JSObject* array = + NewDenseCopiedArray(cx, properties.length(), properties.begin()); + if (!array) { + return false; + } + + rval.setObject(*array); + return true; +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 7.3.21 EnumerableOwnProperties ( O, kind ) +template <EnumerableOwnPropertiesKind kind> +static bool EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args) { + static_assert(kind == EnumerableOwnPropertiesKind::Values || + kind == EnumerableOwnPropertiesKind::KeysAndValues, + "Only implemented for Object.keys and Object.entries"); + + // Step 1. (Step 1 of Object.{keys,values,entries}, really.) + RootedObject obj(cx, IF_RECORD_TUPLE(ToObjectOrGetObjectPayload, ToObject)( + cx, args.get(0))); + if (!obj) { + return false; + } + + bool optimized; + if (!TryEnumerableOwnPropertiesNative<kind>(cx, obj, args.rval(), + &optimized)) { + return false; + } + if (optimized) { + return true; + } + + // Typed arrays are always handled in the fast path. + MOZ_ASSERT(!obj->is<TypedArrayObject>()); + + // Step 2. + RootedIdVector ids(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids)) { + return false; + } + + // Step 3. + RootedValueVector properties(cx); + size_t len = ids.length(); + if (!properties.resize(len)) { + return false; + } + + RootedId id(cx); + RootedValue key(cx); + RootedValue value(cx); + Rooted<Shape*> shape(cx); + Rooted<Maybe<PropertyDescriptor>> desc(cx); + // Step 4. + size_t out = 0; + for (size_t i = 0; i < len; i++) { + id = ids[i]; + + // Step 4.a. (Symbols were filtered out in step 2.) + MOZ_ASSERT(!id.isSymbol()); + + if (kind != EnumerableOwnPropertiesKind::Values) { + if (!IdToStringOrSymbol(cx, id, &key)) { + return false; + } + } + + // Step 4.a.i. + if (obj->is<NativeObject>()) { + Handle<NativeObject*> nobj = obj.as<NativeObject>(); + if (id.isInt() && nobj->containsDenseElement(id.toInt())) { + value.set(nobj->getDenseElement(id.toInt())); + } else { + Maybe<PropertyInfo> prop = nobj->lookup(cx, id); + if (prop.isNothing() || !prop->enumerable()) { + continue; + } + if (prop->isDataProperty()) { + value = nobj->getSlot(prop->slot()); + } else if (!GetProperty(cx, obj, obj, id, &value)) { + return false; + } + } + } else { + if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) { + return false; + } + + // Step 4.a.ii. (inverted.) + if (desc.isNothing() || !desc->enumerable()) { + continue; + } + + // Step 4.a.ii.1. + // (Omitted because Object.keys doesn't use this implementation.) + + // Step 4.a.ii.2.a. + if (!GetProperty(cx, obj, obj, id, &value)) { + return false; + } + } + + // Steps 4.a.ii.2.b-c. + if (kind == EnumerableOwnPropertiesKind::Values) { + properties[out++].set(value); + } else if (!NewValuePair(cx, key, value, properties[out++])) { + return false; + } + } + + // Step 5. + // (Implemented in step 2.) + + // Step 3 of Object.{keys,values,entries} + JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin()); + if (!aobj) { + return false; + } + + args.rval().setObject(*aobj); + return true; +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 19.1.2.16 Object.keys ( O ) +static bool obj_keys(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "keys"); + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + RootedObject obj(cx, IF_RECORD_TUPLE(ToObjectOrGetObjectPayload, ToObject)( + cx, args.get(0))); + if (!obj) { + return false; + } + + bool optimized; + static constexpr EnumerableOwnPropertiesKind kind = + EnumerableOwnPropertiesKind::Keys; + if (!TryEnumerableOwnPropertiesNative<kind>(cx, obj, args.rval(), + &optimized)) { + return false; + } + if (optimized) { + return true; + } + + // Steps 2-3. + return GetOwnPropertyKeys(cx, obj, JSITER_OWNONLY, args.rval()); +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 19.1.2.21 Object.values ( O ) +static bool obj_values(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "values"); + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-3. + return EnumerableOwnProperties<EnumerableOwnPropertiesKind::Values>(cx, args); +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 19.1.2.5 Object.entries ( O ) +static bool obj_entries(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "entries"); + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-3. + return EnumerableOwnProperties<EnumerableOwnPropertiesKind::KeysAndValues>( + cx, args); +} + +/* ES6 draft 15.2.3.16 */ +bool js::obj_is(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + bool same; + if (!SameValue(cx, args.get(0), args.get(1), &same)) { + return false; + } + + args.rval().setBoolean(same); + return true; +} + +bool js::IdToStringOrSymbol(JSContext* cx, HandleId id, + MutableHandleValue result) { + if (id.isInt()) { + JSString* str = Int32ToString<CanGC>(cx, id.toInt()); + if (!str) { + return false; + } + result.setString(str); + } else if (id.isAtom()) { + result.setString(id.toAtom()); + } else { + result.setSymbol(id.toSymbol()); + } + return true; +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 19.1.2.10.1 Runtime Semantics: GetOwnPropertyKeys ( O, Type ) +bool js::GetOwnPropertyKeys(JSContext* cx, HandleObject obj, unsigned flags, + MutableHandleValue rval) { + // Step 1 (Performed in caller). + + // Steps 2-4. + RootedIdVector keys(cx); + if (!GetPropertyKeys(cx, obj, flags, &keys)) { + return false; + } + + // Step 5 (Inlined CreateArrayFromList). + Rooted<ArrayObject*> array(cx, + NewDenseFullyAllocatedArray(cx, keys.length())); + if (!array) { + return false; + } + + array->ensureDenseInitializedLength(0, keys.length()); + + RootedValue val(cx); + for (size_t i = 0, len = keys.length(); i < len; i++) { + MOZ_ASSERT_IF(keys[i].isSymbol(), flags & JSITER_SYMBOLS); + MOZ_ASSERT_IF(!keys[i].isSymbol(), !(flags & JSITER_SYMBOLSONLY)); + if (!IdToStringOrSymbol(cx, keys[i], &val)) { + return false; + } + array->initDenseElement(i, val); + } + + rval.setObject(*array); + return true; +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 19.1.2.9 Object.getOwnPropertyNames ( O ) +static bool obj_getOwnPropertyNames(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "getOwnPropertyNames"); + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) { + return false; + } + + bool optimized; + static constexpr EnumerableOwnPropertiesKind kind = + EnumerableOwnPropertiesKind::Names; + if (!TryEnumerableOwnPropertiesNative<kind>(cx, obj, args.rval(), + &optimized)) { + return false; + } + if (optimized) { + return true; + } + + return GetOwnPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, + args.rval()); +} + +// ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f +// 19.1.2.10 Object.getOwnPropertySymbols ( O ) +static bool obj_getOwnPropertySymbols(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "getOwnPropertySymbols"); + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) { + return false; + } + + return GetOwnPropertyKeys( + cx, obj, + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY, + args.rval()); +} + +/* ES5 15.2.3.7: Object.defineProperties(O, Properties) */ +static bool obj_defineProperties(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "Object", "defineProperties"); + CallArgs args = CallArgsFromVp(argc, vp); + + /* Step 1. */ + RootedObject obj(cx); + if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperties", &obj)) { + return false; + } + + /* Step 2. */ + if (!args.requireAtLeast(cx, "Object.defineProperties", 2)) { + return false; + } + + /* Steps 3-6. */ + bool failedOnWindowProxy = false; + if (!ObjectDefineProperties(cx, obj, args[1], &failedOnWindowProxy)) { + return false; + } + + /* Step 7, but modified to deal with WindowProxy mess */ + if (failedOnWindowProxy) { + args.rval().setNull(); + } else { + args.rval().setObject(*obj); + } + return true; +} + +// ES6 20141014 draft 19.1.2.15 Object.preventExtensions(O) +static bool obj_preventExtensions(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().set(args.get(0)); + + // Step 1. + if (!args.get(0).isObject()) { + return true; + } + + // Steps 2-5. + RootedObject obj(cx, &args.get(0).toObject()); + return PreventExtensions(cx, obj); +} + +// ES6 draft rev27 (2014/08/24) 19.1.2.5 Object.freeze(O) +static bool obj_freeze(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().set(args.get(0)); + + // Step 1. + if (!args.get(0).isObject()) { + return true; + } + + // Steps 2-5. + RootedObject obj(cx, &args.get(0).toObject()); + return SetIntegrityLevel(cx, obj, IntegrityLevel::Frozen); +} + +// ES6 draft rev27 (2014/08/24) 19.1.2.12 Object.isFrozen(O) +static bool obj_isFrozen(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + bool frozen = true; + + // Step 2. + if (args.get(0).isObject()) { + RootedObject obj(cx, &args.get(0).toObject()); + if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &frozen)) { + return false; + } + } + args.rval().setBoolean(frozen); + return true; +} + +// ES6 draft rev27 (2014/08/24) 19.1.2.17 Object.seal(O) +static bool obj_seal(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().set(args.get(0)); + + // Step 1. + if (!args.get(0).isObject()) { + return true; + } + + // Steps 2-5. + RootedObject obj(cx, &args.get(0).toObject()); + return SetIntegrityLevel(cx, obj, IntegrityLevel::Sealed); +} + +// ES6 draft rev27 (2014/08/24) 19.1.2.13 Object.isSealed(O) +static bool obj_isSealed(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + bool sealed = true; + + // Step 2. + if (args.get(0).isObject()) { + RootedObject obj(cx, &args.get(0).toObject()); + if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Sealed, &sealed)) { + return false; + } + } + args.rval().setBoolean(sealed); + return true; +} + +bool js::obj_setProto(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + + HandleValue thisv = args.thisv(); + if (thisv.isNullOrUndefined()) { + ReportIncompatible(cx, args); + return false; + } + if (thisv.isPrimitive()) { + // Mutating a boxed primitive's [[Prototype]] has no side effects. + args.rval().setUndefined(); + return true; + } + + /* Do nothing if __proto__ isn't being set to an object or null. */ + if (!args[0].isObjectOrNull()) { + args.rval().setUndefined(); + return true; + } + + Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull()); + if (!SetPrototype(cx, obj, newProto)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +static const JSFunctionSpec object_methods[] = { + JS_FN(js_toSource_str, obj_toSource, 0, 0), + JS_INLINABLE_FN(js_toString_str, obj_toString, 0, 0, ObjectToString), + JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0), + JS_SELF_HOSTED_FN(js_valueOf_str, "Object_valueOf", 0, 0), + JS_SELF_HOSTED_FN(js_hasOwnProperty_str, "Object_hasOwnProperty", 1, 0), + JS_INLINABLE_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1, 0, + ObjectIsPrototypeOf), + JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1, 0), + JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter", 2, 0), + JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter", 2, 0), + JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter", 1, 0), + JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter", 1, 0), + JS_FS_END}; + +static const JSPropertySpec object_properties[] = { + JS_SELF_HOSTED_GETSET("__proto__", "$ObjectProtoGetter", + "$ObjectProtoSetter", 0), + JS_PS_END}; + +static const JSFunctionSpec object_static_methods[] = { + JS_FN("assign", obj_assign, 2, 0), + JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0), + JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0), + JS_SELF_HOSTED_FN("getOwnPropertyDescriptor", + "ObjectGetOwnPropertyDescriptor", 2, 0), + JS_SELF_HOSTED_FN("getOwnPropertyDescriptors", + "ObjectGetOwnPropertyDescriptors", 1, 0), + JS_FN("keys", obj_keys, 1, 0), + JS_FN("values", obj_values, 1, 0), + JS_FN("entries", obj_entries, 1, 0), + JS_INLINABLE_FN("is", obj_is, 2, 0, ObjectIs), + JS_SELF_HOSTED_FN("defineProperty", "ObjectDefineProperty", 3, 0), + JS_FN("defineProperties", obj_defineProperties, 2, 0), + JS_INLINABLE_FN("create", obj_create, 2, 0, ObjectCreate), + JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1, 0), + JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 1, 0), + JS_SELF_HOSTED_FN("isExtensible", "ObjectIsExtensible", 1, 0), + JS_FN("preventExtensions", obj_preventExtensions, 1, 0), + JS_FN("freeze", obj_freeze, 1, 0), + JS_FN("isFrozen", obj_isFrozen, 1, 0), + JS_FN("seal", obj_seal, 1, 0), + JS_FN("isSealed", obj_isSealed, 1, 0), + JS_SELF_HOSTED_FN("fromEntries", "ObjectFromEntries", 1, 0), + JS_SELF_HOSTED_FN("hasOwn", "ObjectHasOwn", 2, 0), + JS_FS_END}; + +static JSObject* CreateObjectConstructor(JSContext* cx, JSProtoKey key) { + Rooted<GlobalObject*> self(cx, cx->global()); + if (!GlobalObject::ensureConstructor(cx, self, JSProto_Function)) { + return nullptr; + } + + /* Create the Object function now that we have a [[Prototype]] for it. */ + JSFunction* fun = NewNativeConstructor( + cx, obj_construct, 1, Handle<PropertyName*>(cx->names().Object), + gc::AllocKind::FUNCTION, TenuredObject); + if (!fun) { + return nullptr; + } + + fun->setJitInfo(&jit::JitInfo_Object); + return fun; +} + +static JSObject* CreateObjectPrototype(JSContext* cx, JSProtoKey key) { + MOZ_ASSERT(!cx->zone()->isAtomsZone()); + MOZ_ASSERT(cx->global()->is<NativeObject>()); + + /* + * Create |Object.prototype| first, mirroring CreateBlankProto but for the + * prototype of the created object. + */ + Rooted<PlainObject*> objectProto( + cx, NewPlainObjectWithProto(cx, nullptr, TenuredObject)); + if (!objectProto) { + return nullptr; + } + + bool succeeded; + if (!SetImmutablePrototype(cx, objectProto, &succeeded)) { + return nullptr; + } + MOZ_ASSERT(succeeded, + "should have been able to make a fresh Object.prototype's " + "[[Prototype]] immutable"); + + return objectProto; +} + +static bool FinishObjectClassInit(JSContext* cx, JS::HandleObject ctor, + JS::HandleObject proto) { + Rooted<GlobalObject*> global(cx, cx->global()); + + // ES5 15.1.2.1. + RootedId evalId(cx, NameToId(cx->names().eval)); + JSFunction* evalobj = + DefineFunction(cx, global, evalId, IndirectEval, 1, JSPROP_RESOLVING); + if (!evalobj) { + return false; + } + global->setOriginalEval(evalobj); + +#ifdef FUZZING + if (cx->options().fuzzing()) { + if (!DefineTestingFunctions(cx, global, /* fuzzingSafe = */ true, + /* disableOOMFunctions = */ false)) { + return false; + } + } +#endif + + // The global object should have |Object.prototype| as its [[Prototype]]. + MOZ_ASSERT(global->staticPrototype() == nullptr); + MOZ_ASSERT(!global->staticPrototypeIsImmutable()); + return SetPrototype(cx, global, proto); +} + +static const ClassSpec PlainObjectClassSpec = { + CreateObjectConstructor, CreateObjectPrototype, + object_static_methods, nullptr, + object_methods, object_properties, + FinishObjectClassInit}; + +const JSClass PlainObject::class_ = {js_Object_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_Object), + JS_NULL_CLASS_OPS, &PlainObjectClassSpec}; + +const JSClass* const js::ObjectClassPtr = &PlainObject::class_; |