/* -*- 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/MaybeOneOf.h" #include "mozilla/Range.h" #include "mozilla/RangedPtr.h" #include #include "builtin/BigInt.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::CheckRecursionLimit #include "js/PropertySpec.h" #include "js/UniquePtr.h" #include "util/StringBuffer.h" #include "util/Text.h" #include "vm/AsyncFunction.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/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::Range; using mozilla::RangedPtr; bool js::obj_construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, nullptr); if (args.isConstructing() && (&args.newTarget().toObject() != &args.callee())) { RootedObject newTarget(cx, &args.newTarget().toObject()); obj = CreateThis(cx, &PlainObject::class_, newTarget); if (!obj) { return false; } } else if (args.length() > 0 && !args[0].isNullOrUndefined()) { obj = ToObject(cx, args[0]); if (!obj) { return false; } } else { /* Make an object whether this was called with 'new' or not. */ if (!NewObjectScriptedCall(cx, &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(cx, idValue, &id)) { JSObject* obj = &args.thisv().toObject(); /* Step 3. */ PropertyResult prop; if (obj->isNative() && NativeLookupOwnProperty( cx, &obj->as(), id, &prop)) { /* Step 4. */ if (!prop) { args.rval().setBoolean(false); return true; } /* Step 5. */ unsigned attrs = GetPropertyAttributes(obj, prop); args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0); 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 desc(cx); if (!GetOwnPropertyDescriptor(cx, obj, idRoot, &desc)) { return false; } /* Steps 4-5. */ args.rval().setBoolean(desc.object() && desc.enumerable()); return true; } static bool obj_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!CheckRecursionLimit(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 static bool Consume(RangedPtr& s, RangedPtr e, const char* chars) { MOZ_ASSERT(s <= e); size_t len = strlen(chars); if (e - s < len) { return false; } if (!EqualChars(s.get(), chars, len)) { return false; } s += len; return true; } template static bool ConsumeUntil(RangedPtr& s, RangedPtr 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 static void ConsumeSpaces(RangedPtr& s, RangedPtr e) { while (s < e && *s == ' ') { s++; } } /* * Given a function source string, return the offset and length of the part * between '(function $name' and ')'. */ template static bool ArgsAndBodySubstring(Range chars, size_t* outOffset, size_t* outLen) { const RangedPtr start = chars.begin(); RangedPtr s = start; RangedPtr 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: // // -+---------+-+------------+-+-----+-+- [ - - ] - ( -+- // | | | | | | | | // +- async -+ +- function -+ +- * -+ +- - ( ---------+ // | | // +- 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(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; } bool comma = false; auto AddProperty = [cx, &comma, &buf](HandleId id, HandleValue val, PropertyKind kind) -> bool { /* Convert id to a string. */ RootedString idstr(cx); if (JSID_IS_SYMBOL(id)) { RootedValue v(cx, SymbolValue(JSID_TO_SYMBOL(id))); idstr = ValueToSource(cx, v); if (!idstr) { return false; } } else { RootedValue idv(cx, IdToValue(id)); idstr = ToString(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 (JSID_IS_ATOM(id) ? !IsIdentifier(JSID_TO_ATOM(id)) : JSID_TO_INT(id) < 0) { UniqueChars quotedId = QuoteString(cx, idstr, '\''); if (!quotedId) { return false; } idstr = NewStringCopyZ(cx, quotedId.get()); if (!idstr) { return false; } } } RootedString valsource(cx, ValueToSource(cx, val)); if (!valsource) { return false; } RootedLinearString 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()) { fun = &val.toObject().as(); // 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 = JSID_IS_SYMBOL(id); 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 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.object()) { continue; } if (desc.isAccessorDescriptor()) { if (desc.hasGetterObject() && desc.getterObject()) { val.setObject(*desc.getterObject()); if (!AddProperty(id, val, PropertyKind::Getter)) { return nullptr; } } if (desc.hasSetterObject() && desc.setterObject()) { val.setObject(*desc.setterObject()); if (!AddProperty(id, val, PropertyKind::Setter)) { return nullptr; } } continue; } val.set(desc.value()); JSFunction* fun; 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 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, const JSClass* clasp, JSContext* cx) { MOZ_ASSERT(clasp == obj->getClass()); MOZ_ASSERT(!clasp->isProxy()); // 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 == &JSFunction::class_) { 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()) { return cx->names().objectArguments; } if (obj->is()) { return cx->names().objectError; } if (obj->isCallable() && !obj->getClass()->isDOMClass()) { // Non-standard: Prevent 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) { 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); const JSClass* clasp = obj->getClass(); if (MOZ_UNLIKELY(clasp->isProxy())) { 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, clasp, 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, obj->getClass(), 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->isNative() && NativeLookupOwnProperty(cx, &obj->as(), id, &prop)) { if (!prop) { *enumerable = false; return true; } unsigned attrs = GetPropertyAttributes(obj, prop); *enumerable = (attrs & JSPROP_ENUMERATE) != 0; return true; } Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) { return false; } *enumerable = desc.object() && desc.enumerable(); return true; } static bool TryAssignNative(JSContext* cx, HandleObject to, HandleObject from, bool* optimized) { *optimized = false; if (!from->isNative() || !to->isNative()) { return true; } // Don't use the fast path if |from| may have extra indexed or lazy // properties. NativeObject* fromNative = &from->as(); if (fromNative->getDenseInitializedLength() > 0 || fromNative->isIndexed() || fromNative->is() || fromNative->getClass()->getNewEnumerate() || fromNative->getClass()->getEnumerate()) { return true; } // Get a list of |from| shapes. As long as from->lastProperty() == fromShape // we can use this to speed up both the enumerability check and the GetProp. using ShapeVector = GCVector; Rooted shapes(cx, ShapeVector(cx)); RootedShape fromShape(cx, fromNative->lastProperty()); for (Shape::Range r(fromShape); !r.empty(); r.popFront()) { // Symbol properties need to be assigned last. For now fall back to the // slow path if we see a symbol property. if (MOZ_UNLIKELY(JSID_IS_SYMBOL(r.front().propidRaw()))) { return true; } if (MOZ_UNLIKELY(!shapes.append(&r.front()))) { return false; } } *optimized = true; RootedShape shape(cx); RootedValue propValue(cx); RootedId nextKey(cx); RootedValue toReceiver(cx, ObjectValue(*to)); for (size_t i = shapes.length(); i > 0; i--) { shape = shapes[i - 1]; nextKey = shape->propid(); // Ensure |from| is still native: a getter/setter might have been swapped // with a non-native object. if (MOZ_LIKELY(from->isNative() && from->as().lastProperty() == fromShape && shape->isDataProperty())) { if (!shape->enumerable()) { continue; } propValue = from->as().getSlot(shape->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; 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) { 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. */ static bool 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, HandleObjectGroup group) { // Give the new object a small number of fixed slots, like we do for empty // object literals ({}). gc::AllocKind allocKind = GuessObjectGCKind(0); // Use a faster allocation path if the group is known. if (group) { MOZ_ASSERT(group->proto().toObjectOrNull() == proto); return NewObjectWithGroup(cx, group, allocKind, newKind); } return NewObjectWithGivenProtoAndKinds(cx, proto, allocKind, newKind); } PlainObject* js::ObjectCreateWithTemplate(JSContext* cx, HandlePlainObject templateObj) { RootedObject proto(cx, templateObj->staticPrototype()); RootedObjectGroup group(cx, templateObj->group()); return ObjectCreateImpl(cx, proto, GenericObject, group); } // 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 desc(cx); RootedValue descObj(cx); // Step 4. Rooted 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, &desc)) { return false; } // Step 5.b. if (desc.object() && desc.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()); RootedPlainObject 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 desc, MutableHandleValue vp) { // Step 1. if (!desc.object()) { 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; } RootedArrayObject 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.getterObject()) { result->initDenseElement(PROP_DESC_GETTER_INDEX, ObjectValue(*get)); } else { result->initDenseElement(PROP_DESC_GETTER_INDEX, UndefinedValue()); } if (JSObject* set = desc.setterObject()) { 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 desc(cx); if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) { return false; } // [[GetOwnProperty]] is spec'ed to always return a complete property // descriptor record (ES2017, 6.1.7.3, invariants of [[GetOwnProperty]]). desc.assertCompleteIfFound(); // 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. for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) { Shape* shape = &r.front(); if (!shape->isDataProperty() && shape->enumerable() && !JSID_IS_SYMBOL(shape->propid())) { return true; } } return false; } template 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->isNative() || obj->as().isIndexed() || obj->getClass()->getNewEnumerate() || obj->is()) { return true; } HandleNativeObject nobj = obj.as(); // 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 <= JSID_INT_MAX, "dense elements don't exceed JSID_INT_MAX"); str = Int32ToString(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()) { Handle tobj = obj.as(); size_t len = tobj->length().get(); // 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 <= JSID_INT_MAX, "dense elements don't exceed JSID_INT_MAX"); str = Int32ToString(cx, i); if (!str) { return false; } } if (kind == EnumerableOwnPropertiesKind::Keys || kind == EnumerableOwnPropertiesKind::Names) { value.setString(str); } else if (kind == EnumerableOwnPropertiesKind::Values) { if (!tobj->getElement(cx, i, &value)) { return false; } } else { key.setString(str); if (!tobj->getElement(cx, i, &value)) { return false; } if (!NewValuePair(cx, key, value, &value)) { return false; } } properties[i].set(value); } } // 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->isNative()); 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. size_t elements = properties.length(); constexpr bool onlyEnumerable = kind != EnumerableOwnPropertiesKind::Names; constexpr AllowGC allowGC = kind != EnumerableOwnPropertiesKind::KeysAndValues ? AllowGC::NoGC : AllowGC::CanGC; mozilla::MaybeOneOf, Shape::Range> m; if (allowGC == AllowGC::NoGC) { m.construct>(nobj->lastProperty()); } else { m.construct>(cx, nobj->lastProperty()); } for (Shape::Range& r = m.ref>(); !r.empty(); r.popFront()) { Shape* shape = &r.front(); jsid id = shape->propid(); if ((onlyEnumerable && !shape->enumerable()) || JSID_IS_SYMBOL(id)) { continue; } MOZ_ASSERT(!JSID_IS_INT(id), "Unexpected indexed property"); MOZ_ASSERT_IF(kind == EnumerableOwnPropertiesKind::Values || kind == EnumerableOwnPropertiesKind::KeysAndValues, shape->isDataProperty()); if (kind == EnumerableOwnPropertiesKind::Keys || kind == EnumerableOwnPropertiesKind::Names) { value.setString(JSID_TO_STRING(id)); } else if (kind == EnumerableOwnPropertiesKind::Values) { value.set(nobj->getSlot(shape->slot())); } else { key.setString(JSID_TO_STRING(id)); value.set(nobj->getSlot(shape->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| shapes. As long as obj->lastProperty() // is equal to |objShape|, we can use this to speed up both the // enumerability check and GetProperty. using ShapeVector = GCVector; Rooted shapes(cx, ShapeVector(cx)); // Collect all non-symbol properties. RootedShape objShape(cx, nobj->lastProperty()); for (Shape::Range r(objShape); !r.empty(); r.popFront()) { Shape* shape = &r.front(); if (JSID_IS_SYMBOL(shape->propid())) { continue; } MOZ_ASSERT(!JSID_IS_INT(shape->propid()), "Unexpected indexed property"); if (!shapes.append(shape)) { return false; } } RootedId id(cx); for (size_t i = shapes.length(); i > 0; i--) { Shape* shape = shapes[i - 1]; id = shape->propid(); // Ensure |obj| is still native: a getter might have been swapped with a // non-native object. if (obj->isNative() && obj->as().lastProperty() == objShape && shape->isDataProperty()) { if (!shape->enumerable()) { continue; } value = obj->as().getSlot(shape->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(JSID_TO_STRING(id)); if (!NewValuePair(cx, key, value, &value)) { return false; } } if (!properties.append(value)) { return false; } } } 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 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, ToObject(cx, args.get(0))); if (!obj) { return false; } bool optimized; if (!TryEnumerableOwnPropertiesNative(cx, obj, args.rval(), &optimized)) { return false; } if (optimized) { return true; } // Typed arrays are always handled in the fast path. MOZ_ASSERT(!obj->is()); // 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); RootedShape shape(cx); Rooted 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(!JSID_IS_SYMBOL(id)); if (kind != EnumerableOwnPropertiesKind::Values) { if (!IdToStringOrSymbol(cx, id, &key)) { return false; } } // Step 4.a.i. if (obj->is()) { HandleNativeObject nobj = obj.as(); if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) { value.set(nobj->getDenseElement(JSID_TO_INT(id))); } else { shape = nobj->lookup(cx, id); if (!shape || !shape->enumerable()) { continue; } if (!shape->isAccessorShape()) { if (!NativeGetExistingProperty(cx, nobj, nobj, shape, &value)) { return false; } } 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.object() || !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) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.get(0))); if (!obj) { return false; } bool optimized; static constexpr EnumerableOwnPropertiesKind kind = EnumerableOwnPropertiesKind::Keys; if (!TryEnumerableOwnPropertiesNative(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) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. return EnumerableOwnProperties(cx, args); } // ES2018 draft rev c164be80f7ea91de5526b33d54e5c9321ed03d3f // 19.1.2.5 Object.entries ( O ) static bool obj_entries(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. return EnumerableOwnProperties( 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 (JSID_IS_INT(id)) { JSString* str = Int32ToString(cx, JSID_TO_INT(id)); if (!str) { return false; } result.setString(str); } else if (JSID_IS_ATOM(id)) { result.setString(JSID_TO_STRING(id)); } else { result.setSymbol(JSID_TO_SYMBOL(id)); } 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). RootedArrayObject 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(JSID_IS_SYMBOL(keys[i]), flags & JSITER_SYMBOLS); MOZ_ASSERT_IF(!JSID_IS_SYMBOL(keys[i]), !(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) { 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(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) { 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) { 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 obj(cx, &args.thisv().toObject()); Rooted 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_FS_END}; static JSObject* CreateObjectConstructor(JSContext* cx, JSProtoKey key) { Rooted 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, HandlePropertyName(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()->isNative()); /* * Create |Object.prototype| first, mirroring CreateBlankProto but for the * prototype of the created object. */ RootedPlainObject objectProto( cx, NewTenuredObjectWithGivenProto(cx, nullptr)); 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 global(cx, cx->global()); /* ES5 15.1.2.1. */ RootedId evalId(cx, NameToId(cx->names().eval)); JSObject* 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 Rooted holder(cx, GlobalObject::getIntrinsicsHolder(cx, global)); if (!holder) { return false; } /* * The global object should have |Object.prototype| as its [[Prototype]]. * Eventually we'd like to have standard classes be there from the start, * and thus we would know we were always setting what had previously been a * null [[Prototype]], but right now some code assumes it can set the * [[Prototype]] before standard classes have been initialized. For now, * only set the [[Prototype]] if it hasn't already been set. */ Rooted tagged(cx, TaggedProto(proto)); if (global->shouldSplicePrototype()) { if (!GlobalObject::splicePrototype(cx, global, tagged)) { return false; } } return true; } 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_;