513 lines
15 KiB
C++
513 lines
15 KiB
C++
/* -*- 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<SavedFrame>().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<SavedFrame>() : nullptr;
|
|
}
|
|
|
|
JSPrincipals *
|
|
SavedFrame::getPrincipals()
|
|
{
|
|
const Value &v = getReservedSlot(JSSLOT_PRINCIPALS);
|
|
if (v.isUndefined())
|
|
return nullptr;
|
|
return static_cast<JSPrincipals *>(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<JSObject *>(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<SavedFrame>()) {
|
|
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<SavedFrame>() 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<SavedFrame>();
|
|
}
|
|
|
|
// 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<SavedFrame *> frame (will be non-null)
|
|
#define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \
|
|
CallArgs args = CallArgsFromVp(argc, vp); \
|
|
Rooted<SavedFrame *> 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<SavedFrame*> 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<JSObject *>(e.front());
|
|
JSObject *temp = obj;
|
|
|
|
if (IsObjectAboutToBeFinalized(&obj)) {
|
|
e.removeFront();
|
|
} else {
|
|
SavedFrame *frame = &obj->as<SavedFrame>();
|
|
bool parentMoved = frame->parentMoved();
|
|
|
|
if (parentMoved) {
|
|
frame->updatePrivateParent();
|
|
}
|
|
|
|
if (obj != temp || parentMoved) {
|
|
Rooted<SavedFrame*> 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<SavedFrame*> frame)
|
|
{
|
|
if (iter.done()) {
|
|
frame.set(nullptr);
|
|
return true;
|
|
}
|
|
|
|
ScriptFrameIter thisFrame(iter);
|
|
Rooted<SavedFrame*> 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<SavedFrame *> 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<GlobalObject *> 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<SavedFrame>();
|
|
f.initFromLookup(lookup);
|
|
|
|
return &f;
|
|
}
|
|
|
|
} /* namespace js */
|