diff options
Diffstat (limited to 'js/src/builtin/Object.cpp')
-rw-r--r-- | js/src/builtin/Object.cpp | 1341 |
1 files changed, 1341 insertions, 0 deletions
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp new file mode 100644 index 000000000..cd4ac122c --- /dev/null +++ b/js/src/builtin/Object.cpp @@ -0,0 +1,1341 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "mozilla/ArrayUtils.h" + +#include "jscntxt.h" + +#include "builtin/Eval.h" +#include "frontend/BytecodeCompiler.h" +#include "jit/InlinableNatives.h" +#include "js/UniquePtr.h" +#include "vm/StringBuffer.h" + +#include "jsobjinlines.h" + +#include "vm/NativeObject-inl.h" +#include "vm/Shape-inl.h" + +using namespace js; + +using js::frontend::IsIdentifier; +using mozilla::ArrayLength; + +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() && ValueToId<NoGC>(cx, idValue, &id)) { + JSObject* obj = &args.thisv().toObject(); + + /* Step 3. */ + Shape* shape; + if (obj->isNative() && + NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &shape)) + { + /* Step 4. */ + if (!shape) { + args.rval().setBoolean(false); + return true; + } + + /* Step 5. */ + unsigned attrs = GetShapeAttributes(obj, shape); + 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<PropertyDescriptor> desc(cx); + if (!GetOwnPropertyDescriptor(cx, obj, idRoot, &desc)) + return false; + + /* Steps 4-5. */ + args.rval().setBoolean(desc.object() && desc.enumerable()); + return true; +} + +#if JS_HAS_TOSOURCE +static bool +obj_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JS_CHECK_RECURSION(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; +} + +/* + * Given a function source string, return the offset and length of the part + * between '(function $name' and ')'. + */ +template <typename CharT> +static bool +ArgsAndBodySubstring(mozilla::Range<const CharT> chars, size_t* outOffset, size_t* outLen) +{ + const CharT* const start = chars.begin().get(); + const CharT* const end = chars.end().get(); + const CharT* s = start; + + uint8_t parenChomp = 0; + if (s[0] == '(') { + s++; + parenChomp = 1; + } + + /* Try to jump "function" keyword. */ + s = js_strchr_limit(s, ' ', end); + if (!s) + return false; + + /* + * Jump over the function's name: it can't be encoded as part + * of an ECMA getter or setter. + */ + s = js_strchr_limit(s, '(', end); + if (!s) + return false; + + if (*s == ' ') + s++; + + *outOffset = s - start; + *outLen = end - s - parenChomp; + MOZ_ASSERT(*outOffset + *outLen <= chars.length()); + return true; +} + +JSString* +js::ObjectToSource(JSContext* cx, HandleObject obj) +{ + /* If outermost, we need parentheses to be an expression, not a block. */ + bool outermost = (cx->cycleDetectorSet.count() == 0); + + AutoCycleDetector detector(cx, obj); + if (!detector.init()) + return nullptr; + if (detector.foundCycle()) + return NewStringCopyZ<CanGC>(cx, "{}"); + + StringBuffer buf(cx); + if (outermost && !buf.append('(')) + return nullptr; + if (!buf.append('{')) + return nullptr; + + RootedValue v0(cx), v1(cx); + MutableHandleValue val[2] = {&v0, &v1}; + + RootedString str0(cx), str1(cx); + MutableHandleString gsop[2] = {&str0, &str1}; + + AutoIdVector idv(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv)) + return nullptr; + + bool comma = false; + for (size_t i = 0; i < idv.length(); ++i) { + RootedId id(cx, idv[i]); + Rooted<PropertyDescriptor> desc(cx); + if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) + return nullptr; + + int valcnt = 0; + if (desc.object()) { + if (desc.isAccessorDescriptor()) { + if (desc.hasGetterObject() && desc.getterObject()) { + val[valcnt].setObject(*desc.getterObject()); + gsop[valcnt].set(cx->names().get); + valcnt++; + } + if (desc.hasSetterObject() && desc.setterObject()) { + val[valcnt].setObject(*desc.setterObject()); + gsop[valcnt].set(cx->names().set); + valcnt++; + } + } else { + valcnt = 1; + val[0].set(desc.value()); + gsop[0].set(nullptr); + } + } + + /* 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 nullptr; + } else { + RootedValue idv(cx, IdToValue(id)); + idstr = ToString<CanGC>(cx, idv); + if (!idstr) + return nullptr; + + /* + * 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) + { + idstr = QuoteString(cx, idstr, char16_t('\'')); + if (!idstr) + return nullptr; + } + } + + for (int j = 0; j < valcnt; j++) { + /* Convert val[j] to its canonical source form. */ + JSString* valsource = ValueToSource(cx, val[j]); + if (!valsource) + return nullptr; + + RootedLinearString valstr(cx, valsource->ensureLinear(cx)); + if (!valstr) + return nullptr; + + size_t voffset = 0; + size_t vlength = valstr->length(); + + /* + * Remove '(function ' from the beginning of valstr and ')' from the + * end so that we can put "get" in front of the function definition. + */ + if (gsop[j] && IsFunctionObject(val[j])) { + 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) + gsop[j].set(nullptr); + } + + if (comma && !buf.append(", ")) + return nullptr; + comma = true; + + if (gsop[j]) { + if (!buf.append(gsop[j]) || !buf.append(' ')) + return nullptr; + } + if (JSID_IS_SYMBOL(id) && !buf.append('[')) + return nullptr; + if (!buf.append(idstr)) + return nullptr; + if (JSID_IS_SYMBOL(id) && !buf.append(']')) + return nullptr; + if (!buf.append(gsop[j] ? ' ' : ':')) + return nullptr; + + if (!buf.appendSubstring(valstr, voffset, vlength)) + return nullptr; + } + } + + if (!buf.append('}')) + return nullptr; + if (outermost && !buf.append(')')) + return nullptr; + + return buf.finishString(); +} +#endif /* JS_HAS_TOSOURCE */ + +// ES6 19.1.3.6 +bool +js::obj_toString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // 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; + } + + // Step 3. + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + // Step 4. + bool isArray; + if (!IsArray(cx, obj, &isArray)) + return false; + + // Step 5. + RootedString builtinTag(cx); + if (isArray) { + builtinTag = cx->names().objectArray; + } else { + // Steps 6-13. + ESClass cls; + if (!GetBuiltinClass(cx, obj, &cls)) + return false; + + switch (cls) { + case ESClass::String: + builtinTag = cx->names().objectString; + break; + case ESClass::Arguments: + builtinTag = cx->names().objectArguments; + break; + case ESClass::Error: + builtinTag = cx->names().objectError; + break; + case ESClass::Boolean: + builtinTag = cx->names().objectBoolean; + break; + case ESClass::Number: + builtinTag = cx->names().objectNumber; + break; + case ESClass::Date: + builtinTag = cx->names().objectDate; + break; + case ESClass::RegExp: + builtinTag = cx->names().objectRegExp; + break; + default: + if (obj->isCallable()) { + // Non-standard: Prevent <object> from showing up as Function. + RootedObject unwrapped(cx, CheckedUnwrap(obj)); + if (!unwrapped || !unwrapped->getClass()->isDOMClass()) + builtinTag = cx->names().objectFunction; + } + break; + } + } + // Step 14. + // Currently omitted for non-standard fallback. + + // Step 15. + RootedValue tag(cx); + RootedId toStringTagId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toStringTag)); + if (!GetProperty(cx, obj, obj, toStringTagId, &tag)) + return false; + + // Step 16. + if (!tag.isString()) { + // Non-standard (bug 1277801): Use ClassName as a fallback in the interim + if (!builtinTag) { + const char* className = GetObjectClassName(cx, obj); + + StringBuffer sb(cx); + if (!sb.append("[object ") || !sb.append(className, strlen(className)) || + !sb.append("]")) + { + return false; + } + + builtinTag = sb.finishString(); + if (!builtinTag) + return false; + } + + args.rval().setString(builtinTag); + return true; + } + + // Step 17. + StringBuffer sb(cx); + if (!sb.append("[object ") || !sb.append(tag.toString()) || !sb.append("]")) + return false; + + RootedString str(cx, sb.finishString()); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + + +bool +js::obj_valueOf(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + args.rval().setObject(*obj); + return true; +} + +static bool +obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 2) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "Object.setPrototypeOf", "1", ""); + 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; +} + +#if JS_HAS_OBJ_WATCHPOINT + +bool +js::WatchHandler(JSContext* cx, JSObject* obj_, jsid id_, const JS::Value& old, + JS::Value* nvp, void* closure) +{ + RootedObject obj(cx, obj_); + RootedId id(cx, id_); + + /* Avoid recursion on (obj, id) already being watched on cx. */ + AutoResolving resolving(cx, obj, id, AutoResolving::WATCH); + if (resolving.alreadyStarted()) + return true; + + FixedInvokeArgs<3> args(cx); + + args[0].set(IdToValue(id)); + args[1].set(old); + args[2].set(*nvp); + + RootedValue callable(cx, ObjectValue(*static_cast<JSObject*>(closure))); + RootedValue thisv(cx, ObjectValue(*obj)); + RootedValue rv(cx); + if (!Call(cx, callable, thisv, args, &rv)) + return false; + + *nvp = rv; + return true; +} + +static bool +obj_watch(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + if (!GlobalObject::warnOnceAboutWatch(cx, obj)) + return false; + + if (args.length() <= 1) { + ReportMissingArg(cx, args.calleev(), 1); + return false; + } + + RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2)); + if (!callable) + return false; + + RootedId propid(cx); + if (!ValueToId<CanGC>(cx, args[0], &propid)) + return false; + + if (!WatchProperty(cx, obj, propid, callable)) + return false; + + args.rval().setUndefined(); + return true; +} + +static bool +obj_unwatch(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + if (!GlobalObject::warnOnceAboutWatch(cx, obj)) + return false; + + RootedId id(cx); + if (args.length() != 0) { + if (!ValueToId<CanGC>(cx, args[0], &id)) + return false; + } else { + id = JSID_VOID; + } + + if (!UnwatchProperty(cx, obj, id)) + return false; + + args.rval().setUndefined(); + return true; +} + +#endif /* JS_HAS_OBJ_WATCHPOINT */ + +/* ECMA 15.2.4.5. */ +bool +js::obj_hasOwnProperty(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 object's shape. + + /* Step 1, 2. */ + jsid id; + if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) { + JSObject* obj = &args.thisv().toObject(); + Shape* prop; + if (obj->isNative() && + NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &prop)) + { + args.rval().setBoolean(!!prop); + 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. */ + bool found; + if (!HasOwnProperty(cx, obj, idRoot, &found)) + return false; + + /* Step 4,5. */ + args.rval().setBoolean(found); + 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 isDelegate; + if (!IsDelegate(cx, obj, args[0], &isDelegate)) + return false; + args.rval().setBoolean(isDelegate); + 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); + + if (!proto) { + // Object.create(null) is common, optimize it by using an allocation + // site specific ObjectGroup. Because GetCallerInitGroup is pretty + // slow, the caller can pass in the group if it's known and we use that + // instead. + RootedObjectGroup ngroup(cx, group); + if (!ngroup) { + ngroup = ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Null); + if (!ngroup) + return nullptr; + } + + MOZ_ASSERT(!ngroup->proto().toObjectOrNull()); + + return NewObjectWithGroup<PlainObject>(cx, ngroup, allocKind, newKind); + } + + return NewObjectWithGivenProto<PlainObject>(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) +{ + // Step 1. implicit + // Step 2. + RootedObject props(cx, ToObject(cx, properties)); + if (!props) + return false; + + // Step 3. + AutoIdVector keys(cx); + if (!GetPropertyKeys(cx, props, JSITER_OWNONLY | JSITER_SYMBOLS | JSITER_HIDDEN, &keys)) + return false; + + RootedId nextKey(cx); + Rooted<PropertyDescriptor> desc(cx); + RootedValue descObj(cx); + + // Step 4. + Rooted<PropertyDescriptorVector> descriptors(cx, PropertyDescriptorVector(cx)); + AutoIdVector 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. + for (size_t i = 0, len = descriptors.length(); i < len; i++) { + if (!DefineProperty(cx, obj, descriptorKeys[i], descriptors[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.length() == 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "Object.create", "0", "s"); + return false; + } + + if (!args[0].isObjectOrNull()) { + RootedValue v(cx, args[0]); + UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, nullptr); + if (!bytes) + return false; + + JS_ReportErrorNumberLatin1(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)) { + if (!ObjectDefineProperties(cx, obj, args[1])) + return false; + } + + // Step 4. + args.rval().setObject(*obj); + return true; +} + +// ES6 draft rev27 (2014/08/24) 19.1.2.6 Object.getOwnPropertyDescriptor(O, P) +bool +js::obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) + return false; + + // Steps 3-4. + RootedId id(cx); + if (!ToPropertyKey(cx, args.get(1), &id)) + return false; + + // Steps 5-7. + Rooted<PropertyDescriptor> desc(cx); + return GetOwnPropertyDescriptor(cx, obj, id, &desc) && + JS::FromPropertyDescriptor(cx, desc, args.rval()); +} + +enum EnumerableOwnPropertiesKind { + Keys, + Values, + KeysAndValues +}; + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#EnumerableOwnProperties +static bool +EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPropertiesKind kind) +{ + // Step 1. (Step 1 of Object.{keys,values,entries}, really.) + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) + return false; + + // Step 2. + AutoIdVector ids(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids)) + return false; + + // Step 3. + AutoValueVector 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<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(!JSID_IS_SYMBOL(id)); + + if (kind != Values) { + if (!IdToStringOrSymbol(cx, id, &key)) + return false; + } + + // Step 4.a.i. + if (obj->is<NativeObject>()) { + HandleNativeObject nobj = obj.as<NativeObject>(); + if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) { + value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); + } else { + shape = nobj->lookup(cx, id); + if (!shape || !(GetShapeAttributes(nobj, shape) & JSPROP_ENUMERATE)) + 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 (obj->isNative() && desc.hasValue()) + value = desc.value(); + else if (!GetProperty(cx, obj, obj, id, &value)) + return false; + } + + // Steps 4.a.ii.2.b-c. + if (kind == 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; +} + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#Object.keys +static bool +obj_keys(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY); +} + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#Object.values +static bool +obj_values(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return EnumerableOwnProperties(cx, args, Values); +} + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#Object.entries +static bool +obj_entries(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return EnumerableOwnProperties(cx, args, KeysAndValues); +} + +/* ES6 draft 15.2.3.16 */ +static bool +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<CanGC>(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; +} + +/* ES6 draft rev 25 (2014 May 22) 19.1.2.8.1 */ +bool +js::GetOwnPropertyKeys(JSContext* cx, const JS::CallArgs& args, unsigned flags) +{ + // Steps 1-2. + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) + return false; + + // Steps 3-10. + AutoIdVector keys(cx); + if (!GetPropertyKeys(cx, obj, flags, &keys)) + return false; + + // Step 11. + AutoValueVector vals(cx); + if (!vals.resize(keys.length())) + return false; + + 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], vals[i])) + return false; + } + + JSObject* aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin()); + if (!aobj) + return false; + + args.rval().setObject(*aobj); + return true; +} + +bool +js::obj_getOwnPropertyNames(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN); +} + +/* ES6 draft rev 25 (2014 May 22) 19.1.2.8 */ +static bool +obj_getOwnPropertySymbols(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return GetOwnPropertyKeys(cx, args, + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY); +} + +/* ES6 draft rev 32 (2015 Feb 2) 19.1.2.4: Object.defineProperty(O, P, Attributes) */ +bool +js::obj_defineProperty(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-3. + RootedObject obj(cx); + if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) + return false; + RootedId id(cx); + if (!ToPropertyKey(cx, args.get(1), &id)) + return false; + + // Steps 4-5. + Rooted<PropertyDescriptor> desc(cx); + if (!ToPropertyDescriptor(cx, args.get(2), true, &desc)) + return false; + + // Steps 6-8. + if (!DefineProperty(cx, obj, id, desc)) + return false; + args.rval().setObject(*obj); + return true; +} + +/* ES5 15.2.3.7: Object.defineProperties(O, Properties) */ +static bool +obj_defineProperties(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + /* Steps 1 and 7. */ + RootedObject obj(cx); + if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperties", &obj)) + return false; + args.rval().setObject(*obj); + + /* Step 2. */ + if (args.length() < 2) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "Object.defineProperties", "0", "s"); + return false; + } + + /* Steps 3-6. */ + return ObjectDefineProperties(cx, obj, args[1]); +} + +// 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; +} + +static bool +ProtoGetter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) { + if (thisv.isNullOrUndefined()) { + ReportIncompatible(cx, args); + return false; + } + + if (!BoxNonStrictThis(cx, thisv, &thisv)) + return false; + } + + RootedObject obj(cx, &thisv.toObject()); + RootedObject proto(cx); + if (!GetPrototype(cx, obj, &proto)) + return false; + + args.rval().setObjectOrNull(proto); + return true; +} + +namespace js { +size_t sSetProtoCalled = 0; +} // namespace js + +static bool +ProtoSetter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + 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; + } + + if (!cx->runningWithTrustedPrincipals()) + ++sSetProtoCalled; + + Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + + /* Do nothing if __proto__ isn't being set to an object or null. */ + if (args.length() == 0 || !args[0].isObjectOrNull()) { + args.rval().setUndefined(); + return true; + } + + Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull()); + if (!SetPrototype(cx, obj, newProto)) + return false; + + args.rval().setUndefined(); + return true; +} + +static const JSFunctionSpec object_methods[] = { +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, obj_toSource, 0,0), +#endif + JS_FN(js_toString_str, obj_toString, 0,0), + JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0), + JS_FN(js_valueOf_str, obj_valueOf, 0,0), +#if JS_HAS_OBJ_WATCHPOINT + JS_FN(js_watch_str, obj_watch, 2,0), + JS_FN(js_unwatch_str, obj_unwatch, 1,0), +#endif + JS_FN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0), + JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0), + JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0), +#if JS_OLD_GETTER_SETTER_METHODS + 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), +#endif + JS_FS_END +}; + +static const JSPropertySpec object_properties[] = { +#if JS_HAS_OBJ_PROTO_PROP + JS_PSGS("__proto__", ProtoGetter, ProtoSetter, 0), +#endif + JS_PS_END +}; + +static const JSFunctionSpec object_static_methods[] = { + JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, 0), + JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0), + JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0), + JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,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_FN("is", obj_is, 2, 0), + JS_FN("defineProperty", obj_defineProperty, 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_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. */ + return NewNativeConstructor(cx, obj_construct, 1, HandlePropertyName(cx->names().Object), + gc::AllocKind::FUNCTION, SingletonObject); +} + +static JSObject* +CreateObjectPrototype(JSContext* cx, JSProtoKey key) +{ + MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); + MOZ_ASSERT(cx->global()->isNative()); + + /* + * Create |Object.prototype| first, mirroring CreateBlankProto but for the + * prototype of the created object. + */ + RootedPlainObject objectProto(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr, + SingletonObject)); + 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"); + + /* + * The default 'new' type of Object.prototype is required by type inference + * to have unknown properties, to simplify handling of e.g. heterogenous + * objects in JSON and script literals. + */ + if (!JSObject::setNewGroupUnknown(cx, &PlainObject::class_, objectProto)) + return nullptr; + + 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)); + JSObject* evalobj = DefineFunction(cx, global, evalId, IndirectEval, 1, + JSFUN_STUB_GSOPS | JSPROP_RESOLVING); + if (!evalobj) + return false; + global->setOriginalEval(evalobj); + + Rooted<NativeObject*> 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<TaggedProto> tagged(cx, TaggedProto(proto)); + if (global->shouldSplicePrototype(cx)) { + if (!global->splicePrototype(cx, global->getClass(), tagged)) + return false; + } + return true; +} + +static const ClassSpec PlainObjectClassSpec = { + CreateObjectConstructor, + CreateObjectPrototype, + object_static_methods, + nullptr, + object_methods, + object_properties, + FinishObjectClassInit +}; + +const Class PlainObject::class_ = { + js_Object_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_Object), + JS_NULL_CLASS_OPS, + &PlainObjectClassSpec +}; + +const Class* const js::ObjectClassPtr = &PlainObject::class_; |