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: michael@0: #include "vm/SavedStacks.h" michael@0: michael@0: #include "jscompartment.h" michael@0: #include "jsnum.h" michael@0: michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: using mozilla::AddToHash; michael@0: using mozilla::HashString; michael@0: michael@0: namespace js { michael@0: michael@0: /* static */ HashNumber michael@0: SavedFrame::HashPolicy::hash(const Lookup &lookup) michael@0: { michael@0: return AddToHash(HashString(lookup.source->chars(), lookup.source->length()), michael@0: lookup.line, michael@0: lookup.column, michael@0: lookup.functionDisplayName, michael@0: SavedFramePtrHasher::hash(lookup.parent), michael@0: JSPrincipalsPtrHasher::hash(lookup.principals)); michael@0: } michael@0: michael@0: /* static */ bool michael@0: SavedFrame::HashPolicy::match(SavedFrame *existing, const Lookup &lookup) michael@0: { michael@0: if (existing->getLine() != lookup.line) michael@0: return false; michael@0: michael@0: if (existing->getColumn() != lookup.column) michael@0: return false; michael@0: michael@0: if (existing->getParent() != lookup.parent) michael@0: return false; michael@0: michael@0: if (existing->getPrincipals() != lookup.principals) michael@0: return false; michael@0: michael@0: JSAtom *source = existing->getSource(); michael@0: if (source->length() != lookup.source->length()) michael@0: return false; michael@0: if (source != lookup.source) michael@0: return false; michael@0: michael@0: JSAtom *functionDisplayName = existing->getFunctionDisplayName(); michael@0: if (functionDisplayName) { michael@0: if (!lookup.functionDisplayName) michael@0: return false; michael@0: if (functionDisplayName->length() != lookup.functionDisplayName->length()) michael@0: return false; michael@0: if (0 != CompareAtoms(functionDisplayName, lookup.functionDisplayName)) michael@0: return false; michael@0: } else if (lookup.functionDisplayName) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ void michael@0: SavedFrame::HashPolicy::rekey(Key &key, const Key &newKey) michael@0: { michael@0: key = newKey; michael@0: } michael@0: michael@0: /* static */ const Class SavedFrame::class_ = { michael@0: "SavedFrame", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT), michael@0: michael@0: JS_PropertyStub, // addProperty michael@0: JS_DeletePropertyStub, // delProperty michael@0: JS_PropertyStub, // getProperty michael@0: JS_StrictPropertyStub, // setProperty michael@0: JS_EnumerateStub, // enumerate michael@0: JS_ResolveStub, // resolve michael@0: JS_ConvertStub, // convert michael@0: michael@0: SavedFrame::finalize // finalize michael@0: }; michael@0: michael@0: /* static */ void michael@0: SavedFrame::finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: JSPrincipals *p = obj->as().getPrincipals(); michael@0: if (p) { michael@0: JSRuntime *rt = obj->runtimeFromMainThread(); michael@0: JS_DropPrincipals(rt, p); michael@0: } michael@0: } michael@0: michael@0: JSAtom * michael@0: SavedFrame::getSource() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_SOURCE); michael@0: JSString *s = v.toString(); michael@0: return &s->asAtom(); michael@0: } michael@0: michael@0: size_t michael@0: SavedFrame::getLine() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_LINE); michael@0: return v.toInt32(); michael@0: } michael@0: michael@0: size_t michael@0: SavedFrame::getColumn() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_COLUMN); michael@0: return v.toInt32(); michael@0: } michael@0: michael@0: JSAtom * michael@0: SavedFrame::getFunctionDisplayName() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME); michael@0: if (v.isNull()) michael@0: return nullptr; michael@0: JSString *s = v.toString(); michael@0: return &s->asAtom(); michael@0: } michael@0: michael@0: SavedFrame * michael@0: SavedFrame::getParent() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_PARENT); michael@0: return v.isObject() ? &v.toObject().as() : nullptr; michael@0: } michael@0: michael@0: JSPrincipals * michael@0: SavedFrame::getPrincipals() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_PRINCIPALS); michael@0: if (v.isUndefined()) michael@0: return nullptr; michael@0: return static_cast(v.toPrivate()); michael@0: } michael@0: michael@0: void michael@0: SavedFrame::initFromLookup(Lookup &lookup) michael@0: { michael@0: JS_ASSERT(lookup.source); michael@0: JS_ASSERT(getReservedSlot(JSSLOT_SOURCE).isUndefined()); michael@0: setReservedSlot(JSSLOT_SOURCE, StringValue(lookup.source)); michael@0: michael@0: setReservedSlot(JSSLOT_LINE, NumberValue(lookup.line)); michael@0: setReservedSlot(JSSLOT_COLUMN, NumberValue(lookup.column)); michael@0: setReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME, michael@0: lookup.functionDisplayName michael@0: ? StringValue(lookup.functionDisplayName) michael@0: : NullValue()); michael@0: setReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(lookup.parent)); michael@0: setReservedSlot(JSSLOT_PRIVATE_PARENT, PrivateValue(lookup.parent)); michael@0: michael@0: JS_ASSERT(getReservedSlot(JSSLOT_PRINCIPALS).isUndefined()); michael@0: if (lookup.principals) michael@0: JS_HoldPrincipals(lookup.principals); michael@0: setReservedSlot(JSSLOT_PRINCIPALS, PrivateValue(lookup.principals)); michael@0: } michael@0: michael@0: bool michael@0: SavedFrame::parentMoved() michael@0: { michael@0: const Value &v = getReservedSlot(JSSLOT_PRIVATE_PARENT); michael@0: JSObject *p = static_cast(v.toPrivate()); michael@0: return p == getParent(); michael@0: } michael@0: michael@0: void michael@0: SavedFrame::updatePrivateParent() michael@0: { michael@0: setReservedSlot(JSSLOT_PRIVATE_PARENT, PrivateValue(getParent())); michael@0: } michael@0: michael@0: bool michael@0: SavedFrame::isSelfHosted() michael@0: { michael@0: JSAtom *source = getSource(); michael@0: return StringEqualsAscii(source, "self-hosted"); michael@0: } michael@0: michael@0: /* static */ bool michael@0: SavedFrame::construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, michael@0: "SavedFrame"); michael@0: return false; michael@0: } michael@0: michael@0: /* static */ SavedFrame * michael@0: SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName) michael@0: { michael@0: const Value &thisValue = args.thisv(); michael@0: michael@0: if (!thisValue.isObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSObject &thisObject = thisValue.toObject(); michael@0: if (!thisObject.is()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: SavedFrame::class_.name, fnName, thisObject.getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Check for SavedFrame.prototype, which has the same class as SavedFrame michael@0: // instances, however doesn't actually represent a captured stack frame. It michael@0: // is the only object that is() but doesn't have a source. michael@0: if (thisObject.getReservedSlot(JSSLOT_SOURCE).isNull()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: SavedFrame::class_.name, fnName, "prototype object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: return &thisObject.as(); michael@0: } michael@0: michael@0: // Get the SavedFrame * from the current this value and handle any errors that michael@0: // might occur therein. michael@0: // michael@0: // These parameters must already exist when calling this macro: michael@0: // - JSContext *cx michael@0: // - unsigned argc michael@0: // - Value *vp michael@0: // - const char *fnName michael@0: // These parameters will be defined after calling this macro: michael@0: // - CallArgs args michael@0: // - Rooted frame (will be non-null) michael@0: #define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: Rooted frame(cx, checkThis(cx, args, fnName)); \ michael@0: if (!frame) \ michael@0: return false michael@0: michael@0: /* static */ bool michael@0: SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame); michael@0: args.rval().setString(frame->getSource()); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame); michael@0: uint32_t line = frame->getLine(); michael@0: args.rval().setNumber(line); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_SAVEDFRAME(cx, argc, vp, "(get column)", args, frame); michael@0: uint32_t column = frame->getColumn(); michael@0: args.rval().setNumber(column); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame); michael@0: RootedAtom name(cx, frame->getFunctionDisplayName()); michael@0: if (name) michael@0: args.rval().setString(name); michael@0: else michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame); michael@0: JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; michael@0: JSPrincipals *principals = cx->compartment()->principals; michael@0: michael@0: do michael@0: frame = frame->getParent(); michael@0: while (frame && principals && subsumes && michael@0: !subsumes(principals, frame->getPrincipals())); michael@0: michael@0: args.rval().setObjectOrNull(frame); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ const JSPropertySpec SavedFrame::properties[] = { michael@0: JS_PSG("source", SavedFrame::sourceProperty, 0), michael@0: JS_PSG("line", SavedFrame::lineProperty, 0), michael@0: JS_PSG("column", SavedFrame::columnProperty, 0), michael@0: JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0), michael@0: JS_PSG("parent", SavedFrame::parentProperty, 0), michael@0: JS_PS_END michael@0: }; michael@0: michael@0: /* static */ bool michael@0: SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame); michael@0: StringBuffer sb(cx); michael@0: JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; michael@0: JSPrincipals *principals = cx->compartment()->principals; michael@0: michael@0: do { michael@0: if (principals && subsumes && !subsumes(principals, frame->getPrincipals())) michael@0: continue; michael@0: if (frame->isSelfHosted()) michael@0: continue; michael@0: michael@0: RootedAtom name(cx, frame->getFunctionDisplayName()); michael@0: if ((name && !sb.append(name)) michael@0: || !sb.append('@') michael@0: || !sb.append(frame->getSource()) michael@0: || !sb.append(':') michael@0: || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb) michael@0: || !sb.append(':') michael@0: || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb) michael@0: || !sb.append('\n')) { michael@0: return false; michael@0: } michael@0: } while ((frame = frame->getParent())); michael@0: michael@0: args.rval().setString(sb.finishString()); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ const JSFunctionSpec SavedFrame::methods[] = { michael@0: JS_FN("toString", SavedFrame::toStringMethod, 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: bool michael@0: SavedStacks::init() michael@0: { michael@0: return frames.init(); michael@0: } michael@0: michael@0: bool michael@0: SavedStacks::saveCurrentStack(JSContext *cx, MutableHandle frame) michael@0: { michael@0: JS_ASSERT(initialized()); michael@0: JS_ASSERT(&cx->compartment()->savedStacks() == this); michael@0: michael@0: ScriptFrameIter iter(cx); michael@0: return insertFrames(cx, iter, frame); michael@0: } michael@0: michael@0: void michael@0: SavedStacks::sweep(JSRuntime *rt) michael@0: { michael@0: if (frames.initialized()) { michael@0: for (SavedFrame::Set::Enum e(frames); !e.empty(); e.popFront()) { michael@0: JSObject *obj = static_cast(e.front()); michael@0: JSObject *temp = obj; michael@0: michael@0: if (IsObjectAboutToBeFinalized(&obj)) { michael@0: e.removeFront(); michael@0: } else { michael@0: SavedFrame *frame = &obj->as(); michael@0: bool parentMoved = frame->parentMoved(); michael@0: michael@0: if (parentMoved) { michael@0: frame->updatePrivateParent(); michael@0: } michael@0: michael@0: if (obj != temp || parentMoved) { michael@0: Rooted parent(rt, frame->getParent()); michael@0: e.rekeyFront(SavedFrame::Lookup(frame->getSource(), michael@0: frame->getLine(), michael@0: frame->getColumn(), michael@0: frame->getFunctionDisplayName(), michael@0: parent, michael@0: frame->getPrincipals()), michael@0: frame); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (savedFrameProto && IsObjectAboutToBeFinalized(&savedFrameProto)) { michael@0: savedFrameProto = nullptr; michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: SavedStacks::count() michael@0: { michael@0: JS_ASSERT(initialized()); michael@0: return frames.count(); michael@0: } michael@0: michael@0: void michael@0: SavedStacks::clear() michael@0: { michael@0: frames.clear(); michael@0: } michael@0: michael@0: size_t michael@0: SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: return frames.sizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: bool michael@0: SavedStacks::insertFrames(JSContext *cx, ScriptFrameIter &iter, MutableHandle frame) michael@0: { michael@0: if (iter.done()) { michael@0: frame.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: ScriptFrameIter thisFrame(iter); michael@0: Rooted parentFrame(cx); michael@0: if (!insertFrames(cx, ++iter, &parentFrame)) michael@0: return false; michael@0: michael@0: RootedScript script(cx, thisFrame.script()); michael@0: RootedFunction callee(cx, thisFrame.maybeCallee()); michael@0: const char *filename = script->filename(); michael@0: RootedAtom source(cx, Atomize(cx, filename, strlen(filename))); michael@0: if (!source) michael@0: return false; michael@0: uint32_t column; michael@0: uint32_t line = PCToLineNumber(script, thisFrame.pc(), &column); michael@0: michael@0: SavedFrame::Lookup lookup(source, michael@0: line, michael@0: column, michael@0: callee ? callee->displayAtom() : nullptr, michael@0: parentFrame, michael@0: thisFrame.compartment()->principals); michael@0: michael@0: frame.set(getOrCreateSavedFrame(cx, lookup)); michael@0: return frame.address() != nullptr; michael@0: } michael@0: michael@0: SavedFrame * michael@0: SavedStacks::getOrCreateSavedFrame(JSContext *cx, SavedFrame::Lookup &lookup) michael@0: { michael@0: SavedFrame::Set::AddPtr p = frames.lookupForAdd(lookup); michael@0: if (p) michael@0: return *p; michael@0: michael@0: Rooted frame(cx, createFrameFromLookup(cx, lookup)); michael@0: if (!frame) michael@0: return nullptr; michael@0: michael@0: if (!frames.relookupOrAdd(p, lookup, frame)) michael@0: return nullptr; michael@0: michael@0: return frame; michael@0: } michael@0: michael@0: JSObject * michael@0: SavedStacks::getOrCreateSavedFramePrototype(JSContext *cx) michael@0: { michael@0: if (savedFrameProto) michael@0: return savedFrameProto; michael@0: michael@0: Rooted global(cx, cx->compartment()->maybeGlobal()); michael@0: if (!global) michael@0: return nullptr; michael@0: michael@0: savedFrameProto = js_InitClass(cx, global, global->getOrCreateObjectPrototype(cx), michael@0: &SavedFrame::class_, SavedFrame::construct, 0, michael@0: SavedFrame::properties, SavedFrame::methods, nullptr, nullptr); michael@0: // The only object with the SavedFrame::class_ that doesn't have a source michael@0: // should be the prototype. michael@0: savedFrameProto->setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue()); michael@0: return savedFrameProto; michael@0: } michael@0: michael@0: SavedFrame * michael@0: SavedStacks::createFrameFromLookup(JSContext *cx, SavedFrame::Lookup &lookup) michael@0: { michael@0: RootedObject proto(cx, getOrCreateSavedFramePrototype(cx)); michael@0: if (!proto) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(proto->compartment() == cx->compartment()); michael@0: michael@0: RootedObject global(cx, cx->compartment()->maybeGlobal()); michael@0: if (!global) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(global->compartment() == cx->compartment()); michael@0: michael@0: RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto, global)); michael@0: if (!frameObj) michael@0: return nullptr; michael@0: michael@0: SavedFrame &f = frameObj->as(); michael@0: f.initFromLookup(lookup); michael@0: michael@0: return &f; michael@0: } michael@0: michael@0: } /* namespace js */