| /* -*- 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"); |
| } |