blob: 98b97b1595f109a05e3ccca30cd124d741ae8a0b [file] [log] [blame]
/* -*- 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 "js/Value.h"
#include "vm/Debugger.h"
#include "vm/ObjectImpl.h"
#include "jsobjinlines.h"
#include "gc/Barrier-inl.h"
#include "gc/Marking.h"
#include "vm/ObjectImpl-inl.h"
using namespace js;
PropDesc::PropDesc()
: pd_(UndefinedValue()),
value_(UndefinedValue()),
get_(UndefinedValue()),
set_(UndefinedValue()),
attrs(0),
hasGet_(false),
hasSet_(false),
hasValue_(false),
hasWritable_(false),
hasEnumerable_(false),
hasConfigurable_(false),
isUndefined_(true)
{
}
bool
PropDesc::checkGetter(JSContext *cx)
{
if (hasGet_) {
if (!js_IsCallable(get_) && !get_.isUndefined()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_GET_SET_FIELD,
js_getter_str);
return false;
}
}
return true;
}
bool
PropDesc::checkSetter(JSContext *cx)
{
if (hasSet_) {
if (!js_IsCallable(set_) && !set_.isUndefined()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_GET_SET_FIELD,
js_setter_str);
return false;
}
}
return true;
}
static bool
CheckArgCompartment(JSContext *cx, JSObject *obj, HandleValue v,
const char *methodname, const char *propname)
{
if (v.isObject() && v.toObject().compartment() != obj->compartment()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_COMPARTMENT_MISMATCH,
methodname, propname);
return false;
}
return true;
}
/*
* Convert Debugger.Objects in desc to debuggee values.
* Reject non-callable getters and setters.
*/
bool
PropDesc::unwrapDebuggerObjectsInto(JSContext *cx, Debugger *dbg, HandleObject obj,
PropDesc *unwrapped) const
{
MOZ_ASSERT(!isUndefined());
*unwrapped = *this;
if (unwrapped->hasValue()) {
RootedValue value(cx, unwrapped->value_);
if (!dbg->unwrapDebuggeeValue(cx, &value) ||
!CheckArgCompartment(cx, obj, value, "defineProperty", "value"))
{
return false;
}
unwrapped->value_ = value;
}
if (unwrapped->hasGet()) {
RootedValue get(cx, unwrapped->get_);
if (!dbg->unwrapDebuggeeValue(cx, &get) ||
!CheckArgCompartment(cx, obj, get, "defineProperty", "get"))
{
return false;
}
unwrapped->get_ = get;
}
if (unwrapped->hasSet()) {
RootedValue set(cx, unwrapped->set_);
if (!dbg->unwrapDebuggeeValue(cx, &set) ||
!CheckArgCompartment(cx, obj, set, "defineProperty", "set"))
{
return false;
}
unwrapped->set_ = set;
}
return true;
}
/*
* Rewrap *idp and the fields of *desc for the current compartment. Also:
* defining a property on a proxy requires pd_ to contain a descriptor object,
* so reconstitute desc->pd_ if needed.
*/
bool
PropDesc::wrapInto(JSContext *cx, HandleObject obj, const jsid &id, jsid *wrappedId,
PropDesc *desc) const
{
MOZ_ASSERT(!isUndefined());
JSCompartment *comp = cx->compartment();
*wrappedId = id;
if (!comp->wrapId(cx, wrappedId))
return false;
*desc = *this;
RootedValue value(cx, desc->value_);
RootedValue get(cx, desc->get_);
RootedValue set(cx, desc->set_);
if (!comp->wrap(cx, &value) || !comp->wrap(cx, &get) || !comp->wrap(cx, &set))
return false;
desc->value_ = value;
desc->get_ = get;
desc->set_ = set;
return !obj->isProxy() || desc->makeObject(cx);
}
static ObjectElements emptyElementsHeader(0, 0);
/* Objects with no elements share one empty set of elements. */
HeapSlot *js::emptyObjectElements =
reinterpret_cast<HeapSlot *>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
/* static */ bool
ObjectElements::ConvertElementsToDoubles(JSContext *cx, uintptr_t elementsPtr)
{
/*
* This function is infallible, but has a fallible interface so that it can
* be called directly from Ion code. Only arrays can have their dense
* elements converted to doubles, and arrays never have empty elements.
*/
HeapSlot *elementsHeapPtr = (HeapSlot *) elementsPtr;
JS_ASSERT(elementsHeapPtr != emptyObjectElements);
ObjectElements *header = ObjectElements::fromElements(elementsHeapPtr);
JS_ASSERT(!header->shouldConvertDoubleElements());
Value *vp = (Value *) elementsPtr;
for (size_t i = 0; i < header->initializedLength; i++) {
if (vp[i].isInt32())
vp[i].setDouble(vp[i].toInt32());
}
header->setShouldConvertDoubleElements();
return true;
}
#ifdef DEBUG
void
js::ObjectImpl::checkShapeConsistency()
{
static int throttle = -1;
if (throttle < 0) {
#if !defined(STARBOARD)
if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE"))
throttle = atoi(var);
#endif
if (throttle < 0)
throttle = 0;
}
if (throttle == 0)
return;
MOZ_ASSERT(isNative());
Shape *shape = lastProperty();
Shape *prev = NULL;
if (inDictionaryMode()) {
MOZ_ASSERT(shape->hasTable());
ShapeTable &table = shape->table();
for (uint32_t fslot = table.freelist; fslot != SHAPE_INVALID_SLOT;
fslot = getSlot(fslot).toPrivateUint32()) {
MOZ_ASSERT(fslot < slotSpan());
}
for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable());
Shape **spp = table.search(shape->propid(), false);
MOZ_ASSERT(SHAPE_FETCH(spp) == shape);
}
shape = lastProperty();
for (int n = throttle; --n >= 0 && shape; shape = shape->parent) {
MOZ_ASSERT_IF(shape->slot() != SHAPE_INVALID_SLOT, shape->slot() < slotSpan());
if (!prev) {
MOZ_ASSERT(lastProperty() == shape);
MOZ_ASSERT(shape->listp == &shape_);
} else {
MOZ_ASSERT(shape->listp == &prev->parent);
}
prev = shape;
}
} else {
for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
if (shape->hasTable()) {
ShapeTable &table = shape->table();
MOZ_ASSERT(shape->parent);
for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) {
Shape **spp = table.search(r.front().propid(), false);
MOZ_ASSERT(SHAPE_FETCH(spp) == &r.front());
}
}
if (prev) {
MOZ_ASSERT(prev->maybeSlot() >= shape->maybeSlot());
shape->kids.checkConsistency(prev);
}
prev = shape;
}
}
}
#endif
void
js::ObjectImpl::initializeSlotRange(uint32_t start, uint32_t length)
{
/*
* No bounds check, as this is used when the object's shape does not
* reflect its allocated slots (updateSlotsForSpan).
*/
HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd;
getSlotRangeUnchecked(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
JSRuntime *rt = runtime();
uint32_t offset = start;
for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++)
sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue());
for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++)
sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue());
}
void
js::ObjectImpl::initSlotRange(uint32_t start, const Value *vector, uint32_t length)
{
JSRuntime *rt = runtime();
HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd;
getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++)
sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++)
sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
}
void
js::ObjectImpl::copySlotRange(uint32_t start, const Value *vector, uint32_t length)
{
JS::Zone *zone = this->zone();
HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd;
getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++)
sp->set(zone, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++)
sp->set(zone, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
}
#ifdef DEBUG
bool
js::ObjectImpl::slotInRange(uint32_t slot, SentinelAllowed sentinel) const
{
uint32_t capacity = numFixedSlots() + numDynamicSlots();
if (sentinel == SENTINEL_ALLOWED)
return slot <= capacity;
return slot < capacity;
}
#endif /* DEBUG */
// See bug 844580.
#if defined(_MSC_VER)
# pragma optimize("g", off)
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1500
/*
* Work around a compiler bug in MSVC9 and above, where inlining this function
* causes stack pointer offsets to go awry and spp to refer to something higher
* up the stack.
*/
MOZ_NEVER_INLINE
#endif
Shape *
js::ObjectImpl::nativeLookup(JSContext *cx, jsid id)
{
MOZ_ASSERT(isNative());
Shape **spp;
return Shape::search(cx, lastProperty(), id, &spp);
}
#if defined(_MSC_VER)
# pragma optimize("", on)
#endif
Shape *
js::ObjectImpl::nativeLookupPure(jsid id)
{
MOZ_ASSERT(isNative());
return Shape::searchNoHashify(lastProperty(), id);
}
void
js::ObjectImpl::markChildren(JSTracer *trc)
{
MarkTypeObject(trc, &type_, "type");
MarkShape(trc, &shape_, "shape");
Class *clasp = type_->clasp;
JSObject *obj = asObjectPtr();
if (clasp->trace)
clasp->trace(trc, obj);
if (shape_->isNative()) {
MarkObjectSlots(trc, obj, 0, obj->slotSpan());
gc::MarkArraySlots(trc, obj->getDenseInitializedLength(), obj->getDenseElements(), "objectElements");
}
}
bool
DenseElementsHeader::getOwnElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index,
unsigned resolveFlags, PropDesc *desc)
{
MOZ_ASSERT(this == &obj->elementsHeader());
uint32_t len = initializedLength();
if (index >= len) {
*desc = PropDesc::undefined();
return true;
}
HeapSlot &slot = obj->elements[index];
if (slot.isMagic(JS_ELEMENTS_HOLE)) {
*desc = PropDesc::undefined();
return true;
}
*desc = PropDesc(slot, PropDesc::Writable, PropDesc::Enumerable, PropDesc::Configurable);
return true;
}
bool
SparseElementsHeader::getOwnElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index,
unsigned resolveFlags, PropDesc *desc)
{
MOZ_ASSERT(this == &obj->elementsHeader());
MOZ_NOT_REACHED("NYI");
return false;
}
template<typename T>
static Value
ElementToValue(const T &t)
{
return NumberValue(t);
}
template<>
/* static */ Value
ElementToValue(const uint8_clamped &u)
{
return NumberValue(uint8_t(u));
}
template<typename T>
bool
TypedElementsHeader<T>::getOwnElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index,
unsigned resolveFlags, PropDesc *desc)
{
MOZ_ASSERT(this == &obj->elementsHeader());
if (index >= length()) {
*desc = PropDesc::undefined();
return true;
}
*desc = PropDesc(ElementToValue(getElement(index)), PropDesc::Writable,
PropDesc::Enumerable, PropDesc::Configurable);
return false;
}
bool
ArrayBufferElementsHeader::getOwnElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index,
unsigned resolveFlags, PropDesc *desc)
{
MOZ_ASSERT(this == &obj->elementsHeader());
MOZ_NOT_REACHED("NYI");
return false;
}
bool
SparseElementsHeader::defineElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index,
const PropDesc &desc, bool shouldThrow, unsigned resolveFlags,
bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
MOZ_NOT_REACHED("NYI");
return false;
}
bool
DenseElementsHeader::defineElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index,
const PropDesc &desc, bool shouldThrow, unsigned resolveFlags,
bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
MOZ_ASSERT_IF(desc.hasGet() || desc.hasSet(), !desc.hasValue() && !desc.hasWritable());
MOZ_ASSERT_IF(desc.hasValue() || desc.hasWritable(), !desc.hasGet() && !desc.hasSet());
/*
* If desc is an accessor descriptor or a data descriptor with atypical
* attributes, convert to sparse and retry.
*/
if (desc.hasGet() || desc.hasSet() ||
(desc.hasEnumerable() && !desc.enumerable()) ||
(desc.hasConfigurable() && !desc.configurable()) ||
(desc.hasWritable() && !desc.writable()))
{
if (!obj->makeElementsSparse(cx))
return false;
SparseElementsHeader &elts = obj->elementsHeader().asSparseElements();
return elts.defineElement(cx, obj, index, desc, shouldThrow, resolveFlags, succeeded);
}
/* Does the element exist? All behavior depends upon this. */
uint32_t initLen = initializedLength();
if (index < initLen) {
HeapSlot &slot = obj->elements[index];
if (!slot.isMagic(JS_ELEMENTS_HOLE)) {
/*
* The element exists with attributes { [[Enumerable]]: true,
* [[Configurable]]: true, [[Writable]]: true, [[Value]]: slot }.
*/
// XXX jwalden fill this in!
}
}
/*
* If the element doesn't exist, we can only add it if the object is
* extensible.
*/
if (!obj->isExtensible()) {
*succeeded = false;
if (!shouldThrow)
return true;
RootedValue val(cx, ObjectValue(*obj));
MOZ_ALWAYS_FALSE(js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_OBJECT_NOT_EXTENSIBLE,
JSDVG_IGNORE_STACK,
val, NullPtr(),
NULL, NULL));
return false;
}
/* Otherwise we ensure space for it exists and that it's initialized. */
ObjectImpl::DenseElementsResult res = obj->ensureDenseElementsInitialized(cx, index, 0);
/* Propagate any error. */
if (res == ObjectImpl::Failure)
return false;
/* Otherwise, if the index was too far out of range, go sparse. */
if (res == ObjectImpl::ConvertToSparse) {
if (!obj->makeElementsSparse(cx))
return false;
SparseElementsHeader &elts = obj->elementsHeader().asSparseElements();
return elts.defineElement(cx, obj, index, desc, shouldThrow, resolveFlags, succeeded);
}
/* But if we were able to ensure the element's existence, we're good. */
MOZ_ASSERT(res == ObjectImpl::Succeeded);
obj->elements[index].set(obj->asObjectPtr(), HeapSlot::Element, index, desc.value());
*succeeded = true;
return true;
}
JSObject *
js::ArrayBufferDelegate(JSContext *cx, Handle<ObjectImpl*> obj)
{
MOZ_ASSERT(obj->hasClass(&ArrayBufferObject::class_));
if (obj->getPrivate())
return static_cast<JSObject *>(obj->getPrivate());
JSObject *delegate = NewObjectWithGivenProto(cx, &ObjectClass, obj->getProto(), NULL);
obj->setPrivateGCThing(delegate);
return delegate;
}
template <typename T>
bool
TypedElementsHeader<T>::defineElement(JSContext *cx, Handle<ObjectImpl*> obj,
uint32_t index, const PropDesc &desc, bool shouldThrow,
unsigned resolveFlags, bool *succeeded)
{
/* XXX jwalden This probably isn't how typed arrays should behave... */
*succeeded = false;
RootedValue val(cx, ObjectValue(*obj));
js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_OBJECT_NOT_EXTENSIBLE,
JSDVG_IGNORE_STACK,
val, NullPtr(), NULL, NULL);
return false;
}
bool
ArrayBufferElementsHeader::defineElement(JSContext *cx, Handle<ObjectImpl*> obj,
uint32_t index, const PropDesc &desc, bool shouldThrow,
unsigned resolveFlags, bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
Rooted<JSObject*> delegate(cx, ArrayBufferDelegate(cx, obj));
if (!delegate)
return false;
return DefineElement(cx, delegate, index, desc, shouldThrow, resolveFlags, succeeded);
}
bool
js::GetOwnProperty(JSContext *cx, Handle<ObjectImpl*> obj, PropertyId pid_, unsigned resolveFlags,
PropDesc *desc)
{
NEW_OBJECT_REPRESENTATION_ONLY();
JS_CHECK_RECURSION(cx, return false);
Rooted<PropertyId> pid(cx, pid_);
if (static_cast<JSObject *>(obj.get())->isProxy()) {
MOZ_NOT_REACHED("NYI: proxy [[GetOwnProperty]]");
return false;
}
RootedShape shape(cx, obj->nativeLookup(cx, pid));
if (!shape) {
/* Not found: attempt to resolve it. */
Class *clasp = obj->getClass();
JSResolveOp resolve = clasp->resolve;
if (resolve != JS_ResolveStub) {
Rooted<jsid> id(cx, pid.get().asId());
Rooted<JSObject*> robj(cx, static_cast<JSObject*>(obj.get()));
if (clasp->flags & JSCLASS_NEW_RESOLVE) {
Rooted<JSObject*> obj2(cx, NULL);
JSNewResolveOp op = reinterpret_cast<JSNewResolveOp>(resolve);
if (!op(cx, robj, id, resolveFlags, &obj2))
return false;
} else {
if (!resolve(cx, robj, id))
return false;
}
}
/* Now look it up again. */
shape = obj->nativeLookup(cx, pid);
if (!shape) {
desc->setUndefined();
return true;
}
}
if (shape->isDataDescriptor()) {
*desc = PropDesc(obj->nativeGetSlot(shape->slot()), shape->writability(),
shape->enumerability(), shape->configurability());
return true;
}
if (shape->isAccessorDescriptor()) {
*desc = PropDesc(shape->getterValue(), shape->setterValue(),
shape->enumerability(), shape->configurability());
return true;
}
MOZ_NOT_REACHED("NYI: PropertyOp-based properties");
return false;
}
bool
js::GetOwnElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index, unsigned resolveFlags,
PropDesc *desc)
{
ElementsHeader &header = obj->elementsHeader();
switch (header.kind()) {
case DenseElements:
return header.asDenseElements().getOwnElement(cx, obj, index, resolveFlags, desc);
case SparseElements:
return header.asSparseElements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Uint8Elements:
return header.asUint8Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Int8Elements:
return header.asInt8Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Uint16Elements:
return header.asUint16Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Int16Elements:
return header.asInt16Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Uint32Elements:
return header.asUint32Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Int32Elements:
return header.asInt32Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Uint8ClampedElements:
return header.asUint8ClampedElements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Float32Elements:
return header.asFloat32Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case Float64Elements:
return header.asFloat64Elements().getOwnElement(cx, obj, index, resolveFlags, desc);
case ArrayBufferElements:
return header.asArrayBufferElements().getOwnElement(cx, obj, index, resolveFlags, desc);
}
MOZ_NOT_REACHED("bad elements kind!");
return false;
}
bool
js::GetProperty(JSContext *cx, Handle<ObjectImpl*> obj, Handle<ObjectImpl*> receiver,
Handle<PropertyId> pid, unsigned resolveFlags, MutableHandle<Value> vp)
{
NEW_OBJECT_REPRESENTATION_ONLY();
MOZ_ASSERT(receiver);
Rooted<ObjectImpl*> current(cx, obj);
do {
MOZ_ASSERT(obj);
if (Downcast(current)->isProxy()) {
MOZ_NOT_REACHED("NYI: proxy [[GetP]]");
return false;
}
AutoPropDescRooter desc(cx);
if (!GetOwnProperty(cx, current, pid, resolveFlags, &desc.getPropDesc()))
return false;
/* No property? Recur or bottom out. */
if (desc.isUndefined()) {
current = current->getProto();
if (current)
continue;
vp.setUndefined();
return true;
}
/* If it's a data property, return the value. */
if (desc.isDataDescriptor()) {
vp.set(desc.value());
return true;
}
/* If it's an accessor property, call its [[Get]] with the receiver. */
if (desc.isAccessorDescriptor()) {
Rooted<Value> get(cx, desc.getterValue());
if (get.isUndefined()) {
vp.setUndefined();
return true;
}
InvokeArgs args(cx);
if (!args.init(0))
return false;
args.setCallee(get);
args.setThis(ObjectValue(*receiver));
bool ok = Invoke(cx, args);
vp.set(args.rval());
return ok;
}
/* Otherwise it's a PropertyOp-based property. XXX handle this! */
MOZ_NOT_REACHED("NYI: handle PropertyOp'd properties here");
return false;
} while (false);
MOZ_NOT_REACHED("buggy control flow");
return false;
}
bool
js::GetElement(JSContext *cx, Handle<ObjectImpl*> obj, Handle<ObjectImpl*> receiver, uint32_t index,
unsigned resolveFlags, Value *vp)
{
NEW_OBJECT_REPRESENTATION_ONLY();
Rooted<ObjectImpl*> current(cx, obj);
RootedValue getter(cx);
do {
MOZ_ASSERT(current);
if (Downcast(current)->isProxy()) {
MOZ_NOT_REACHED("NYI: proxy [[GetP]]");
return false;
}
PropDesc desc;
if (!GetOwnElement(cx, current, index, resolveFlags, &desc))
return false;
/* No property? Recur or bottom out. */
if (desc.isUndefined()) {
current = current->getProto();
if (current)
continue;
vp->setUndefined();
return true;
}
/* If it's a data property, return the value. */
if (desc.isDataDescriptor()) {
*vp = desc.value();
return true;
}
/* If it's an accessor property, call its [[Get]] with the receiver. */
if (desc.isAccessorDescriptor()) {
getter = desc.getterValue();
if (getter.isUndefined()) {
vp->setUndefined();
return true;
}
InvokeArgs args(cx);
if (!args.init(0))
return false;
/* Push getter, receiver, and no args. */
args.setCallee(getter);
args.setThis(ObjectValue(*current));
bool ok = Invoke(cx, args);
*vp = args.rval();
return ok;
}
/* Otherwise it's a PropertyOp-based property. XXX handle this! */
MOZ_NOT_REACHED("NYI: handle PropertyOp'd properties here");
return false;
} while (false);
MOZ_NOT_REACHED("buggy control flow");
return false;
}
bool
js::HasElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index, unsigned resolveFlags,
bool *found)
{
NEW_OBJECT_REPRESENTATION_ONLY();
Rooted<ObjectImpl*> current(cx, obj);
do {
MOZ_ASSERT(current);
if (Downcast(current)->isProxy()) {
MOZ_NOT_REACHED("NYI: proxy [[HasProperty]]");
return false;
}
PropDesc prop;
if (!GetOwnElement(cx, current, index, resolveFlags, &prop))
return false;
if (!prop.isUndefined()) {
*found = true;
return true;
}
current = current->getProto();
if (current)
continue;
*found = false;
return true;
} while (false);
MOZ_NOT_REACHED("buggy control flow");
return false;
}
bool
js::DefineElement(JSContext *cx, Handle<ObjectImpl*> obj, uint32_t index, const PropDesc &desc,
bool shouldThrow, unsigned resolveFlags, bool *succeeded)
{
NEW_OBJECT_REPRESENTATION_ONLY();
ElementsHeader &header = obj->elementsHeader();
switch (header.kind()) {
case DenseElements:
return header.asDenseElements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case SparseElements:
return header.asSparseElements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Uint8Elements:
return header.asUint8Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Int8Elements:
return header.asInt8Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Uint16Elements:
return header.asUint16Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Int16Elements:
return header.asInt16Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Uint32Elements:
return header.asUint32Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Int32Elements:
return header.asInt32Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Uint8ClampedElements:
return header.asUint8ClampedElements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Float32Elements:
return header.asFloat32Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case Float64Elements:
return header.asFloat64Elements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
case ArrayBufferElements:
return header.asArrayBufferElements().defineElement(cx, obj, index, desc, shouldThrow,
resolveFlags, succeeded);
}
MOZ_NOT_REACHED("bad elements kind!");
return false;
}
bool
SparseElementsHeader::setElement(JSContext *cx, Handle<ObjectImpl*> obj,
Handle<ObjectImpl*> receiver, uint32_t index, const Value &v,
unsigned resolveFlags, bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
MOZ_NOT_REACHED("NYI");
return false;
}
bool
DenseElementsHeader::setElement(JSContext *cx, Handle<ObjectImpl*> obj,
Handle<ObjectImpl*> receiver, uint32_t index, const Value &v,
unsigned resolveFlags, bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
MOZ_NOT_REACHED("NYI");
return false;
}
template <typename T>
bool
TypedElementsHeader<T>::setElement(JSContext *cx, Handle<ObjectImpl*> obj,
Handle<ObjectImpl*> receiver, uint32_t index, const Value &v,
unsigned resolveFlags, bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
uint32_t len = length();
if (index >= len) {
/*
* Silent ignore is better than an exception here, because at some
* point we may want to support other properties on these objects.
*/
*succeeded = true;
return true;
}
/* Convert the value being set to the element type. */
double d;
if (v.isNumber()) {
d = v.toNumber();
} else if (v.isNull()) {
d = 0.0;
} else if (v.isPrimitive()) {
if (v.isString()) {
if (!ToNumber(cx, v, &d))
return false;
} else if (v.isUndefined()) {
d = js_NaN;
} else {
d = double(v.toBoolean());
}
} else {
// non-primitive assignments become NaN or 0 (for float/int arrays)
d = js_NaN;
}
assign(index, d);
*succeeded = true;
return true;
}
bool
ArrayBufferElementsHeader::setElement(JSContext *cx, Handle<ObjectImpl*> obj,
Handle<ObjectImpl*> receiver, uint32_t index, const Value &v,
unsigned resolveFlags, bool *succeeded)
{
MOZ_ASSERT(this == &obj->elementsHeader());
JSObject *delegate = ArrayBufferDelegate(cx, obj);
if (!delegate)
return false;
return SetElement(cx, obj, receiver, index, v, resolveFlags, succeeded);
}
bool
js::SetElement(JSContext *cx, Handle<ObjectImpl*> obj, Handle<ObjectImpl*> receiver,
uint32_t index, const Value &v, unsigned resolveFlags, bool *succeeded)
{
NEW_OBJECT_REPRESENTATION_ONLY();
Rooted<ObjectImpl*> current(cx, obj);
RootedValue setter(cx);
MOZ_ASSERT(receiver);
do {
MOZ_ASSERT(current);
if (Downcast(current)->isProxy()) {
MOZ_NOT_REACHED("NYI: proxy [[SetP]]");
return false;
}
PropDesc ownDesc;
if (!GetOwnElement(cx, current, index, resolveFlags, &ownDesc))
return false;
if (!ownDesc.isUndefined()) {
if (ownDesc.isDataDescriptor()) {
if (!ownDesc.writable()) {
*succeeded = false;
return true;
}
if (receiver == current) {
PropDesc updateDesc = PropDesc::valueOnly(v);
return DefineElement(cx, receiver, index, updateDesc, false, resolveFlags,
succeeded);
}
PropDesc newDesc;
return DefineElement(cx, receiver, index, newDesc, false, resolveFlags, succeeded);
}
if (ownDesc.isAccessorDescriptor()) {
setter = ownDesc.setterValue();
if (setter.isUndefined()) {
*succeeded = false;
return true;
}
InvokeArgs args(cx);
if (!args.init(1))
return false;
/* Push set, receiver, and v as the sole argument. */
args.setCallee(setter);
args.setThis(ObjectValue(*current));
args[0] = v;
*succeeded = true;
return Invoke(cx, args);
}
MOZ_NOT_REACHED("NYI: setting PropertyOp-based property");
return false;
}
current = current->getProto();
if (current)
continue;
PropDesc newDesc(v, PropDesc::Writable, PropDesc::Enumerable, PropDesc::Configurable);
return DefineElement(cx, receiver, index, newDesc, false, resolveFlags, succeeded);
} while (false);
MOZ_NOT_REACHED("buggy control flow");
return false;
}
void
AutoPropDescRooter::trace(JSTracer *trc)
{
gc::MarkValueRoot(trc, &propDesc.pd_, "AutoPropDescRooter pd");
gc::MarkValueRoot(trc, &propDesc.value_, "AutoPropDescRooter value");
gc::MarkValueRoot(trc, &propDesc.get_, "AutoPropDescRooter get");
gc::MarkValueRoot(trc, &propDesc.set_, "AutoPropDescRooter set");
}