2019-03-11 03:26:37 -07:00
|
|
|
/* -*- 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"
|
2019-05-19 23:02:17 -07:00
|
|
|
#include "jsstr.h"
|
2019-03-11 03:26:37 -07:00
|
|
|
|
|
|
|
#include "builtin/Eval.h"
|
|
|
|
#include "frontend/BytecodeCompiler.h"
|
|
|
|
#include "jit/InlinableNatives.h"
|
|
|
|
#include "js/UniquePtr.h"
|
2019-05-19 23:02:17 -07:00
|
|
|
#include "vm/AsyncFunction.h"
|
2019-03-11 03:26:37 -07:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
template <typename CharT>
|
|
|
|
static bool
|
|
|
|
Consume(const CharT*& s, const CharT* e, const char *chars)
|
|
|
|
{
|
|
|
|
size_t len = strlen(chars);
|
|
|
|
if (s + len >= e)
|
|
|
|
return false;
|
|
|
|
if (!EqualChars(s, chars, len))
|
|
|
|
return false;
|
|
|
|
s += len;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename CharT>
|
|
|
|
static void
|
|
|
|
ConsumeSpaces(const CharT*& s, const CharT* e)
|
|
|
|
{
|
|
|
|
while (*s == ' ' && s < e)
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
|
2019-03-11 03:26:37 -07:00
|
|
|
/*
|
|
|
|
* 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* s = start;
|
2019-05-19 23:02:17 -07:00
|
|
|
const CharT* e = chars.end().get();
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
if (s == e)
|
2019-03-11 03:26:37 -07:00
|
|
|
return false;
|
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
// Remove enclosing parentheses.
|
|
|
|
if (*s == '(' && *(e - 1) == ')') {
|
|
|
|
s++;
|
|
|
|
e--;
|
|
|
|
}
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
(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, "[")) {
|
|
|
|
s = js_strchr_limit(s, ']', e);
|
|
|
|
if (!s)
|
|
|
|
return false;
|
2019-03-11 03:26:37 -07:00
|
|
|
s++;
|
2019-05-19 23:02:17 -07:00
|
|
|
ConsumeSpaces(s, e);
|
|
|
|
if (*s != '(')
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
s = js_strchr_limit(s, '(', e);
|
|
|
|
if (!s)
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-11 03:26:37 -07:00
|
|
|
|
|
|
|
*outOffset = s - start;
|
2019-05-19 23:02:17 -07:00
|
|
|
*outLen = e - s;
|
2019-03-11 03:26:37 -07:00
|
|
|
MOZ_ASSERT(*outOffset + *outLen <= chars.length());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
enum class PropertyKind {
|
|
|
|
Getter,
|
|
|
|
Setter,
|
|
|
|
Method,
|
|
|
|
Normal
|
|
|
|
};
|
|
|
|
|
2019-03-11 03:26:37 -07:00
|
|
|
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;
|
|
|
|
|
|
|
|
AutoIdVector idv(cx);
|
|
|
|
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
bool comma = false;
|
2019-05-19 23:02:17 -07:00
|
|
|
auto AddProperty = [cx, &comma, &buf](HandleId id, HandleValue val, PropertyKind kind) -> bool {
|
2019-03-11 03:26:37 -07:00
|
|
|
/* 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)
|
2019-05-19 23:02:17 -07:00
|
|
|
return false;
|
2019-03-11 03:26:37 -07:00
|
|
|
} else {
|
|
|
|
RootedValue idv(cx, IdToValue(id));
|
|
|
|
idstr = ToString<CanGC>(cx, idv);
|
|
|
|
if (!idstr)
|
2019-05-19 23:02:17 -07:00
|
|
|
return false;
|
2019-03-11 03:26:37 -07:00
|
|
|
|
|
|
|
/*
|
2019-05-19 23:02:17 -07:00
|
|
|
* If id is a string that's not an identifier, or if it's a
|
|
|
|
* negative integer, then it must be quoted.
|
2019-03-11 03:26:37 -07:00
|
|
|
*/
|
|
|
|
if (JSID_IS_ATOM(id)
|
|
|
|
? !IsIdentifier(JSID_TO_ATOM(id))
|
|
|
|
: JSID_TO_INT(id) < 0)
|
|
|
|
{
|
|
|
|
idstr = QuoteString(cx, idstr, char16_t('\''));
|
|
|
|
if (!idstr)
|
2019-05-19 23:02:17 -07:00
|
|
|
return false;
|
2019-03-11 03:26:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
RootedString valsource(cx, ValueToSource(cx, val));
|
|
|
|
if (!valsource)
|
|
|
|
return false;
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
RootedLinearString valstr(cx, valsource->ensureLinear(cx));
|
|
|
|
if (!valstr)
|
|
|
|
return false;
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
if (comma && !buf.append(", "))
|
|
|
|
return false;
|
|
|
|
comma = true;
|
|
|
|
|
|
|
|
size_t voffset, vlength;
|
|
|
|
|
|
|
|
// Methods and accessors can return exact syntax of source, that fits
|
|
|
|
// into property without adding property name or "get"/"set" prefix.
|
|
|
|
// Use the exact syntax when the following conditions are met:
|
|
|
|
//
|
|
|
|
// * It's a function object
|
|
|
|
// (exclude proxies)
|
|
|
|
// * Function's kind and property's kind are same
|
|
|
|
// (this can be false for dynamically defined properties)
|
|
|
|
// * Function has explicit name
|
|
|
|
// (this can be false for computed property and dynamically defined
|
|
|
|
// properties)
|
|
|
|
// * Function's name and property's name are same
|
|
|
|
// (this can be false for dynamically defined properties)
|
|
|
|
if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
|
|
|
|
kind == PropertyKind::Method)
|
|
|
|
{
|
|
|
|
RootedFunction fun(cx);
|
|
|
|
if (val.toObject().is<JSFunction>()) {
|
|
|
|
fun = &val.toObject().as<JSFunction>();
|
|
|
|
// Method's case should be checked on caller.
|
|
|
|
if (((fun->isGetter() && kind == PropertyKind::Getter) ||
|
|
|
|
(fun->isSetter() && kind == PropertyKind::Setter) ||
|
|
|
|
kind == PropertyKind::Method) &&
|
|
|
|
fun->explicitName())
|
|
|
|
{
|
|
|
|
bool result;
|
|
|
|
if (!EqualStrings(cx, fun->explicitName(), idstr, &result))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
if (!buf.append(valstr))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
{
|
|
|
|
// When falling back try to generate a better string
|
|
|
|
// representation by skipping the prelude, and also removing
|
|
|
|
// the enclosing parentheses.
|
2019-03-11 03:26:37 -07:00
|
|
|
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)
|
2019-05-19 23:02:17 -07:00
|
|
|
kind = PropertyKind::Normal;
|
2019-03-11 03:26:37 -07:00
|
|
|
}
|
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
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 (IsWrappedAsyncFunction(fun)) {
|
|
|
|
if (!buf.append("async "))
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
if (fun->isStarGenerator()) {
|
|
|
|
if (!buf.append('*'))
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-11 03:26:37 -07:00
|
|
|
}
|
2019-05-19 23:02:17 -07:00
|
|
|
}
|
2019-03-11 03:26:37 -07:00
|
|
|
|
2019-05-19 23:02:17 -07:00
|
|
|
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)
|
|
|
|
{
|
2019-03-11 03:26:37 -07:00
|
|
|
if (!buf.appendSubstring(valstr, voffset, vlength))
|
2019-05-19 23:02:17 -07:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
if (!buf.append(':'))
|
|
|
|
return false;
|
|
|
|
if (!buf.append(valstr))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
RootedId id(cx);
|
|
|
|
Rooted<PropertyDescriptor> desc(cx);
|
|
|
|
RootedValue val(cx);
|
|
|
|
RootedFunction fun(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;
|
2019-03-11 03:26:37 -07:00
|
|
|
}
|
2019-05-19 23:02:17 -07:00
|
|
|
|
|
|
|
val.set(desc.value());
|
|
|
|
if (IsFunctionObject(val, fun.address())) {
|
|
|
|
if (IsWrappedAsyncFunction(fun))
|
|
|
|
fun = GetUnwrappedAsyncFunction(fun);
|
|
|
|
|
|
|
|
if (fun->isMethod()) {
|
|
|
|
if (!AddProperty(id, val, PropertyKind::Method))
|
|
|
|
return nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!AddProperty(id, val, PropertyKind::Normal))
|
|
|
|
return nullptr;
|
2019-03-11 03:26:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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),
|
2019-09-05 10:05:43 -07:00
|
|
|
JS_SELF_HOSTED_FN(js_valueOf_str, "Object_valueOf", 0,0),
|
2019-03-11 03:26:37 -07:00
|
|
|
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),
|
2020-10-02 00:34:21 -07:00
|
|
|
JS_SELF_HOSTED_FN("fromEntries", "ObjectFromEntries", 1, 0),
|
2019-03-11 03:26:37 -07:00
|
|
|
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));
|
2019-09-05 10:05:54 -07:00
|
|
|
if (global->shouldSplicePrototype()) {
|
|
|
|
if (!JSObject::splicePrototype(cx, global, global->getClass(), tagged))
|
2019-03-11 03:26:37 -07:00
|
|
|
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_;
|