michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "builtin/Object.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "jscntxt.h" michael@0: michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/ObjectImpl-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::types; michael@0: michael@0: using js::frontend::IsIdentifier; michael@0: using mozilla::ArrayLength; michael@0: michael@0: michael@0: bool michael@0: js::obj_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject obj(cx, nullptr); michael@0: if (args.length() > 0 && !args[0].isNullOrUndefined()) { michael@0: obj = ToObject(cx, args[0]); michael@0: if (!obj) michael@0: return false; michael@0: } else { michael@0: /* Make an object whether this was called with 'new' or not. */ michael@0: if (!NewObjectScriptedCall(cx, &obj)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.2.4.7. */ michael@0: static bool michael@0: obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args.get(0), &id)) michael@0: return false; michael@0: michael@0: /* Step 2. */ michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Steps 3. */ michael@0: RootedObject pobj(cx); michael@0: RootedShape prop(cx); michael@0: if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &prop)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: if (!prop) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: if (pobj != obj) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 5. */ michael@0: unsigned attrs; michael@0: if (!JSObject::getGenericAttributes(cx, pobj, id, &attrs)) michael@0: return false; michael@0: michael@0: args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: static bool michael@0: obj_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSString *str = ObjectToSource(cx, obj); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: JSString * michael@0: js::ObjectToSource(JSContext *cx, HandleObject obj) michael@0: { michael@0: /* If outermost, we need parentheses to be an expression, not a block. */ michael@0: bool outermost = (cx->cycleDetectorSet.count() == 0); michael@0: michael@0: AutoCycleDetector detector(cx, obj); michael@0: if (!detector.init()) michael@0: return nullptr; michael@0: if (detector.foundCycle()) michael@0: return js_NewStringCopyZ(cx, "{}"); michael@0: michael@0: StringBuffer buf(cx); michael@0: if (outermost && !buf.append('(')) michael@0: return nullptr; michael@0: if (!buf.append('{')) michael@0: return nullptr; michael@0: michael@0: RootedValue v0(cx), v1(cx); michael@0: MutableHandleValue val[2] = {&v0, &v1}; michael@0: michael@0: RootedString str0(cx), str1(cx); michael@0: MutableHandleString gsop[2] = {&str0, &str1}; michael@0: michael@0: AutoIdVector idv(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &idv)) michael@0: return nullptr; michael@0: michael@0: bool comma = false; michael@0: for (size_t i = 0; i < idv.length(); ++i) { michael@0: RootedId id(cx, idv[i]); michael@0: RootedObject obj2(cx); michael@0: RootedShape shape(cx); michael@0: if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &shape)) michael@0: return nullptr; michael@0: michael@0: /* Decide early whether we prefer get/set or old getter/setter syntax. */ michael@0: int valcnt = 0; michael@0: if (shape) { michael@0: bool doGet = true; michael@0: if (obj2->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: unsigned attrs = shape->attributes(); michael@0: if (attrs & JSPROP_GETTER) { michael@0: doGet = false; michael@0: val[valcnt].set(shape->getterValue()); michael@0: gsop[valcnt].set(cx->names().get); michael@0: valcnt++; michael@0: } michael@0: if (attrs & JSPROP_SETTER) { michael@0: doGet = false; michael@0: val[valcnt].set(shape->setterValue()); michael@0: gsop[valcnt].set(cx->names().set); michael@0: valcnt++; michael@0: } michael@0: } michael@0: if (doGet) { michael@0: valcnt = 1; michael@0: gsop[0].set(nullptr); michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, val[0])) michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: /* Convert id to a linear string. */ michael@0: RootedValue idv(cx, IdToValue(id)); michael@0: JSString *s = ToString(cx, idv); michael@0: if (!s) michael@0: return nullptr; michael@0: Rooted idstr(cx, s->ensureLinear(cx)); michael@0: if (!idstr) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * If id is a string that's not an identifier, or if it's a negative michael@0: * integer, then it must be quoted. michael@0: */ michael@0: if (JSID_IS_ATOM(id) michael@0: ? !IsIdentifier(idstr) michael@0: : (!JSID_IS_INT(id) || JSID_TO_INT(id) < 0)) michael@0: { michael@0: s = js_QuoteString(cx, idstr, jschar('\'')); michael@0: if (!s || !(idstr = s->ensureLinear(cx))) michael@0: return nullptr; michael@0: } michael@0: michael@0: for (int j = 0; j < valcnt; j++) { michael@0: /* michael@0: * Censor an accessor descriptor getter or setter part if it's michael@0: * undefined. michael@0: */ michael@0: if (gsop[j] && val[j].isUndefined()) michael@0: continue; michael@0: michael@0: /* Convert val[j] to its canonical source form. */ michael@0: RootedString valstr(cx, ValueToSource(cx, val[j])); michael@0: if (!valstr) michael@0: return nullptr; michael@0: const jschar *vchars = valstr->getChars(cx); michael@0: if (!vchars) michael@0: return nullptr; michael@0: size_t vlength = valstr->length(); michael@0: michael@0: /* michael@0: * Remove '(function ' from the beginning of valstr and ')' from the michael@0: * end so that we can put "get" in front of the function definition. michael@0: */ michael@0: if (gsop[j] && IsFunctionObject(val[j])) { michael@0: const jschar *start = vchars; michael@0: const jschar *end = vchars + vlength; michael@0: michael@0: uint8_t parenChomp = 0; michael@0: if (vchars[0] == '(') { michael@0: vchars++; michael@0: parenChomp = 1; michael@0: } michael@0: michael@0: /* Try to jump "function" keyword. */ michael@0: if (vchars) michael@0: vchars = js_strchr_limit(vchars, ' ', end); michael@0: michael@0: /* michael@0: * Jump over the function's name: it can't be encoded as part michael@0: * of an ECMA getter or setter. michael@0: */ michael@0: if (vchars) michael@0: vchars = js_strchr_limit(vchars, '(', end); michael@0: michael@0: if (vchars) { michael@0: if (*vchars == ' ') michael@0: vchars++; michael@0: vlength = end - vchars - parenChomp; michael@0: } else { michael@0: gsop[j].set(nullptr); michael@0: vchars = start; michael@0: } michael@0: } michael@0: michael@0: if (comma && !buf.append(", ")) michael@0: return nullptr; michael@0: comma = true; michael@0: michael@0: if (gsop[j]) michael@0: if (!buf.append(gsop[j]) || !buf.append(' ')) michael@0: return nullptr; michael@0: michael@0: if (!buf.append(idstr)) michael@0: return nullptr; michael@0: if (!buf.append(gsop[j] ? ' ' : ':')) michael@0: return nullptr; michael@0: michael@0: if (!buf.append(vchars, vlength)) michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: if (!buf.append('}')) michael@0: return nullptr; michael@0: if (outermost && !buf.append(')')) michael@0: return nullptr; michael@0: michael@0: return buf.finishString(); michael@0: } michael@0: #endif /* JS_HAS_TOSOURCE */ michael@0: michael@0: JSString * michael@0: JS_BasicObjectToString(JSContext *cx, HandleObject obj) michael@0: { michael@0: // Some classes are really common, don't allocate new strings for them. michael@0: // The ordering below is based on the measurements in bug 966264. michael@0: if (obj->is()) michael@0: return cx->names().objectObject; michael@0: if (obj->is()) michael@0: return cx->names().objectString; michael@0: if (obj->is()) michael@0: return cx->names().objectArray; michael@0: if (obj->is()) michael@0: return cx->names().objectFunction; michael@0: if (obj->is()) michael@0: return cx->names().objectNumber; michael@0: michael@0: const char *className = JSObject::className(cx, obj); michael@0: michael@0: if (strcmp(className, "Window") == 0) michael@0: return cx->names().objectWindow; michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!sb.append("[object ") || !sb.appendInflated(className, strlen(className)) || michael@0: !sb.append("]")) michael@0: { michael@0: return nullptr; michael@0: } michael@0: return sb.finishString(); michael@0: } michael@0: michael@0: /* ES5 15.2.4.2. Note steps 1 and 2 are errata. */ michael@0: static bool michael@0: obj_toString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: if (args.thisv().isUndefined()) { michael@0: args.rval().setString(cx->names().objectUndefined); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 2. */ michael@0: if (args.thisv().isNull()) { michael@0: args.rval().setString(cx->names().objectNull); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 3. */ michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Steps 4-5. */ michael@0: JSString *str = JS_BasicObjectToString(cx, obj); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.2.4.3. */ michael@0: static bool michael@0: obj_toLocaleString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Steps 2-4. */ michael@0: RootedId id(cx, NameToId(cx->names().toString)); michael@0: return obj->callMethod(cx, id, 0, nullptr, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: obj_valueOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_OLD_GETTER_SETTER_METHODS michael@0: michael@0: enum DefineType { GetterAccessor, SetterAccessor }; michael@0: michael@0: template michael@0: static bool michael@0: DefineAccessor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!BoxNonStrictThis(cx, args)) michael@0: return false; michael@0: michael@0: if (args.length() < 2 || !js_IsCallable(args[1])) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_BAD_GETTER_OR_SETTER, michael@0: Type == GetterAccessor ? js_getter_str : js_setter_str); michael@0: return false; michael@0: } michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args[0], &id)) michael@0: return false; michael@0: michael@0: RootedObject descObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!descObj) michael@0: return false; michael@0: michael@0: JSAtomState &names = cx->names(); michael@0: RootedValue trueVal(cx, BooleanValue(true)); michael@0: michael@0: /* enumerable: true */ michael@0: if (!JSObject::defineProperty(cx, descObj, names.enumerable, trueVal)) michael@0: return false; michael@0: michael@0: /* configurable: true */ michael@0: if (!JSObject::defineProperty(cx, descObj, names.configurable, trueVal)) michael@0: return false; michael@0: michael@0: /* enumerable: true */ michael@0: PropertyName *acc = (Type == GetterAccessor) ? names.get : names.set; michael@0: RootedValue accessorVal(cx, args[1]); michael@0: if (!JSObject::defineProperty(cx, descObj, acc, accessorVal)) michael@0: return false; michael@0: michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: michael@0: bool dummy; michael@0: RootedValue descObjValue(cx, ObjectValue(*descObj)); michael@0: if (!DefineOwnProperty(cx, thisObj, id, descObjValue, &dummy)) michael@0: return false; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js::obj_defineGetter(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DefineAccessor(cx, argc, vp); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js::obj_defineSetter(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DefineAccessor(cx, argc, vp); michael@0: } michael@0: michael@0: static bool michael@0: obj_lookupGetter(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args.get(0), &id)) michael@0: return false; michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: if (obj->is()) { michael@0: // The vanilla getter lookup code below requires that the object is michael@0: // native. Handle proxies separately. michael@0: args.rval().setUndefined(); michael@0: Rooted desc(cx); michael@0: if (!Proxy::getPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: if (desc.object() && desc.hasGetterObject() && desc.getterObject()) michael@0: args.rval().setObject(*desc.getterObject()); michael@0: return true; michael@0: } michael@0: RootedObject pobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &shape)) michael@0: return false; michael@0: args.rval().setUndefined(); michael@0: if (shape) { michael@0: if (pobj->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: if (shape->hasGetterValue()) michael@0: args.rval().set(shape->getterValue()); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_lookupSetter(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args.get(0), &id)) michael@0: return false; michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: if (obj->is()) { michael@0: // The vanilla setter lookup code below requires that the object is michael@0: // native. Handle proxies separately. michael@0: args.rval().setUndefined(); michael@0: Rooted desc(cx); michael@0: if (!Proxy::getPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: if (desc.object() && desc.hasSetterObject() && desc.setterObject()) michael@0: args.rval().setObject(*desc.setterObject()); michael@0: return true; michael@0: } michael@0: RootedObject pobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &shape)) michael@0: return false; michael@0: args.rval().setUndefined(); michael@0: if (shape) { michael@0: if (pobj->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: if (shape->hasSetterValue()) michael@0: args.rval().set(shape->setterValue()); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: #endif /* JS_OLD_GETTER_SETTER_METHODS */ michael@0: michael@0: /* ES5 15.2.3.2. */ michael@0: static bool michael@0: obj_getPrototypeOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: if (args.length() == 0) { michael@0: js_ReportMissingArg(cx, args.calleev(), 0); michael@0: return false; michael@0: } michael@0: michael@0: if (args[0].isPrimitive()) { michael@0: RootedValue val(cx, args[0]); michael@0: char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr()); michael@0: if (!bytes) michael@0: return false; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_UNEXPECTED_TYPE, bytes, "not an object"); michael@0: js_free(bytes); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 2. */ michael@0: michael@0: /* michael@0: * Implement [[Prototype]]-getting -- particularly across compartment michael@0: * boundaries -- by calling a cached __proto__ getter function. michael@0: */ michael@0: InvokeArgs args2(cx); michael@0: if (!args2.init(0)) michael@0: return false; michael@0: args2.setCallee(cx->global()->protoGetter()); michael@0: args2.setThis(args[0]); michael@0: if (!Invoke(cx, args2)) michael@0: return false; michael@0: args.rval().set(args2.rval()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_setPrototypeOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject setPrototypeOf(cx, &args.callee()); michael@0: if (!GlobalObject::warnOnceAboutPrototypeMutation(cx, setPrototypeOf)) michael@0: return false; michael@0: michael@0: if (args.length() < 2) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "Object.setPrototypeOf", "1", ""); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 1-2. */ michael@0: if (args[0].isNullOrUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, michael@0: args[0].isNull() ? "null" : "undefined", "object"); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 3. */ michael@0: if (!args[1].isObjectOrNull()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, michael@0: "Object.setPrototypeOf", "an object or null", InformalValueTypeName(args[1])); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 4. */ michael@0: if (!args[0].isObject()) { michael@0: args.rval().set(args[0]); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 5-6. */ michael@0: RootedObject obj(cx, &args[0].toObject()); michael@0: RootedObject newProto(cx, args[1].toObjectOrNull()); michael@0: michael@0: bool success; michael@0: if (!JSObject::setProto(cx, obj, newProto, &success)) michael@0: return false; michael@0: michael@0: /* Step 7. */ michael@0: if (!success) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_OBJECT_NOT_EXTENSIBLE, "object"); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 8. */ michael@0: args.rval().set(args[0]); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_OBJ_WATCHPOINT michael@0: michael@0: bool michael@0: js::WatchHandler(JSContext *cx, JSObject *obj_, jsid id_, JS::Value old, michael@0: JS::Value *nvp, void *closure) michael@0: { michael@0: RootedObject obj(cx, obj_); michael@0: RootedId id(cx, id_); michael@0: michael@0: /* Avoid recursion on (obj, id) already being watched on cx. */ michael@0: AutoResolving resolving(cx, obj, id, AutoResolving::WATCH); michael@0: if (resolving.alreadyStarted()) michael@0: return true; michael@0: michael@0: JSObject *callable = (JSObject *)closure; michael@0: Value argv[] = { IdToValue(id), old, *nvp }; michael@0: RootedValue rv(cx); michael@0: if (!Invoke(cx, ObjectValue(*obj), ObjectOrNullValue(callable), ArrayLength(argv), argv, &rv)) michael@0: return false; michael@0: michael@0: *nvp = rv; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_watch(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!GlobalObject::warnOnceAboutWatch(cx, obj)) michael@0: return false; michael@0: michael@0: if (args.length() <= 1) { michael@0: js_ReportMissingArg(cx, args.calleev(), 1); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2)); michael@0: if (!callable) michael@0: return false; michael@0: michael@0: RootedId propid(cx); michael@0: if (!ValueToId(cx, args[0], &propid)) michael@0: return false; michael@0: michael@0: if (!JSObject::watch(cx, obj, propid, callable)) michael@0: return false; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_unwatch(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!GlobalObject::warnOnceAboutWatch(cx, obj)) michael@0: return false; michael@0: michael@0: RootedId id(cx); michael@0: if (args.length() != 0) { michael@0: if (!ValueToId(cx, args[0], &id)) michael@0: return false; michael@0: } else { michael@0: id = JSID_VOID; michael@0: } michael@0: michael@0: if (!JSObject::unwatch(cx, obj, id)) michael@0: return false; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: #endif /* JS_HAS_OBJ_WATCHPOINT */ michael@0: michael@0: /* ECMA 15.2.4.5. */ michael@0: static bool michael@0: obj_hasOwnProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: HandleValue idValue = args.get(0); michael@0: michael@0: /* Step 1, 2. */ michael@0: jsid id; michael@0: if (args.thisv().isObject() && ValueToId(cx, idValue, &id)) { michael@0: JSObject *obj = &args.thisv().toObject(), *obj2; michael@0: Shape *prop; michael@0: if (!obj->is() && michael@0: HasOwnProperty(cx, obj->getOps()->lookupGeneric, obj, id, &obj2, &prop)) michael@0: { michael@0: args.rval().setBoolean(!!prop); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: /* Step 1. */ michael@0: RootedId idRoot(cx); michael@0: if (!ValueToId(cx, idValue, &idRoot)) michael@0: return false; michael@0: michael@0: /* Step 2. */ michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Non-standard code for proxies. */ michael@0: if (obj->is()) { michael@0: bool has; michael@0: if (!Proxy::hasOwn(cx, obj, idRoot, &has)) michael@0: return false; michael@0: args.rval().setBoolean(has); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 3. */ michael@0: bool found; michael@0: if (!HasOwnProperty(cx, obj, idRoot, &found)) michael@0: return false; michael@0: michael@0: /* Step 4,5. */ michael@0: args.rval().setBoolean(found); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.2.4.6. */ michael@0: static bool michael@0: obj_isPrototypeOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: if (args.length() < 1 || !args[0].isObject()) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 2. */ michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: bool isDelegate; michael@0: if (!IsDelegate(cx, obj, args[0], &isDelegate)) michael@0: return false; michael@0: args.rval().setBoolean(isDelegate); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.2.3.5: Object.create(O [, Properties]) */ michael@0: static bool michael@0: obj_create(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "Object.create", "0", "s"); michael@0: return false; michael@0: } michael@0: michael@0: RootedValue v(cx, args[0]); michael@0: if (!v.isObjectOrNull()) { michael@0: char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); michael@0: if (!bytes) michael@0: return false; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: bytes, "not an object or null"); michael@0: js_free(bytes); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject proto(cx, v.toObjectOrNull()); michael@0: michael@0: /* michael@0: * Use the callee's global as the parent of the new object to avoid dynamic michael@0: * scoping (i.e., using the caller's global). michael@0: */ michael@0: RootedObject obj(cx, NewObjectWithGivenProto(cx, &JSObject::class_, proto, &args.callee().global())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* 15.2.3.5 step 4. */ michael@0: if (args.hasDefined(1)) { michael@0: if (args[1].isPrimitive()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject props(cx, &args[1].toObject()); michael@0: if (!DefineProperties(cx, obj, props)) michael@0: return false; michael@0: } michael@0: michael@0: /* 5. Return obj. */ michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj)) michael@0: return false; michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args.get(1), &id)) michael@0: return false; michael@0: return GetOwnPropertyDescriptor(cx, obj, id, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: obj_keys(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.keys", &obj)) michael@0: return false; michael@0: michael@0: AutoIdVector props(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &props)) michael@0: return false; michael@0: michael@0: AutoValueVector vals(cx); michael@0: if (!vals.reserve(props.length())) michael@0: return false; michael@0: for (size_t i = 0, len = props.length(); i < len; i++) { michael@0: jsid id = props[i]; michael@0: if (JSID_IS_STRING(id)) { michael@0: vals.infallibleAppend(StringValue(JSID_TO_STRING(id))); michael@0: } else if (JSID_IS_INT(id)) { michael@0: JSString *str = Int32ToString(cx, JSID_TO_INT(id)); michael@0: if (!str) michael@0: return false; michael@0: vals.infallibleAppend(StringValue(str)); michael@0: } else { michael@0: JS_ASSERT(JSID_IS_OBJECT(id)); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(props.length() <= UINT32_MAX); michael@0: JSObject *aobj = NewDenseCopiedArray(cx, uint32_t(vals.length()), vals.begin()); michael@0: if (!aobj) michael@0: return false; michael@0: michael@0: args.rval().setObject(*aobj); michael@0: return true; michael@0: } michael@0: michael@0: /* ES6 draft 15.2.3.16 */ michael@0: static bool michael@0: obj_is(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: bool same; michael@0: if (!SameValue(cx, args.get(0), args.get(1), &same)) michael@0: return false; michael@0: michael@0: args.rval().setBoolean(same); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyNames", &obj)) michael@0: return false; michael@0: michael@0: AutoIdVector keys(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys)) michael@0: return false; michael@0: michael@0: AutoValueVector vals(cx); michael@0: if (!vals.resize(keys.length())) michael@0: return false; michael@0: michael@0: for (size_t i = 0, len = keys.length(); i < len; i++) { michael@0: jsid id = keys[i]; michael@0: if (JSID_IS_INT(id)) { michael@0: JSString *str = Int32ToString(cx, JSID_TO_INT(id)); michael@0: if (!str) michael@0: return false; michael@0: vals[i].setString(str); michael@0: } else if (JSID_IS_ATOM(id)) { michael@0: vals[i].setString(JSID_TO_STRING(id)); michael@0: } else { michael@0: vals[i].setObject(*JSID_TO_OBJECT(id)); michael@0: } michael@0: } michael@0: michael@0: JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin()); michael@0: if (!aobj) michael@0: return false; michael@0: michael@0: args.rval().setObject(*aobj); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.2.3.6: Object.defineProperty(O, P, Attributes) */ michael@0: static bool michael@0: obj_defineProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) michael@0: return false; michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args.get(1), &id)) michael@0: return false; michael@0: michael@0: bool junk; michael@0: if (!DefineOwnProperty(cx, obj, id, args.get(2), &junk)) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */ michael@0: static bool michael@0: obj_defineProperties(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Steps 1 and 7. */ michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperties", &obj)) michael@0: return false; michael@0: args.rval().setObject(*obj); michael@0: michael@0: /* Step 2. */ michael@0: if (args.length() < 2) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "Object.defineProperties", "0", "s"); michael@0: return false; michael@0: } michael@0: RootedValue val(cx, args[1]); michael@0: RootedObject props(cx, ToObject(cx, val)); michael@0: if (!props) michael@0: return false; michael@0: michael@0: /* Steps 3-6. */ michael@0: return DefineProperties(cx, obj, props); michael@0: } michael@0: michael@0: static bool michael@0: obj_isExtensible(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.isExtensible", &obj)) michael@0: return false; michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: args.rval().setBoolean(extensible); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_preventExtensions(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.preventExtensions", &obj)) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (!extensible) michael@0: return true; michael@0: michael@0: return JSObject::preventExtensions(cx, obj); michael@0: } michael@0: michael@0: static bool michael@0: obj_freeze(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.freeze", &obj)) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: michael@0: return JSObject::freeze(cx, obj); michael@0: } michael@0: michael@0: static bool michael@0: obj_isFrozen(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.preventExtensions", &obj)) michael@0: return false; michael@0: michael@0: bool frozen; michael@0: if (!JSObject::isFrozen(cx, obj, &frozen)) michael@0: return false; michael@0: args.rval().setBoolean(frozen); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: obj_seal(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.seal", &obj)) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: michael@0: return JSObject::seal(cx, obj); michael@0: } michael@0: michael@0: static bool michael@0: obj_isSealed(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx); michael@0: if (!GetFirstArgumentAsObject(cx, args, "Object.isSealed", &obj)) michael@0: return false; michael@0: michael@0: bool sealed; michael@0: if (!JSObject::isSealed(cx, obj, &sealed)) michael@0: return false; michael@0: args.rval().setBoolean(sealed); michael@0: return true; michael@0: } michael@0: michael@0: const JSFunctionSpec js::object_methods[] = { michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, obj_toSource, 0,0), michael@0: #endif michael@0: JS_FN(js_toString_str, obj_toString, 0,0), michael@0: JS_FN(js_toLocaleString_str, obj_toLocaleString, 0,0), michael@0: JS_FN(js_valueOf_str, obj_valueOf, 0,0), michael@0: #if JS_HAS_OBJ_WATCHPOINT michael@0: JS_FN(js_watch_str, obj_watch, 2,0), michael@0: JS_FN(js_unwatch_str, obj_unwatch, 1,0), michael@0: #endif michael@0: JS_FN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0), michael@0: JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0), michael@0: JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0), michael@0: #if JS_OLD_GETTER_SETTER_METHODS michael@0: JS_FN(js_defineGetter_str, js::obj_defineGetter, 2,0), michael@0: JS_FN(js_defineSetter_str, js::obj_defineSetter, 2,0), michael@0: JS_FN(js_lookupGetter_str, obj_lookupGetter, 1,0), michael@0: JS_FN(js_lookupSetter_str, obj_lookupSetter, 1,0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: const JSFunctionSpec js::object_static_methods[] = { michael@0: JS_FN("getPrototypeOf", obj_getPrototypeOf, 1,0), michael@0: JS_FN("setPrototypeOf", obj_setPrototypeOf, 2,0), michael@0: JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2,0), michael@0: JS_FN("keys", obj_keys, 1,0), michael@0: JS_FN("is", obj_is, 2,0), michael@0: JS_FN("defineProperty", obj_defineProperty, 3,0), michael@0: JS_FN("defineProperties", obj_defineProperties, 2,0), michael@0: JS_FN("create", obj_create, 2,0), michael@0: JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1,0), michael@0: JS_FN("isExtensible", obj_isExtensible, 1,0), michael@0: JS_FN("preventExtensions", obj_preventExtensions, 1,0), michael@0: JS_FN("freeze", obj_freeze, 1,0), michael@0: JS_FN("isFrozen", obj_isFrozen, 1,0), michael@0: JS_FN("seal", obj_seal, 1,0), michael@0: JS_FN("isSealed", obj_isSealed, 1,0), michael@0: JS_FS_END michael@0: };