https://github.com/mozilla/gecko-dev
Raw File
Tip revision: 68bba3abd53ee78d9996e00a47b9d024604d30bf authored by Ryan VanderMeulen on 29 July 2015, 14:12:35 UTC
Added tag B2G_2_0_END for changeset 2e6f1d4deff9 on a CLOSED TREE
Tip revision: 68bba3a
SavedStacks.cpp
/* -*- 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 "jsapi.h"
#include "jscompartment.h"
#include "jsfriendapi.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("constructor", SavedFrame::construct, 0, 0),
    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;
    }

    // Don't report the over-recursion error because if we are blowing the stack
    // here, we already blew the stack in JS, reported it, and we are creating
    // the saved stack for the over-recursion error object. We do this check
    // here, rather than inside saveCurrentStack, because in some cases we will
    // pass the check there, despite later failing the check here (for example,
    // in js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js).
    JS_CHECK_RECURSION_DONT_REPORT(cx, return false);

    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();
    if (!filename)
        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.get() != 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;

    RootedObject proto(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_,
                                                   global->getOrCreateObjectPrototype(cx),
                                                   global));
    if (!proto
        || !JS_DefineProperties(cx, proto, SavedFrame::properties)
        || !JS_DefineFunctions(cx, proto, SavedFrame::methods))
        return nullptr;

    savedFrameProto = proto;
    // 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;
}

bool
SavedStacksMetadataCallback(JSContext* cx, JSObject** pmetadata)
{
    Rooted<SavedFrame*> frame(cx);
    if (!cx->compartment()->savedStacks().saveCurrentStack(cx, &frame))
        return false;
    *pmetadata = frame;
    return true;
}

} /* namespace js */
back to top