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