michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/ObjectImpl-inl.h" michael@0: michael@0: #include "gc/Marking.h" michael@0: #include "js/Value.h" michael@0: #include "vm/Debugger.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: #include "vm/Shape-inl.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using JS::GenericNaN; michael@0: michael@0: PropDesc::PropDesc() michael@0: : pd_(UndefinedValue()), michael@0: value_(UndefinedValue()), michael@0: get_(UndefinedValue()), michael@0: set_(UndefinedValue()), michael@0: attrs(0), michael@0: hasGet_(false), michael@0: hasSet_(false), michael@0: hasValue_(false), michael@0: hasWritable_(false), michael@0: hasEnumerable_(false), michael@0: hasConfigurable_(false), michael@0: isUndefined_(true) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: PropDesc::checkGetter(JSContext *cx) michael@0: { michael@0: if (hasGet_) { michael@0: if (!js_IsCallable(get_) && !get_.isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD, michael@0: js_getter_str); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: PropDesc::checkSetter(JSContext *cx) michael@0: { michael@0: if (hasSet_) { michael@0: if (!js_IsCallable(set_) && !set_.isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD, michael@0: js_setter_str); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: CheckArgCompartment(JSContext *cx, JSObject *obj, HandleValue v, michael@0: const char *methodname, const char *propname) michael@0: { michael@0: if (v.isObject() && v.toObject().compartment() != obj->compartment()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_COMPARTMENT_MISMATCH, michael@0: methodname, propname); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Convert Debugger.Objects in desc to debuggee values. michael@0: * Reject non-callable getters and setters. michael@0: */ michael@0: bool michael@0: PropDesc::unwrapDebuggerObjectsInto(JSContext *cx, Debugger *dbg, HandleObject obj, michael@0: PropDesc *unwrapped) const michael@0: { michael@0: MOZ_ASSERT(!isUndefined()); michael@0: michael@0: *unwrapped = *this; michael@0: michael@0: if (unwrapped->hasValue()) { michael@0: RootedValue value(cx, unwrapped->value_); michael@0: if (!dbg->unwrapDebuggeeValue(cx, &value) || michael@0: !CheckArgCompartment(cx, obj, value, "defineProperty", "value")) michael@0: { michael@0: return false; michael@0: } michael@0: unwrapped->value_ = value; michael@0: } michael@0: michael@0: if (unwrapped->hasGet()) { michael@0: RootedValue get(cx, unwrapped->get_); michael@0: if (!dbg->unwrapDebuggeeValue(cx, &get) || michael@0: !CheckArgCompartment(cx, obj, get, "defineProperty", "get")) michael@0: { michael@0: return false; michael@0: } michael@0: unwrapped->get_ = get; michael@0: } michael@0: michael@0: if (unwrapped->hasSet()) { michael@0: RootedValue set(cx, unwrapped->set_); michael@0: if (!dbg->unwrapDebuggeeValue(cx, &set) || michael@0: !CheckArgCompartment(cx, obj, set, "defineProperty", "set")) michael@0: { michael@0: return false; michael@0: } michael@0: unwrapped->set_ = set; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Rewrap *idp and the fields of *desc for the current compartment. Also: michael@0: * defining a property on a proxy requires pd_ to contain a descriptor object, michael@0: * so reconstitute desc->pd_ if needed. michael@0: */ michael@0: bool michael@0: PropDesc::wrapInto(JSContext *cx, HandleObject obj, const jsid &id, jsid *wrappedId, michael@0: PropDesc *desc) const michael@0: { michael@0: MOZ_ASSERT(!isUndefined()); michael@0: michael@0: JSCompartment *comp = cx->compartment(); michael@0: michael@0: *wrappedId = id; michael@0: if (!comp->wrapId(cx, wrappedId)) michael@0: return false; michael@0: michael@0: *desc = *this; michael@0: RootedValue value(cx, desc->value_); michael@0: RootedValue get(cx, desc->get_); michael@0: RootedValue set(cx, desc->set_); michael@0: michael@0: if (!comp->wrap(cx, &value) || !comp->wrap(cx, &get) || !comp->wrap(cx, &set)) michael@0: return false; michael@0: michael@0: desc->value_ = value; michael@0: desc->get_ = get; michael@0: desc->set_ = set; michael@0: return !obj->is() || desc->makeObject(cx); michael@0: } michael@0: michael@0: static const ObjectElements emptyElementsHeader(0, 0); michael@0: michael@0: /* Objects with no elements share one empty set of elements. */ michael@0: HeapSlot *const js::emptyObjectElements = michael@0: reinterpret_cast(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements)); michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: bool michael@0: ObjectImpl::canHaveNonEmptyElements() michael@0: { michael@0: JSObject *obj = static_cast(this); michael@0: return isNative() && !obj->is(); michael@0: } michael@0: michael@0: #endif // DEBUG michael@0: michael@0: /* static */ bool michael@0: ObjectElements::ConvertElementsToDoubles(JSContext *cx, uintptr_t elementsPtr) michael@0: { michael@0: /* michael@0: * This function is infallible, but has a fallible interface so that it can michael@0: * be called directly from Ion code. Only arrays can have their dense michael@0: * elements converted to doubles, and arrays never have empty elements. michael@0: */ michael@0: HeapSlot *elementsHeapPtr = (HeapSlot *) elementsPtr; michael@0: JS_ASSERT(elementsHeapPtr != emptyObjectElements); michael@0: michael@0: ObjectElements *header = ObjectElements::fromElements(elementsHeapPtr); michael@0: JS_ASSERT(!header->shouldConvertDoubleElements()); michael@0: michael@0: Value *vp = (Value *) elementsPtr; michael@0: for (size_t i = 0; i < header->initializedLength; i++) { michael@0: if (vp[i].isInt32()) michael@0: vp[i].setDouble(vp[i].toInt32()); michael@0: } michael@0: michael@0: header->setShouldConvertDoubleElements(); michael@0: return true; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: js::ObjectImpl::checkShapeConsistency() michael@0: { michael@0: static int throttle = -1; michael@0: if (throttle < 0) { michael@0: if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE")) michael@0: throttle = atoi(var); michael@0: if (throttle < 0) michael@0: throttle = 0; michael@0: } michael@0: if (throttle == 0) michael@0: return; michael@0: michael@0: MOZ_ASSERT(isNative()); michael@0: michael@0: Shape *shape = lastProperty(); michael@0: Shape *prev = nullptr; michael@0: michael@0: if (inDictionaryMode()) { michael@0: MOZ_ASSERT(shape->hasTable()); michael@0: michael@0: ShapeTable &table = shape->table(); michael@0: for (uint32_t fslot = table.freelist; fslot != SHAPE_INVALID_SLOT; michael@0: fslot = getSlot(fslot).toPrivateUint32()) { michael@0: MOZ_ASSERT(fslot < slotSpan()); michael@0: } michael@0: michael@0: for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) { michael@0: MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable()); michael@0: michael@0: Shape **spp = table.search(shape->propid(), false); michael@0: MOZ_ASSERT(SHAPE_FETCH(spp) == shape); michael@0: } michael@0: michael@0: shape = lastProperty(); michael@0: for (int n = throttle; --n >= 0 && shape; shape = shape->parent) { michael@0: MOZ_ASSERT_IF(shape->slot() != SHAPE_INVALID_SLOT, shape->slot() < slotSpan()); michael@0: if (!prev) { michael@0: MOZ_ASSERT(lastProperty() == shape); michael@0: MOZ_ASSERT(shape->listp == &shape_); michael@0: } else { michael@0: MOZ_ASSERT(shape->listp == &prev->parent); michael@0: } michael@0: prev = shape; michael@0: } michael@0: } else { michael@0: for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) { michael@0: if (shape->hasTable()) { michael@0: ShapeTable &table = shape->table(); michael@0: MOZ_ASSERT(shape->parent); michael@0: for (Shape::Range r(shape); !r.empty(); r.popFront()) { michael@0: Shape **spp = table.search(r.front().propid(), false); michael@0: MOZ_ASSERT(SHAPE_FETCH(spp) == &r.front()); michael@0: } michael@0: } michael@0: if (prev) { michael@0: MOZ_ASSERT(prev->maybeSlot() >= shape->maybeSlot()); michael@0: shape->kids.checkConsistency(prev); michael@0: } michael@0: prev = shape; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: js::ObjectImpl::initializeSlotRange(uint32_t start, uint32_t length) michael@0: { michael@0: /* michael@0: * No bounds check, as this is used when the object's shape does not michael@0: * reflect its allocated slots (updateSlotsForSpan). michael@0: */ michael@0: HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; michael@0: getSlotRangeUnchecked(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); michael@0: michael@0: JSRuntime *rt = runtimeFromAnyThread(); michael@0: uint32_t offset = start; michael@0: for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++) michael@0: sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue()); michael@0: for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++) michael@0: sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue()); michael@0: } michael@0: michael@0: void michael@0: js::ObjectImpl::initSlotRange(uint32_t start, const Value *vector, uint32_t length) michael@0: { michael@0: JSRuntime *rt = runtimeFromAnyThread(); michael@0: HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; michael@0: getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); michael@0: for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++) michael@0: sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); michael@0: for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++) michael@0: sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); michael@0: } michael@0: michael@0: void michael@0: js::ObjectImpl::copySlotRange(uint32_t start, const Value *vector, uint32_t length) michael@0: { michael@0: JS::Zone *zone = this->zone(); michael@0: HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; michael@0: getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); michael@0: for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++) michael@0: sp->set(zone, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); michael@0: for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++) michael@0: sp->set(zone, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: js::ObjectImpl::isProxy() const michael@0: { michael@0: return asObjectPtr()->is(); michael@0: } michael@0: michael@0: bool michael@0: js::ObjectImpl::slotInRange(uint32_t slot, SentinelAllowed sentinel) const michael@0: { michael@0: uint32_t capacity = numFixedSlots() + numDynamicSlots(); michael@0: if (sentinel == SENTINEL_ALLOWED) michael@0: return slot <= capacity; michael@0: return slot < capacity; michael@0: } michael@0: #endif /* DEBUG */ michael@0: michael@0: // See bug 844580. michael@0: #if defined(_MSC_VER) michael@0: # pragma optimize("g", off) michael@0: #endif michael@0: michael@0: #if defined(_MSC_VER) && _MSC_VER >= 1500 michael@0: /* michael@0: * Work around a compiler bug in MSVC9 and above, where inlining this function michael@0: * causes stack pointer offsets to go awry and spp to refer to something higher michael@0: * up the stack. michael@0: */ michael@0: MOZ_NEVER_INLINE michael@0: #endif michael@0: Shape * michael@0: js::ObjectImpl::nativeLookup(ExclusiveContext *cx, jsid id) michael@0: { michael@0: MOZ_ASSERT(isNative()); michael@0: Shape **spp; michael@0: return Shape::search(cx, lastProperty(), id, &spp); michael@0: } michael@0: michael@0: #if defined(_MSC_VER) michael@0: # pragma optimize("", on) michael@0: #endif michael@0: michael@0: Shape * michael@0: js::ObjectImpl::nativeLookupPure(jsid id) michael@0: { michael@0: MOZ_ASSERT(isNative()); michael@0: return Shape::searchNoHashify(lastProperty(), id); michael@0: } michael@0: michael@0: uint32_t michael@0: js::ObjectImpl::dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class *clasp) michael@0: { michael@0: if (span <= nfixed) michael@0: return 0; michael@0: span -= nfixed; michael@0: michael@0: // Increase the slots to SLOT_CAPACITY_MIN to decrease the likelihood michael@0: // the dynamic slots need to get increased again. ArrayObjects ignore michael@0: // this because slots are uncommon in that case. michael@0: if (clasp != &ArrayObject::class_ && span <= SLOT_CAPACITY_MIN) michael@0: return SLOT_CAPACITY_MIN; michael@0: michael@0: uint32_t slots = mozilla::RoundUpPow2(span); michael@0: MOZ_ASSERT(slots >= span); michael@0: return slots; michael@0: } michael@0: michael@0: void michael@0: js::ObjectImpl::markChildren(JSTracer *trc) michael@0: { michael@0: MarkTypeObject(trc, &type_, "type"); michael@0: michael@0: MarkShape(trc, &shape_, "shape"); michael@0: michael@0: const Class *clasp = type_->clasp(); michael@0: JSObject *obj = asObjectPtr(); michael@0: if (clasp->trace) michael@0: clasp->trace(trc, obj); michael@0: michael@0: if (shape_->isNative()) { michael@0: MarkObjectSlots(trc, obj, 0, obj->slotSpan()); michael@0: gc::MarkArraySlots(trc, obj->getDenseInitializedLength(), obj->getDenseElements(), "objectElements"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AutoPropDescRooter::trace(JSTracer *trc) michael@0: { michael@0: gc::MarkValueRoot(trc, &propDesc.pd_, "AutoPropDescRooter pd"); michael@0: gc::MarkValueRoot(trc, &propDesc.value_, "AutoPropDescRooter value"); michael@0: gc::MarkValueRoot(trc, &propDesc.get_, "AutoPropDescRooter get"); michael@0: gc::MarkValueRoot(trc, &propDesc.set_, "AutoPropDescRooter set"); michael@0: }