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/Debugger-inl.h" michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jscompartment.h" michael@0: #include "jshashutil.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jswrapper.h" michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "gc/Marking.h" michael@0: #include "jit/BaselineJIT.h" michael@0: #include "js/Vector.h" michael@0: #include "vm/ArgumentsObject.h" michael@0: #include "vm/DebuggerMemory.h" michael@0: #include "vm/WrapperObject.h" michael@0: #include "jsgcinlines.h" michael@0: #include "jsobjinlines.h" michael@0: #include "jsopcodeinlines.h" michael@0: #include "jsscriptinlines.h" michael@0: #include "vm/ObjectImpl-inl.h" michael@0: #include "vm/Stack-inl.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using js::frontend::IsIdentifier; michael@0: using mozilla::ArrayLength; michael@0: using mozilla::Maybe; michael@0: michael@0: michael@0: /*** Forward declarations ************************************************************************/ michael@0: michael@0: extern const Class DebuggerFrame_class; michael@0: michael@0: enum { michael@0: JSSLOT_DEBUGFRAME_OWNER, michael@0: JSSLOT_DEBUGFRAME_ARGUMENTS, michael@0: JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, michael@0: JSSLOT_DEBUGFRAME_ONPOP_HANDLER, michael@0: JSSLOT_DEBUGFRAME_COUNT michael@0: }; michael@0: michael@0: extern const Class DebuggerArguments_class; michael@0: michael@0: enum { michael@0: JSSLOT_DEBUGARGUMENTS_FRAME, michael@0: JSSLOT_DEBUGARGUMENTS_COUNT michael@0: }; michael@0: michael@0: extern const Class DebuggerEnv_class; michael@0: michael@0: enum { michael@0: JSSLOT_DEBUGENV_OWNER, michael@0: JSSLOT_DEBUGENV_COUNT michael@0: }; michael@0: michael@0: extern const Class DebuggerObject_class; michael@0: michael@0: enum { michael@0: JSSLOT_DEBUGOBJECT_OWNER, michael@0: JSSLOT_DEBUGOBJECT_COUNT michael@0: }; michael@0: michael@0: extern const Class DebuggerScript_class; michael@0: michael@0: enum { michael@0: JSSLOT_DEBUGSCRIPT_OWNER, michael@0: JSSLOT_DEBUGSCRIPT_COUNT michael@0: }; michael@0: michael@0: extern const Class DebuggerSource_class; michael@0: michael@0: enum { michael@0: JSSLOT_DEBUGSOURCE_OWNER, michael@0: JSSLOT_DEBUGSOURCE_COUNT michael@0: }; michael@0: michael@0: michael@0: /*** Utils ***************************************************************************************/ michael@0: michael@0: static bool michael@0: ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required) michael@0: { michael@0: JS_ASSERT(required > 0); michael@0: JS_ASSERT(required <= 10); michael@0: char s[2]; michael@0: s[0] = '0' + (required - 1); michael@0: s[1] = '\0'; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: name, s, required == 2 ? "" : "s"); michael@0: return false; michael@0: } michael@0: michael@0: static inline bool michael@0: EnsureFunctionHasScript(JSContext *cx, HandleFunction fun) michael@0: { michael@0: if (fun->isInterpretedLazy()) { michael@0: AutoCompartment ac(cx, fun); michael@0: return !!fun->getOrCreateScript(cx); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static inline JSScript * michael@0: GetOrCreateFunctionScript(JSContext *cx, HandleFunction fun) michael@0: { michael@0: MOZ_ASSERT(fun->isInterpreted()); michael@0: if (!EnsureFunctionHasScript(cx, fun)) michael@0: return nullptr; michael@0: return fun->nonLazyScript(); michael@0: } michael@0: michael@0: #define REQUIRE_ARGC(name, n) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (argc < (n)) \ michael@0: return ReportMoreArgsNeeded(cx, name, n); \ michael@0: JS_END_MACRO michael@0: michael@0: static bool michael@0: ReportObjectRequired(JSContext *cx) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: ValueToIdentifier(JSContext *cx, HandleValue v, MutableHandleId id) michael@0: { michael@0: if (!ValueToId(cx, v, id)) michael@0: return false; michael@0: if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) { michael@0: RootedValue val(cx, v); michael@0: js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, michael@0: JSDVG_SEARCH_STACK, val, js::NullPtr(), "not an identifier", michael@0: nullptr); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * A range of all the Debugger.Frame objects for a particular AbstractFramePtr. michael@0: * michael@0: * FIXME This checks only current debuggers, so it relies on a hack in michael@0: * Debugger::removeDebuggeeGlobal to make sure only current debuggers michael@0: * have Frame objects with .live === true. michael@0: */ michael@0: class Debugger::FrameRange michael@0: { michael@0: AbstractFramePtr frame; michael@0: michael@0: /* The debuggers in |fp|'s compartment, or nullptr if there are none. */ michael@0: GlobalObject::DebuggerVector *debuggers; michael@0: michael@0: /* michael@0: * The index of the front Debugger.Frame's debugger in debuggers. michael@0: * nextDebugger < debuggerCount if and only if the range is not empty. michael@0: */ michael@0: size_t debuggerCount, nextDebugger; michael@0: michael@0: /* michael@0: * If the range is not empty, this is front Debugger.Frame's entry in its michael@0: * debugger's frame table. michael@0: */ michael@0: FrameMap::Ptr entry; michael@0: michael@0: public: michael@0: /* michael@0: * Return a range containing all Debugger.Frame instances referring to michael@0: * |fp|. |global| is |fp|'s global object; if nullptr or omitted, we michael@0: * compute it ourselves from |fp|. michael@0: * michael@0: * We keep an index into the compartment's debugger list, and a michael@0: * FrameMap::Ptr into the current debugger's frame map. Thus, if the set of michael@0: * debuggers in |fp|'s compartment changes, this range becomes invalid. michael@0: * Similarly, if stack frames are added to or removed from frontDebugger(), michael@0: * then the range's front is invalid until popFront is called. michael@0: */ michael@0: FrameRange(AbstractFramePtr frame, GlobalObject *global = nullptr) michael@0: : frame(frame) michael@0: { michael@0: nextDebugger = 0; michael@0: michael@0: /* Find our global, if we were not given one. */ michael@0: if (!global) michael@0: global = &frame.script()->global(); michael@0: michael@0: /* The frame and global must match. */ michael@0: JS_ASSERT(&frame.script()->global() == global); michael@0: michael@0: /* Find the list of debuggers we'll iterate over. There may be none. */ michael@0: debuggers = global->getDebuggers(); michael@0: if (debuggers) { michael@0: debuggerCount = debuggers->length(); michael@0: findNext(); michael@0: } else { michael@0: debuggerCount = 0; michael@0: } michael@0: } michael@0: michael@0: bool empty() const { michael@0: return nextDebugger >= debuggerCount; michael@0: } michael@0: michael@0: JSObject *frontFrame() const { michael@0: JS_ASSERT(!empty()); michael@0: return entry->value(); michael@0: } michael@0: michael@0: Debugger *frontDebugger() const { michael@0: JS_ASSERT(!empty()); michael@0: return (*debuggers)[nextDebugger]; michael@0: } michael@0: michael@0: /* michael@0: * Delete the front frame from its Debugger's frame map. After this call, michael@0: * the range's front is invalid until popFront is called. michael@0: */ michael@0: void removeFrontFrame() const { michael@0: JS_ASSERT(!empty()); michael@0: frontDebugger()->frames.remove(entry); michael@0: } michael@0: michael@0: void popFront() { michael@0: JS_ASSERT(!empty()); michael@0: nextDebugger++; michael@0: findNext(); michael@0: } michael@0: michael@0: private: michael@0: /* michael@0: * Either make this range refer to the first appropriate Debugger.Frame at michael@0: * or after nextDebugger, or make it empty. michael@0: */ michael@0: void findNext() { michael@0: while (!empty()) { michael@0: Debugger *dbg = (*debuggers)[nextDebugger]; michael@0: entry = dbg->frames.lookup(frame); michael@0: if (entry) michael@0: break; michael@0: nextDebugger++; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /*** Breakpoints *********************************************************************************/ michael@0: michael@0: BreakpointSite::BreakpointSite(JSScript *script, jsbytecode *pc) michael@0: : script(script), pc(pc), enabledCount(0), michael@0: trapHandler(nullptr), trapClosure(UndefinedValue()) michael@0: { michael@0: JS_ASSERT(!script->hasBreakpointsAt(pc)); michael@0: JS_INIT_CLIST(&breakpoints); michael@0: } michael@0: michael@0: void michael@0: BreakpointSite::recompile(FreeOp *fop) michael@0: { michael@0: #ifdef JS_ION michael@0: if (script->hasBaselineScript()) michael@0: script->baselineScript()->toggleDebugTraps(script, pc); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: BreakpointSite::inc(FreeOp *fop) michael@0: { michael@0: enabledCount++; michael@0: if (enabledCount == 1 && !trapHandler) michael@0: recompile(fop); michael@0: } michael@0: michael@0: void michael@0: BreakpointSite::dec(FreeOp *fop) michael@0: { michael@0: JS_ASSERT(enabledCount > 0); michael@0: enabledCount--; michael@0: if (enabledCount == 0 && !trapHandler) michael@0: recompile(fop); michael@0: } michael@0: michael@0: void michael@0: BreakpointSite::setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure) michael@0: { michael@0: trapHandler = handler; michael@0: trapClosure = closure; michael@0: michael@0: if (enabledCount == 0) michael@0: recompile(fop); michael@0: } michael@0: michael@0: void michael@0: BreakpointSite::clearTrap(FreeOp *fop, JSTrapHandler *handlerp, Value *closurep) michael@0: { michael@0: if (handlerp) michael@0: *handlerp = trapHandler; michael@0: if (closurep) michael@0: *closurep = trapClosure; michael@0: michael@0: trapHandler = nullptr; michael@0: trapClosure = UndefinedValue(); michael@0: if (enabledCount == 0) { michael@0: if (!fop->runtime()->isHeapBusy()) { michael@0: /* If the GC is running then the script is being destroyed. */ michael@0: recompile(fop); michael@0: } michael@0: destroyIfEmpty(fop); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BreakpointSite::destroyIfEmpty(FreeOp *fop) michael@0: { michael@0: if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler) michael@0: script->destroyBreakpointSite(fop, pc); michael@0: } michael@0: michael@0: Breakpoint * michael@0: BreakpointSite::firstBreakpoint() const michael@0: { michael@0: if (JS_CLIST_IS_EMPTY(&breakpoints)) michael@0: return nullptr; michael@0: return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints)); michael@0: } michael@0: michael@0: bool michael@0: BreakpointSite::hasBreakpoint(Breakpoint *bp) michael@0: { michael@0: for (Breakpoint *p = firstBreakpoint(); p; p = p->nextInSite()) michael@0: if (p == bp) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: Breakpoint::Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler) michael@0: : debugger(debugger), site(site), handler(handler) michael@0: { michael@0: JS_ASSERT(handler->compartment() == debugger->object->compartment()); michael@0: JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints); michael@0: JS_APPEND_LINK(&siteLinks, &site->breakpoints); michael@0: } michael@0: michael@0: Breakpoint * michael@0: Breakpoint::fromDebuggerLinks(JSCList *links) michael@0: { michael@0: return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, debuggerLinks)); michael@0: } michael@0: michael@0: Breakpoint * michael@0: Breakpoint::fromSiteLinks(JSCList *links) michael@0: { michael@0: return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, siteLinks)); michael@0: } michael@0: michael@0: void michael@0: Breakpoint::destroy(FreeOp *fop) michael@0: { michael@0: if (debugger->enabled) michael@0: site->dec(fop); michael@0: JS_REMOVE_LINK(&debuggerLinks); michael@0: JS_REMOVE_LINK(&siteLinks); michael@0: site->destroyIfEmpty(fop); michael@0: fop->delete_(this); michael@0: } michael@0: michael@0: Breakpoint * michael@0: Breakpoint::nextInDebugger() michael@0: { michael@0: JSCList *link = JS_NEXT_LINK(&debuggerLinks); michael@0: return (link == &debugger->breakpoints) ? nullptr : fromDebuggerLinks(link); michael@0: } michael@0: michael@0: Breakpoint * michael@0: Breakpoint::nextInSite() michael@0: { michael@0: JSCList *link = JS_NEXT_LINK(&siteLinks); michael@0: return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link); michael@0: } michael@0: michael@0: /*** Debugger hook dispatch **********************************************************************/ michael@0: michael@0: Debugger::Debugger(JSContext *cx, JSObject *dbg) michael@0: : object(dbg), uncaughtExceptionHook(nullptr), enabled(true), michael@0: frames(cx->runtime()), scripts(cx), sources(cx), objects(cx), environments(cx) michael@0: { michael@0: assertSameCompartment(cx, dbg); michael@0: michael@0: cx->runtime()->debuggerList.insertBack(this); michael@0: JS_INIT_CLIST(&breakpoints); michael@0: JS_INIT_CLIST(&onNewGlobalObjectWatchersLink); michael@0: } michael@0: michael@0: Debugger::~Debugger() michael@0: { michael@0: JS_ASSERT_IF(debuggees.initialized(), debuggees.empty()); michael@0: michael@0: /* michael@0: * Since the inactive state for this link is a singleton cycle, it's always michael@0: * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not. michael@0: * michael@0: * We don't have to worry about locking here since Debugger is not michael@0: * background finalized. michael@0: */ michael@0: JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink); michael@0: } michael@0: michael@0: bool michael@0: Debugger::init(JSContext *cx) michael@0: { michael@0: bool ok = debuggees.init() && michael@0: frames.init() && michael@0: scripts.init() && michael@0: sources.init() && michael@0: objects.init() && michael@0: environments.init(); michael@0: if (!ok) michael@0: js_ReportOutOfMemory(cx); michael@0: return ok; michael@0: } michael@0: michael@0: Debugger * michael@0: Debugger::fromJSObject(JSObject *obj) michael@0: { michael@0: JS_ASSERT(js::GetObjectClass(obj) == &jsclass); michael@0: return (Debugger *) obj->getPrivate(); michael@0: } michael@0: michael@0: JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER)); michael@0: JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSOURCE_OWNER)); michael@0: JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER)); michael@0: JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGENV_OWNER)); michael@0: michael@0: Debugger * michael@0: Debugger::fromChildJSObject(JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->getClass() == &DebuggerFrame_class || michael@0: obj->getClass() == &DebuggerScript_class || michael@0: obj->getClass() == &DebuggerSource_class || michael@0: obj->getClass() == &DebuggerObject_class || michael@0: obj->getClass() == &DebuggerEnv_class); michael@0: JSObject *dbgobj = &obj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject(); michael@0: return fromJSObject(dbgobj); michael@0: } michael@0: michael@0: bool michael@0: Debugger::getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame, michael@0: const ScriptFrameIter *maybeIter, MutableHandleValue vp) michael@0: { michael@0: MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == frame); michael@0: michael@0: FrameMap::AddPtr p = frames.lookupForAdd(frame); michael@0: if (!p) { michael@0: /* Create and populate the Debugger.Frame object. */ michael@0: JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject(); michael@0: JSObject *frameobj = michael@0: NewObjectWithGivenProto(cx, &DebuggerFrame_class, proto, nullptr); michael@0: if (!frameobj) michael@0: return false; michael@0: michael@0: // Eagerly copy ScriptFrameIter data if we've already walked the michael@0: // stack. michael@0: if (maybeIter) { michael@0: AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr(); michael@0: if (!data) michael@0: return false; michael@0: frameobj->setPrivate(data.raw()); michael@0: } else { michael@0: frameobj->setPrivate(frame.raw()); michael@0: } michael@0: michael@0: frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object)); michael@0: michael@0: if (!frames.add(p, frame, frameobj)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: vp.setObject(*p->value()); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: Debugger::getHook(Hook hook) const michael@0: { michael@0: JS_ASSERT(hook >= 0 && hook < HookCount); michael@0: const Value &v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook); michael@0: return v.isUndefined() ? nullptr : &v.toObject(); michael@0: } michael@0: michael@0: bool michael@0: Debugger::hasAnyLiveHooks() const michael@0: { michael@0: if (!enabled) michael@0: return false; michael@0: michael@0: if (getHook(OnDebuggerStatement) || michael@0: getHook(OnExceptionUnwind) || michael@0: getHook(OnNewScript) || michael@0: getHook(OnEnterFrame)) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: /* If any breakpoints are in live scripts, return true. */ michael@0: for (Breakpoint *bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) { michael@0: if (IsScriptMarked(&bp->site->script)) michael@0: return true; michael@0: } michael@0: michael@0: for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) { michael@0: JSObject *frameObj = r.front().value(); michael@0: if (!frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() || michael@0: !frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) michael@0: { michael@0: /* Build the list of recipients. */ michael@0: AutoValueVector triggered(cx); michael@0: Handle global = cx->global(); michael@0: michael@0: if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) { michael@0: for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { michael@0: Debugger *dbg = *p; michael@0: if (dbg->observesFrame(frame) && dbg->observesEnterFrame() && michael@0: !triggered.append(ObjectValue(*dbg->toJSObject()))) michael@0: { michael@0: return JSTRAP_ERROR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Deliver the event, checking again as in dispatchHook. */ michael@0: for (Value *p = triggered.begin(); p != triggered.end(); p++) { michael@0: Debugger *dbg = Debugger::fromJSObject(&p->toObject()); michael@0: if (dbg->debuggees.has(global) && dbg->observesEnterFrame()) { michael@0: JSTrapStatus status = dbg->fireEnterFrame(cx, frame, vp); michael@0: if (status != JSTRAP_CONTINUE) michael@0: return status; michael@0: } michael@0: } michael@0: michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: static void michael@0: DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame, michael@0: JSObject *frameobj); michael@0: michael@0: static void michael@0: DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj); michael@0: michael@0: /* michael@0: * Handle leaving a frame with debuggers watching. |frameOk| indicates whether michael@0: * the frame is exiting normally or abruptly. Set |cx|'s exception and/or michael@0: * |cx->fp()|'s return value, and return a new success value. michael@0: */ michael@0: bool michael@0: Debugger::slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool frameOk) michael@0: { michael@0: Handle global = cx->global(); michael@0: michael@0: /* Save the frame's completion value. */ michael@0: JSTrapStatus status; michael@0: RootedValue value(cx); michael@0: Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value); michael@0: michael@0: /* Build a list of the recipients. */ michael@0: AutoObjectVector frames(cx); michael@0: for (FrameRange r(frame, global); !r.empty(); r.popFront()) { michael@0: if (!frames.append(r.frontFrame())) { michael@0: cx->clearPendingException(); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* For each Debugger.Frame, fire its onPop handler, if any. */ michael@0: for (JSObject **p = frames.begin(); p != frames.end(); p++) { michael@0: RootedObject frameobj(cx, *p); michael@0: Debugger *dbg = Debugger::fromChildJSObject(frameobj); michael@0: michael@0: if (dbg->enabled && michael@0: !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) { michael@0: RootedValue handler(cx, frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER)); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, dbg->object); michael@0: michael@0: RootedValue completion(cx); michael@0: if (!dbg->newCompletionValue(cx, status, value, &completion)) { michael@0: status = dbg->handleUncaughtException(ac, false); michael@0: break; michael@0: } michael@0: michael@0: /* Call the onPop handler. */ michael@0: RootedValue rval(cx); michael@0: bool hookOk = Invoke(cx, ObjectValue(*frameobj), handler, 1, completion.address(), michael@0: &rval); michael@0: RootedValue nextValue(cx); michael@0: JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval, &nextValue); michael@0: michael@0: /* michael@0: * At this point, we are back in the debuggee compartment, and any error has michael@0: * been wrapped up as a completion value. michael@0: */ michael@0: JS_ASSERT(cx->compartment() == global->compartment()); michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: michael@0: /* JSTRAP_CONTINUE means "make no change". */ michael@0: if (nextStatus != JSTRAP_CONTINUE) { michael@0: status = nextStatus; michael@0: value = nextValue; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Clean up all Debugger.Frame instances. Use a fresh FrameRange, as one michael@0: * debugger's onPop handler could have caused another debugger to create its michael@0: * own Debugger.Frame instance. michael@0: */ michael@0: for (FrameRange r(frame, global); !r.empty(); r.popFront()) { michael@0: RootedObject frameobj(cx, r.frontFrame()); michael@0: Debugger *dbg = r.frontDebugger(); michael@0: JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj)); michael@0: michael@0: FreeOp *fop = cx->runtime()->defaultFreeOp(); michael@0: DebuggerFrame_freeScriptFrameIterData(fop, frameobj); michael@0: DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj); michael@0: michael@0: dbg->frames.remove(frame); michael@0: } michael@0: michael@0: /* michael@0: * If this is an eval frame, then from the debugger's perspective the michael@0: * script is about to be destroyed. Remove any breakpoints in it. michael@0: */ michael@0: if (frame.isEvalFrame()) { michael@0: RootedScript script(cx, frame.script()); michael@0: script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), nullptr, nullptr); michael@0: } michael@0: michael@0: /* Establish (status, value) as our resumption value. */ michael@0: switch (status) { michael@0: case JSTRAP_RETURN: michael@0: frame.setReturnValue(value); michael@0: return true; michael@0: michael@0: case JSTRAP_THROW: michael@0: cx->setPendingException(value); michael@0: return false; michael@0: michael@0: case JSTRAP_ERROR: michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: return false; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad final trap status"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: Debugger::wrapEnvironment(JSContext *cx, Handle env, MutableHandleValue rval) michael@0: { michael@0: if (!env) { michael@0: rval.setNull(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * DebuggerEnv should only wrap a debug scope chain obtained (transitively) michael@0: * from GetDebugScopeFor(Frame|Function). michael@0: */ michael@0: JS_ASSERT(!env->is()); michael@0: michael@0: JSObject *envobj; michael@0: DependentAddPtr p(cx, environments, env); michael@0: if (p) { michael@0: envobj = p->value(); michael@0: } else { michael@0: /* Create a new Debugger.Environment for env. */ michael@0: JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject(); michael@0: envobj = NewObjectWithGivenProto(cx, &DebuggerEnv_class, proto, nullptr, TenuredObject); michael@0: if (!envobj) michael@0: return false; michael@0: envobj->setPrivateGCThing(env); michael@0: envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object)); michael@0: if (!p.add(cx, environments, env, envobj)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env); michael@0: if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) { michael@0: environments.remove(env); michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: rval.setObject(*envobj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: assertSameCompartment(cx, object.get()); michael@0: michael@0: if (vp.isObject()) { michael@0: RootedObject obj(cx, &vp.toObject()); michael@0: michael@0: if (obj->is()) { michael@0: RootedFunction fun(cx, &obj->as()); michael@0: if (!EnsureFunctionHasScript(cx, fun)) michael@0: return false; michael@0: } michael@0: michael@0: DependentAddPtr p(cx, objects, obj); michael@0: if (p) { michael@0: vp.setObject(*p->value()); michael@0: } else { michael@0: /* Create a new Debugger.Object for obj. */ michael@0: JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject(); michael@0: JSObject *dobj = michael@0: NewObjectWithGivenProto(cx, &DebuggerObject_class, proto, nullptr, TenuredObject); michael@0: if (!dobj) michael@0: return false; michael@0: dobj->setPrivateGCThing(obj); michael@0: dobj->setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*object)); michael@0: michael@0: if (!p.add(cx, objects, obj, dobj)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: if (obj->compartment() != object->compartment()) { michael@0: CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj); michael@0: if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) { michael@0: objects.remove(obj); michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: vp.setObject(*dobj); michael@0: } michael@0: } else if (vp.isMagic()) { michael@0: RootedObject optObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!optObj) michael@0: return false; michael@0: michael@0: // We handle two sentinel values: missing arguments (overloading michael@0: // JS_OPTIMIZED_ARGUMENTS) and optimized out slots (JS_OPTIMIZED_OUT). michael@0: // Other magic values should not have escaped. michael@0: PropertyName *name; michael@0: if (vp.whyMagic() == JS_OPTIMIZED_ARGUMENTS) { michael@0: name = cx->names().missingArguments; michael@0: } else { michael@0: MOZ_ASSERT(vp.whyMagic() == JS_OPTIMIZED_OUT); michael@0: name = cx->names().optimizedOut; michael@0: } michael@0: michael@0: RootedValue trueVal(cx, BooleanValue(true)); michael@0: if (!JSObject::defineProperty(cx, optObj, name, trueVal)) michael@0: return false; michael@0: michael@0: vp.setObject(*optObj); michael@0: } else if (!cx->compartment()->wrap(cx, vp)) { michael@0: vp.setUndefined(); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::unwrapDebuggeeValue(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: assertSameCompartment(cx, object.get(), vp); michael@0: if (vp.isObject()) { michael@0: JSObject *dobj = &vp.toObject(); michael@0: if (dobj->getClass() != &DebuggerObject_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, michael@0: "Debugger", "Debugger.Object", dobj->getClass()->name); michael@0: return false; michael@0: } michael@0: michael@0: Value owner = dobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER); michael@0: if (owner.isUndefined() || &owner.toObject() != object) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: owner.isUndefined() michael@0: ? JSMSG_DEBUG_OBJECT_PROTO michael@0: : JSMSG_DEBUG_OBJECT_WRONG_OWNER); michael@0: return false; michael@0: } michael@0: michael@0: vp.setObject(*static_cast(dobj->getPrivate())); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::handleUncaughtExceptionHelper(Maybe &ac, michael@0: MutableHandleValue *vp, bool callHook) michael@0: { michael@0: JSContext *cx = ac.ref().context()->asJSContext(); michael@0: if (cx->isExceptionPending()) { michael@0: if (callHook && uncaughtExceptionHook) { michael@0: RootedValue exc(cx); michael@0: if (!cx->getPendingException(&exc)) michael@0: return JSTRAP_ERROR; michael@0: cx->clearPendingException(); michael@0: RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook)); michael@0: RootedValue rv(cx); michael@0: if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv)) michael@0: return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: if (cx->isExceptionPending()) { michael@0: JS_ReportPendingException(cx); michael@0: cx->clearPendingException(); michael@0: } michael@0: } michael@0: ac.destroy(); michael@0: return JSTRAP_ERROR; michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::handleUncaughtException(Maybe &ac, MutableHandleValue vp, bool callHook) michael@0: { michael@0: return handleUncaughtExceptionHelper(ac, &vp, callHook); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::handleUncaughtException(Maybe &ac, bool callHook) michael@0: { michael@0: return handleUncaughtExceptionHelper(ac, nullptr, callHook); michael@0: } michael@0: michael@0: void michael@0: Debugger::resultToCompletion(JSContext *cx, bool ok, const Value &rv, michael@0: JSTrapStatus *status, MutableHandleValue value) michael@0: { michael@0: JS_ASSERT_IF(ok, !cx->isExceptionPending()); michael@0: michael@0: if (ok) { michael@0: *status = JSTRAP_RETURN; michael@0: value.set(rv); michael@0: } else if (cx->isExceptionPending()) { michael@0: *status = JSTRAP_THROW; michael@0: if (!cx->getPendingException(value)) michael@0: *status = JSTRAP_ERROR; michael@0: cx->clearPendingException(); michael@0: } else { michael@0: *status = JSTRAP_ERROR; michael@0: value.setUndefined(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: Debugger::newCompletionValue(JSContext *cx, JSTrapStatus status, Value value_, michael@0: MutableHandleValue result) michael@0: { michael@0: /* michael@0: * We must be in the debugger's compartment, since that's where we want michael@0: * to construct the completion value. michael@0: */ michael@0: assertSameCompartment(cx, object.get()); michael@0: michael@0: RootedId key(cx); michael@0: RootedValue value(cx, value_); michael@0: michael@0: switch (status) { michael@0: case JSTRAP_RETURN: michael@0: key = NameToId(cx->names().return_); michael@0: break; michael@0: michael@0: case JSTRAP_THROW: michael@0: key = NameToId(cx->names().throw_); michael@0: break; michael@0: michael@0: case JSTRAP_ERROR: michael@0: result.setNull(); michael@0: return true; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad status passed to Debugger::newCompletionValue"); michael@0: } michael@0: michael@0: /* Common tail for JSTRAP_RETURN and JSTRAP_THROW. */ michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!obj || michael@0: !wrapDebuggeeValue(cx, &value) || michael@0: !DefineNativeProperty(cx, obj, key, value, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JSPROP_ENUMERATE)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: result.setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::receiveCompletionValue(Maybe &ac, bool ok, michael@0: HandleValue val, michael@0: MutableHandleValue vp) michael@0: { michael@0: JSContext *cx = ac.ref().context()->asJSContext(); michael@0: michael@0: JSTrapStatus status; michael@0: RootedValue value(cx); michael@0: resultToCompletion(cx, ok, val, &status, &value); michael@0: ac.destroy(); michael@0: return newCompletionValue(cx, status, value, vp); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::parseResumptionValue(Maybe &ac, bool ok, const Value &rv, MutableHandleValue vp, michael@0: bool callHook) michael@0: { michael@0: vp.setUndefined(); michael@0: if (!ok) michael@0: return handleUncaughtException(ac, vp, callHook); michael@0: if (rv.isUndefined()) { michael@0: ac.destroy(); michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: if (rv.isNull()) { michael@0: ac.destroy(); michael@0: return JSTRAP_ERROR; michael@0: } michael@0: michael@0: /* Check that rv is {return: val} or {throw: val}. */ michael@0: JSContext *cx = ac.ref().context()->asJSContext(); michael@0: Rooted obj(cx); michael@0: RootedShape shape(cx); michael@0: RootedId returnId(cx, NameToId(cx->names().return_)); michael@0: RootedId throwId(cx, NameToId(cx->names().throw_)); michael@0: bool okResumption = rv.isObject(); michael@0: if (okResumption) { michael@0: obj = &rv.toObject(); michael@0: okResumption = obj->is(); michael@0: } michael@0: if (okResumption) { michael@0: shape = obj->lastProperty(); michael@0: okResumption = shape->previous() && michael@0: !shape->previous()->previous() && michael@0: (shape->propid() == returnId || shape->propid() == throwId) && michael@0: shape->isDataDescriptor(); michael@0: } michael@0: if (!okResumption) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_RESUMPTION); michael@0: return handleUncaughtException(ac, vp, callHook); michael@0: } michael@0: michael@0: RootedValue v(cx, vp.get()); michael@0: if (!NativeGet(cx, obj, obj, shape, &v) || !unwrapDebuggeeValue(cx, &v)) michael@0: return handleUncaughtException(ac, &v, callHook); michael@0: michael@0: ac.destroy(); michael@0: if (!cx->compartment()->wrap(cx, &v)) { michael@0: vp.setUndefined(); michael@0: return JSTRAP_ERROR; michael@0: } michael@0: vp.set(v); michael@0: michael@0: return shape->propid() == returnId ? JSTRAP_RETURN : JSTRAP_THROW; michael@0: } michael@0: michael@0: static bool michael@0: CallMethodIfPresent(JSContext *cx, HandleObject obj, const char *name, int argc, Value *argv, michael@0: MutableHandleValue rval) michael@0: { michael@0: rval.setUndefined(); michael@0: JSAtom *atom = Atomize(cx, name, strlen(name)); michael@0: if (!atom) michael@0: return false; michael@0: michael@0: RootedId id(cx, AtomToId(atom)); michael@0: RootedValue fval(cx); michael@0: return JSObject::getGeneric(cx, obj, obj, id, &fval) && michael@0: (!js_IsCallable(fval) || Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval)); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::fireDebuggerStatement(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: RootedObject hook(cx, getHook(OnDebuggerStatement)); michael@0: JS_ASSERT(hook); michael@0: JS_ASSERT(hook->isCallable()); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, object); michael@0: michael@0: ScriptFrameIter iter(cx); michael@0: michael@0: RootedValue scriptFrame(cx); michael@0: if (!getScriptFrame(cx, iter, &scriptFrame)) michael@0: return handleUncaughtException(ac, false); michael@0: michael@0: RootedValue rv(cx); michael@0: bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv); michael@0: return parseResumptionValue(ac, ok, rv, vp); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::fireExceptionUnwind(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: RootedObject hook(cx, getHook(OnExceptionUnwind)); michael@0: JS_ASSERT(hook); michael@0: JS_ASSERT(hook->isCallable()); michael@0: michael@0: RootedValue exc(cx); michael@0: if (!cx->getPendingException(&exc)) michael@0: return JSTRAP_ERROR; michael@0: cx->clearPendingException(); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, object); michael@0: michael@0: JS::AutoValueArray<2> argv(cx); michael@0: argv[0].setUndefined(); michael@0: argv[1].set(exc); michael@0: michael@0: ScriptFrameIter iter(cx); michael@0: if (!getScriptFrame(cx, iter, argv[0]) || !wrapDebuggeeValue(cx, argv[1])) michael@0: return handleUncaughtException(ac, false); michael@0: michael@0: RootedValue rv(cx); michael@0: bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 2, argv.begin(), &rv); michael@0: JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp); michael@0: if (st == JSTRAP_CONTINUE) michael@0: cx->setPendingException(exc); michael@0: return st; michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::fireEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) michael@0: { michael@0: RootedObject hook(cx, getHook(OnEnterFrame)); michael@0: JS_ASSERT(hook); michael@0: JS_ASSERT(hook->isCallable()); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, object); michael@0: michael@0: RootedValue scriptFrame(cx); michael@0: if (!getScriptFrame(cx, frame, &scriptFrame)) michael@0: return handleUncaughtException(ac, false); michael@0: michael@0: RootedValue rv(cx); michael@0: bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv); michael@0: return parseResumptionValue(ac, ok, rv, vp); michael@0: } michael@0: michael@0: void michael@0: Debugger::fireNewScript(JSContext *cx, HandleScript script) michael@0: { michael@0: RootedObject hook(cx, getHook(OnNewScript)); michael@0: JS_ASSERT(hook); michael@0: JS_ASSERT(hook->isCallable()); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, object); michael@0: michael@0: JSObject *dsobj = wrapScript(cx, script); michael@0: if (!dsobj) { michael@0: handleUncaughtException(ac, false); michael@0: return; michael@0: } michael@0: michael@0: RootedValue scriptObject(cx, ObjectValue(*dsobj)); michael@0: RootedValue rv(cx); michael@0: if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptObject.address(), &rv)) michael@0: handleUncaughtException(ac, true); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which) michael@0: { michael@0: JS_ASSERT(which == OnDebuggerStatement || which == OnExceptionUnwind); michael@0: michael@0: /* michael@0: * Determine which debuggers will receive this event, and in what order. michael@0: * Make a copy of the list, since the original is mutable and we will be michael@0: * calling into arbitrary JS. michael@0: * michael@0: * Note: In the general case, 'triggered' contains references to objects in michael@0: * different compartments--every compartment *except* this one. michael@0: */ michael@0: AutoValueVector triggered(cx); michael@0: Handle global = cx->global(); michael@0: if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) { michael@0: for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { michael@0: Debugger *dbg = *p; michael@0: if (dbg->enabled && dbg->getHook(which)) { michael@0: if (!triggered.append(ObjectValue(*dbg->toJSObject()))) michael@0: return JSTRAP_ERROR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Deliver the event to each debugger, checking again to make sure it michael@0: * should still be delivered. michael@0: */ michael@0: for (Value *p = triggered.begin(); p != triggered.end(); p++) { michael@0: Debugger *dbg = Debugger::fromJSObject(&p->toObject()); michael@0: if (dbg->debuggees.has(global) && dbg->enabled && dbg->getHook(which)) { michael@0: JSTrapStatus st = (which == OnDebuggerStatement) michael@0: ? dbg->fireDebuggerStatement(cx, vp) michael@0: : dbg->fireExceptionUnwind(cx, vp); michael@0: if (st != JSTRAP_CONTINUE) michael@0: return st; michael@0: } michael@0: } michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: static bool michael@0: AddNewScriptRecipients(GlobalObject::DebuggerVector *src, HandleScript script, michael@0: AutoValueVector *dest) michael@0: { michael@0: bool wasEmpty = dest->length() == 0; michael@0: for (Debugger **p = src->begin(); p != src->end(); p++) { michael@0: Debugger *dbg = *p; michael@0: Value v = ObjectValue(*dbg->toJSObject()); michael@0: if (dbg->observesScript(script) && dbg->observesNewScript() && michael@0: (wasEmpty || Find(dest->begin(), dest->end(), v) == dest->end()) && michael@0: !dest->append(v)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal_) michael@0: { michael@0: Rooted compileAndGoGlobal(cx, compileAndGoGlobal_); michael@0: michael@0: JS_ASSERT(script->compileAndGo() == !!compileAndGoGlobal); michael@0: michael@0: /* michael@0: * Build the list of recipients. For compile-and-go scripts, this is the michael@0: * same as the generic Debugger::dispatchHook code, but non-compile-and-go michael@0: * scripts are not tied to particular globals. We deliver them to every michael@0: * debugger observing any global in the script's compartment. michael@0: */ michael@0: AutoValueVector triggered(cx); michael@0: if (script->compileAndGo()) { michael@0: if (GlobalObject::DebuggerVector *debuggers = compileAndGoGlobal->getDebuggers()) { michael@0: if (!AddNewScriptRecipients(debuggers, script, &triggered)) michael@0: return; michael@0: } michael@0: } else { michael@0: GlobalObjectSet &debuggees = script->compartment()->getDebuggees(); michael@0: for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) { michael@0: if (!AddNewScriptRecipients(r.front()->getDebuggers(), script, &triggered)) michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Deliver the event to each debugger, checking again as in michael@0: * Debugger::dispatchHook. michael@0: */ michael@0: for (Value *p = triggered.begin(); p != triggered.end(); p++) { michael@0: Debugger *dbg = Debugger::fromJSObject(&p->toObject()); michael@0: if ((!compileAndGoGlobal || dbg->debuggees.has(compileAndGoGlobal)) && michael@0: dbg->enabled && dbg->getHook(OnNewScript)) { michael@0: dbg->fireNewScript(cx, script); michael@0: } michael@0: } michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::onTrap(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: MOZ_ASSERT(cx->compartment()->debugMode()); michael@0: michael@0: ScriptFrameIter iter(cx); michael@0: RootedScript script(cx, iter.script()); michael@0: Rooted scriptGlobal(cx, &script->global()); michael@0: jsbytecode *pc = iter.pc(); michael@0: BreakpointSite *site = script->getBreakpointSite(pc); michael@0: JSOp op = JSOp(*pc); michael@0: michael@0: /* Build list of breakpoint handlers. */ michael@0: Vector triggered(cx); michael@0: for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) { michael@0: if (!triggered.append(bp)) michael@0: return JSTRAP_ERROR; michael@0: } michael@0: michael@0: for (Breakpoint **p = triggered.begin(); p != triggered.end(); p++) { michael@0: Breakpoint *bp = *p; michael@0: michael@0: /* Handlers can clear breakpoints. Check that bp still exists. */ michael@0: if (!site || !site->hasBreakpoint(bp)) michael@0: continue; michael@0: michael@0: michael@0: /* michael@0: * There are two reasons we have to check whether dbg is enabled and michael@0: * debugging scriptGlobal. michael@0: * michael@0: * One is just that one breakpoint handler can disable other Debuggers michael@0: * or remove debuggees. michael@0: * michael@0: * The other has to do with non-compile-and-go scripts, which have no michael@0: * specific global--until they are executed. Only now do we know which michael@0: * global the script is running against. michael@0: */ michael@0: Debugger *dbg = bp->debugger; michael@0: if (dbg->enabled && dbg->debuggees.lookup(scriptGlobal)) { michael@0: Maybe ac; michael@0: ac.construct(cx, dbg->object); michael@0: michael@0: RootedValue scriptFrame(cx); michael@0: if (!dbg->getScriptFrame(cx, iter, &scriptFrame)) michael@0: return dbg->handleUncaughtException(ac, false); michael@0: RootedValue rv(cx); michael@0: Rooted handler(cx, bp->handler); michael@0: bool ok = CallMethodIfPresent(cx, handler, "hit", 1, scriptFrame.address(), &rv); michael@0: JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true); michael@0: if (st != JSTRAP_CONTINUE) michael@0: return st; michael@0: michael@0: /* Calling JS code invalidates site. Reload it. */ michael@0: site = script->getBreakpointSite(pc); michael@0: } michael@0: } michael@0: michael@0: if (site && site->trapHandler) { michael@0: JSTrapStatus st = site->trapHandler(cx, script, pc, vp.address(), site->trapClosure); michael@0: if (st != JSTRAP_CONTINUE) michael@0: return st; michael@0: } michael@0: michael@0: /* By convention, return the true op to the interpreter in vp. */ michael@0: vp.setInt32(op); michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::onSingleStep(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: ScriptFrameIter iter(cx); michael@0: michael@0: /* michael@0: * We may be stepping over a JSOP_EXCEPTION, that pushes the context's michael@0: * pending exception for a 'catch' clause to handle. Don't let the michael@0: * onStep handlers mess with that (other than by returning a resumption michael@0: * value). michael@0: */ michael@0: RootedValue exception(cx, UndefinedValue()); michael@0: bool exceptionPending = cx->isExceptionPending(); michael@0: if (exceptionPending) { michael@0: if (!cx->getPendingException(&exception)) michael@0: return JSTRAP_ERROR; michael@0: cx->clearPendingException(); michael@0: } michael@0: michael@0: /* michael@0: * Build list of Debugger.Frame instances referring to this frame with michael@0: * onStep handlers. michael@0: */ michael@0: AutoObjectVector frames(cx); michael@0: for (FrameRange r(iter.abstractFramePtr()); !r.empty(); r.popFront()) { michael@0: JSObject *frame = r.frontFrame(); michael@0: if (!frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() && michael@0: !frames.append(frame)) michael@0: { michael@0: return JSTRAP_ERROR; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: /* michael@0: * Validate the single-step count on this frame's script, to ensure that michael@0: * we're not receiving traps we didn't ask for. Even when frames is michael@0: * non-empty (and thus we know this trap was requested), do the check michael@0: * anyway, to make sure the count has the correct non-zero value. michael@0: * michael@0: * The converse --- ensuring that we do receive traps when we should --- can michael@0: * be done with unit tests. michael@0: */ michael@0: { michael@0: uint32_t stepperCount = 0; michael@0: JSScript *trappingScript = iter.script(); michael@0: GlobalObject *global = cx->global(); michael@0: if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) { michael@0: for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { michael@0: Debugger *dbg = *p; michael@0: for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) { michael@0: AbstractFramePtr frame = r.front().key(); michael@0: JSObject *frameobj = r.front().value(); michael@0: if (frame.script() == trappingScript && michael@0: !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined()) michael@0: { michael@0: stepperCount++; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (trappingScript->compileAndGo()) michael@0: JS_ASSERT(stepperCount == trappingScript->stepModeCount()); michael@0: else michael@0: JS_ASSERT(stepperCount <= trappingScript->stepModeCount()); michael@0: } michael@0: #endif michael@0: michael@0: /* Preserve the debuggee's iterValue while handlers run. */ michael@0: class PreserveIterValue { michael@0: JSContext *cx; michael@0: RootedValue savedIterValue; michael@0: michael@0: public: michael@0: PreserveIterValue(JSContext *cx) : cx(cx), savedIterValue(cx, cx->iterValue) { michael@0: cx->iterValue.setMagic(JS_NO_ITER_VALUE); michael@0: } michael@0: ~PreserveIterValue() { michael@0: cx->iterValue = savedIterValue; michael@0: } michael@0: }; michael@0: PreserveIterValue piv(cx); michael@0: michael@0: /* Call all the onStep handlers we found. */ michael@0: for (JSObject **p = frames.begin(); p != frames.end(); p++) { michael@0: RootedObject frame(cx, *p); michael@0: Debugger *dbg = Debugger::fromChildJSObject(frame); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, dbg->object); michael@0: michael@0: const Value &handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER); michael@0: RootedValue rval(cx); michael@0: bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, nullptr, &rval); michael@0: JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp); michael@0: if (st != JSTRAP_CONTINUE) michael@0: return st; michael@0: } michael@0: michael@0: vp.setUndefined(); michael@0: if (exceptionPending) michael@0: cx->setPendingException(exception); michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::fireNewGlobalObject(JSContext *cx, Handle global, MutableHandleValue vp) michael@0: { michael@0: RootedObject hook(cx, getHook(OnNewGlobalObject)); michael@0: JS_ASSERT(hook); michael@0: JS_ASSERT(hook->isCallable()); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, object); michael@0: michael@0: RootedValue wrappedGlobal(cx, ObjectValue(*global)); michael@0: if (!wrapDebuggeeValue(cx, &wrappedGlobal)) michael@0: return handleUncaughtException(ac, false); michael@0: michael@0: RootedValue rv(cx); michael@0: michael@0: // onNewGlobalObject is infallible, and thus is only allowed to return michael@0: // undefined as a resumption value. If it returns anything else, we throw. michael@0: // And if that happens, or if the hook itself throws, we invoke the michael@0: // uncaughtExceptionHook so that we never leave an exception pending on the michael@0: // cx. This allows JS_NewGlobalObject to avoid handling failures from debugger michael@0: // hooks. michael@0: bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, wrappedGlobal.address(), &rv); michael@0: if (ok && !rv.isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED); michael@0: ok = false; michael@0: } michael@0: // NB: Even though we don't care about what goes into it, we have to pass vp michael@0: // to handleUncaughtException so that it parses resumption values from the michael@0: // uncaughtExceptionHook and tells the caller whether we should execute the michael@0: // rest of the onNewGlobalObject hooks or not. michael@0: JSTrapStatus status = ok ? JSTRAP_CONTINUE michael@0: : handleUncaughtException(ac, vp, true); michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: return status; michael@0: } michael@0: michael@0: void michael@0: Debugger::slowPathOnNewGlobalObject(JSContext *cx, Handle global) michael@0: { michael@0: JS_ASSERT(!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers)); michael@0: if (global->compartment()->options().invisibleToDebugger()) michael@0: return; michael@0: michael@0: /* michael@0: * Make a copy of the runtime's onNewGlobalObjectWatchers before running the michael@0: * handlers. Since one Debugger's handler can disable another's, the list michael@0: * can be mutated while we're walking it. michael@0: */ michael@0: AutoObjectVector watchers(cx); michael@0: for (JSCList *link = JS_LIST_HEAD(&cx->runtime()->onNewGlobalObjectWatchers); michael@0: link != &cx->runtime()->onNewGlobalObjectWatchers; michael@0: link = JS_NEXT_LINK(link)) { michael@0: Debugger *dbg = fromOnNewGlobalObjectWatchersLink(link); michael@0: JS_ASSERT(dbg->observesNewGlobalObject()); michael@0: if (!watchers.append(dbg->object)) michael@0: return; michael@0: } michael@0: michael@0: JSTrapStatus status = JSTRAP_CONTINUE; michael@0: RootedValue value(cx); michael@0: michael@0: for (size_t i = 0; i < watchers.length(); i++) { michael@0: Debugger *dbg = fromJSObject(watchers[i]); michael@0: michael@0: // We disallow resumption values from onNewGlobalObject hooks, because we michael@0: // want the debugger hooks for global object creation to be infallible. michael@0: // But if an onNewGlobalObject hook throws, and the uncaughtExceptionHook michael@0: // decides to raise an error, we want to at least avoid invoking the rest michael@0: // of the onNewGlobalObject handlers in the list (not for any super michael@0: // compelling reason, just because it seems like the right thing to do). michael@0: // So we ignore whatever comes out in |value|, but break out of the loop michael@0: // if a non-success trap status is returned. michael@0: if (dbg->observesNewGlobalObject()) { michael@0: status = dbg->fireNewGlobalObject(cx, global, &value); michael@0: if (status != JSTRAP_CONTINUE && status != JSTRAP_RETURN) michael@0: break; michael@0: } michael@0: } michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: } michael@0: michael@0: michael@0: /*** Debugger JSObjects **************************************************************************/ michael@0: michael@0: void michael@0: Debugger::markKeysInCompartment(JSTracer *trc) michael@0: { michael@0: /* michael@0: * WeakMap::Range is deliberately private, to discourage C++ code from michael@0: * enumerating WeakMap keys. However in this case we need access, so we michael@0: * make a base-class reference. Range is public in HashMap. michael@0: */ michael@0: objects.markKeys(trc); michael@0: environments.markKeys(trc); michael@0: scripts.markKeys(trc); michael@0: sources.markKeys(trc); michael@0: } michael@0: michael@0: /* michael@0: * Ordinarily, WeakMap keys and values are marked because at some point it was michael@0: * discovered that the WeakMap was live; that is, some object containing the michael@0: * WeakMap was marked during mark phase. michael@0: * michael@0: * However, during compartment GC, we have to do something about michael@0: * cross-compartment WeakMaps in non-GC'd compartments. If their keys and values michael@0: * might need to be marked, we have to do it manually. michael@0: * michael@0: * Each Debugger object keeps found cross-compartment WeakMaps: objects, scripts, michael@0: * script source objects, and environments. They have the nice property that all michael@0: * their values are in the same compartment as the Debugger object, so we only michael@0: * need to mark the keys. We must simply mark all keys that are in a compartment michael@0: * being GC'd. michael@0: * michael@0: * We must scan all Debugger objects regardless of whether they *currently* michael@0: * have any debuggees in a compartment being GC'd, because the WeakMap michael@0: * entries persist even when debuggees are removed. michael@0: * michael@0: * This happens during the initial mark phase, not iterative marking, because michael@0: * all the edges being reported here are strong references. michael@0: */ michael@0: void michael@0: Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *trc) michael@0: { michael@0: JSRuntime *rt = trc->runtime(); michael@0: michael@0: /* michael@0: * Mark all objects in comp that are referents of Debugger.Objects in other michael@0: * compartments. michael@0: */ michael@0: for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) { michael@0: if (!dbg->object->zone()->isCollecting()) michael@0: dbg->markKeysInCompartment(trc); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * This method has two tasks: michael@0: * 1. Mark Debugger objects that are unreachable except for debugger hooks that michael@0: * may yet be called. michael@0: * 2. Mark breakpoint handlers. michael@0: * michael@0: * This happens during the iterative part of the GC mark phase. This method michael@0: * returns true if it has to mark anything; GC calls it repeatedly until it michael@0: * returns false. michael@0: */ michael@0: bool michael@0: Debugger::markAllIteratively(GCMarker *trc) michael@0: { michael@0: bool markedAny = false; michael@0: michael@0: /* michael@0: * Find all Debugger objects in danger of GC. This code is a little michael@0: * convoluted since the easiest way to find them is via their debuggees. michael@0: */ michael@0: JSRuntime *rt = trc->runtime(); michael@0: for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { michael@0: GlobalObjectSet &debuggees = c->getDebuggees(); michael@0: for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { michael@0: GlobalObject *global = e.front(); michael@0: if (!IsObjectMarked(&global)) michael@0: continue; michael@0: else if (global != e.front()) michael@0: e.rekeyFront(global); michael@0: michael@0: /* michael@0: * Every debuggee has at least one debugger, so in this case michael@0: * getDebuggers can't return nullptr. michael@0: */ michael@0: const GlobalObject::DebuggerVector *debuggers = global->getDebuggers(); michael@0: JS_ASSERT(debuggers); michael@0: for (Debugger * const *p = debuggers->begin(); p != debuggers->end(); p++) { michael@0: Debugger *dbg = *p; michael@0: michael@0: /* michael@0: * dbg is a Debugger with at least one debuggee. Check three things: michael@0: * - dbg is actually in a compartment that is being marked michael@0: * - it isn't already marked michael@0: * - it actually has hooks that might be called michael@0: */ michael@0: HeapPtrObject &dbgobj = dbg->toJSObjectRef(); michael@0: if (!dbgobj->zone()->isGCMarking()) michael@0: continue; michael@0: michael@0: bool dbgMarked = IsObjectMarked(&dbgobj); michael@0: if (!dbgMarked && dbg->hasAnyLiveHooks()) { michael@0: /* michael@0: * obj could be reachable only via its live, enabled michael@0: * debugger hooks, which may yet be called. michael@0: */ michael@0: MarkObject(trc, &dbgobj, "enabled Debugger"); michael@0: markedAny = true; michael@0: dbgMarked = true; michael@0: } michael@0: michael@0: if (dbgMarked) { michael@0: /* Search for breakpoints to mark. */ michael@0: for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) { michael@0: if (IsScriptMarked(&bp->site->script)) { michael@0: /* michael@0: * The debugger and the script are both live. michael@0: * Therefore the breakpoint handler is live. michael@0: */ michael@0: if (!IsObjectMarked(&bp->getHandlerRef())) { michael@0: MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler"); michael@0: markedAny = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return markedAny; michael@0: } michael@0: michael@0: /* michael@0: * Mark all debugger-owned GC things unconditionally. This is used by the minor michael@0: * GC: the minor GC cannot apply the weak constraints of the full GC because it michael@0: * visits only part of the heap. michael@0: */ michael@0: void michael@0: Debugger::markAll(JSTracer *trc) michael@0: { michael@0: JSRuntime *rt = trc->runtime(); michael@0: for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) { michael@0: GlobalObjectSet &debuggees = dbg->debuggees; michael@0: for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { michael@0: GlobalObject *global = e.front(); michael@0: michael@0: MarkObjectUnbarriered(trc, &global, "Global Object"); michael@0: if (global != e.front()) michael@0: e.rekeyFront(global); michael@0: } michael@0: michael@0: HeapPtrObject &dbgobj = dbg->toJSObjectRef(); michael@0: MarkObject(trc, &dbgobj, "Debugger Object"); michael@0: michael@0: dbg->scripts.trace(trc); michael@0: dbg->sources.trace(trc); michael@0: dbg->objects.trace(trc); michael@0: dbg->environments.trace(trc); michael@0: michael@0: for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) { michael@0: MarkScriptUnbarriered(trc, &bp->site->script, "breakpoint script"); michael@0: MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: Debugger::traceObject(JSTracer *trc, JSObject *obj) michael@0: { michael@0: if (Debugger *dbg = Debugger::fromJSObject(obj)) michael@0: dbg->trace(trc); michael@0: } michael@0: michael@0: void michael@0: Debugger::trace(JSTracer *trc) michael@0: { michael@0: if (uncaughtExceptionHook) michael@0: MarkObject(trc, &uncaughtExceptionHook, "hooks"); michael@0: michael@0: /* michael@0: * Mark Debugger.Frame objects. These are all reachable from JS, because the michael@0: * corresponding JS frames are still on the stack. michael@0: * michael@0: * (Once we support generator frames properly, we will need michael@0: * weakly-referenced Debugger.Frame objects as well, for suspended generator michael@0: * frames.) michael@0: */ michael@0: for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) { michael@0: RelocatablePtrObject &frameobj = r.front().value(); michael@0: JS_ASSERT(frameobj->getPrivate()); michael@0: MarkObject(trc, &frameobj, "live Debugger.Frame"); michael@0: } michael@0: michael@0: /* Trace the weak map from JSScript instances to Debugger.Script objects. */ michael@0: scripts.trace(trc); michael@0: michael@0: /* Trace the referent ->Debugger.Source weak map */ michael@0: sources.trace(trc); michael@0: michael@0: /* Trace the referent -> Debugger.Object weak map. */ michael@0: objects.trace(trc); michael@0: michael@0: /* Trace the referent -> Debugger.Environment weak map. */ michael@0: environments.trace(trc); michael@0: } michael@0: michael@0: void michael@0: Debugger::sweepAll(FreeOp *fop) michael@0: { michael@0: JSRuntime *rt = fop->runtime(); michael@0: michael@0: for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) { michael@0: if (IsObjectAboutToBeFinalized(&dbg->object)) { michael@0: /* michael@0: * dbg is being GC'd. Detach it from its debuggees. The debuggee michael@0: * might be GC'd too. Since detaching requires access to both michael@0: * objects, this must be done before finalize time. michael@0: */ michael@0: for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) { michael@0: // We can't recompile on-stack scripts here, and we michael@0: // can only toggle debug mode to off, so we use an michael@0: // infallible variant of removeDebuggeeGlobal. michael@0: dbg->removeDebuggeeGlobalUnderGC(fop, e.front(), nullptr, &e); michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (gc::GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) { michael@0: /* For each debuggee being GC'd, detach it from all its debuggers. */ michael@0: GlobalObjectSet &debuggees = comp->getDebuggees(); michael@0: for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { michael@0: GlobalObject *global = e.front(); michael@0: if (IsObjectAboutToBeFinalized(&global)) { michael@0: // See infallibility note above. michael@0: detachAllDebuggersFromGlobal(fop, global, &e); michael@0: } michael@0: else if (global != e.front()) michael@0: e.rekeyFront(global); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, michael@0: GlobalObjectSet::Enum *compartmentEnum) michael@0: { michael@0: const GlobalObject::DebuggerVector *debuggers = global->getDebuggers(); michael@0: JS_ASSERT(!debuggers->empty()); michael@0: while (!debuggers->empty()) michael@0: debuggers->back()->removeDebuggeeGlobalUnderGC(fop, global, compartmentEnum, nullptr); michael@0: } michael@0: michael@0: /* static */ void michael@0: Debugger::findCompartmentEdges(Zone *zone, js::gc::ComponentFinder &finder) michael@0: { michael@0: /* michael@0: * For debugger cross compartment wrappers, add edges in the opposite michael@0: * direction to those already added by JSCompartment::findOutgoingEdges. michael@0: * This ensure that debuggers and their debuggees are finalized in the same michael@0: * group. michael@0: */ michael@0: for (Debugger *dbg = zone->runtimeFromMainThread()->debuggerList.getFirst(); michael@0: dbg; michael@0: dbg = dbg->getNext()) michael@0: { michael@0: Zone *w = dbg->object->zone(); michael@0: if (w == zone || !w->isGCMarking()) michael@0: continue; michael@0: if (dbg->scripts.hasKeyInZone(zone) || michael@0: dbg->sources.hasKeyInZone(zone) || michael@0: dbg->objects.hasKeyInZone(zone) || michael@0: dbg->environments.hasKeyInZone(zone)) michael@0: { michael@0: finder.addEdgeTo(w); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: Debugger::finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: Debugger *dbg = fromJSObject(obj); michael@0: if (!dbg) michael@0: return; michael@0: fop->delete_(dbg); michael@0: } michael@0: michael@0: const Class Debugger::jsclass = { michael@0: "Debugger", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Debugger::finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: Debugger::traceObject michael@0: }; michael@0: michael@0: Debugger * michael@0: Debugger::fromThisValue(JSContext *cx, const CallArgs &args, const char *fnname) michael@0: { michael@0: if (!args.thisv().isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return nullptr; michael@0: } michael@0: JSObject *thisobj = &args.thisv().toObject(); michael@0: if (thisobj->getClass() != &Debugger::jsclass) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger", fnname, thisobj->getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Forbid Debugger.prototype, which is of the Debugger JSClass but isn't michael@0: * really a Debugger object. The prototype object is distinguished by michael@0: * having a nullptr private value. michael@0: */ michael@0: Debugger *dbg = fromJSObject(thisobj); michael@0: if (!dbg) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger", fnname, "prototype object"); michael@0: } michael@0: return dbg; michael@0: } michael@0: michael@0: #define THIS_DEBUGGER(cx, argc, vp, fnname, args, dbg) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: Debugger *dbg = Debugger::fromThisValue(cx, args, fnname); \ michael@0: if (!dbg) \ michael@0: return false michael@0: michael@0: bool michael@0: Debugger::getEnabled(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg); michael@0: args.rval().setBoolean(dbg->enabled); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.set enabled", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg); michael@0: bool enabled = ToBoolean(args[0]); michael@0: michael@0: if (enabled != dbg->enabled) { michael@0: for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) { michael@0: if (enabled) michael@0: bp->site->inc(cx->runtime()->defaultFreeOp()); michael@0: else michael@0: bp->site->dec(cx->runtime()->defaultFreeOp()); michael@0: } michael@0: michael@0: /* michael@0: * Add or remove ourselves from the runtime's list of Debuggers michael@0: * that care about new globals. michael@0: */ michael@0: if (dbg->getHook(OnNewGlobalObject)) { michael@0: if (enabled) { michael@0: /* If we were not enabled, the link should be a singleton list. */ michael@0: JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); michael@0: JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink, michael@0: &cx->runtime()->onNewGlobalObjectWatchers); michael@0: } else { michael@0: /* If we were enabled, the link should be inserted in the list. */ michael@0: JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); michael@0: JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink); michael@0: } michael@0: } michael@0: } michael@0: michael@0: dbg->enabled = enabled; michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which) michael@0: { michael@0: JS_ASSERT(which >= 0 && which < HookCount); michael@0: THIS_DEBUGGER(cx, argc, vp, "getHook", args, dbg); michael@0: args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + which)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which) michael@0: { michael@0: JS_ASSERT(which >= 0 && which < HookCount); michael@0: REQUIRE_ARGC("Debugger.setHook", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "setHook", args, dbg); michael@0: if (args[0].isObject()) { michael@0: if (!args[0].toObject().isCallable()) michael@0: return ReportIsNotFunction(cx, args[0], args.length() - 1); michael@0: } else if (!args[0].isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED); michael@0: return false; michael@0: } michael@0: dbg->object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return getHookImpl(cx, argc, vp, OnDebuggerStatement); michael@0: } michael@0: michael@0: bool michael@0: Debugger::setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return setHookImpl(cx, argc, vp, OnDebuggerStatement); michael@0: } michael@0: michael@0: bool michael@0: Debugger::getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return getHookImpl(cx, argc, vp, OnExceptionUnwind); michael@0: } michael@0: michael@0: bool michael@0: Debugger::setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return setHookImpl(cx, argc, vp, OnExceptionUnwind); michael@0: } michael@0: michael@0: bool michael@0: Debugger::getOnNewScript(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return getHookImpl(cx, argc, vp, OnNewScript); michael@0: } michael@0: michael@0: bool michael@0: Debugger::setOnNewScript(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return setHookImpl(cx, argc, vp, OnNewScript); michael@0: } michael@0: michael@0: bool michael@0: Debugger::getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return getHookImpl(cx, argc, vp, OnEnterFrame); michael@0: } michael@0: michael@0: bool michael@0: Debugger::setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return setHookImpl(cx, argc, vp, OnEnterFrame); michael@0: } michael@0: michael@0: bool michael@0: Debugger::getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return getHookImpl(cx, argc, vp, OnNewGlobalObject); michael@0: } michael@0: michael@0: bool michael@0: Debugger::setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "setOnNewGlobalObject", args, dbg); michael@0: RootedObject oldHook(cx, dbg->getHook(OnNewGlobalObject)); michael@0: michael@0: if (!setHookImpl(cx, argc, vp, OnNewGlobalObject)) michael@0: return false; michael@0: michael@0: /* michael@0: * Add or remove ourselves from the runtime's list of Debuggers that michael@0: * care about new globals. michael@0: */ michael@0: if (dbg->enabled) { michael@0: JSObject *newHook = dbg->getHook(OnNewGlobalObject); michael@0: if (!oldHook && newHook) { michael@0: /* If we didn't have a hook, the link should be a singleton list. */ michael@0: JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); michael@0: JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink, michael@0: &cx->runtime()->onNewGlobalObjectWatchers); michael@0: } else if (oldHook && !newHook) { michael@0: /* If we did have a hook, the link should be inserted in the list. */ michael@0: JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); michael@0: JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg); michael@0: args.rval().setObjectOrNull(dbg->uncaughtExceptionHook); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.set uncaughtExceptionHook", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg); michael@0: if (!args[0].isNull() && (!args[0].isObject() || !args[0].toObject().isCallable())) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ASSIGN_FUNCTION_OR_NULL, michael@0: "uncaughtExceptionHook"); michael@0: return false; michael@0: } michael@0: dbg->uncaughtExceptionHook = args[0].toObjectOrNull(); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: bool michael@0: Debugger::getMemory(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "get memory", args, dbg); michael@0: args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE)); michael@0: return true; michael@0: } michael@0: michael@0: GlobalObject * michael@0: Debugger::unwrapDebuggeeArgument(JSContext *cx, const Value &v) michael@0: { michael@0: if (!v.isObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "argument", "not a global object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: RootedObject obj(cx, &v.toObject()); michael@0: michael@0: /* If it's a Debugger.Object belonging to this debugger, dereference that. */ michael@0: if (obj->getClass() == &DebuggerObject_class) { michael@0: RootedValue rv(cx, v); michael@0: if (!unwrapDebuggeeValue(cx, &rv)) michael@0: return nullptr; michael@0: obj = &rv.toObject(); michael@0: } michael@0: michael@0: /* If we have a cross-compartment wrapper, dereference as far as is secure. */ michael@0: obj = CheckedUnwrap(obj); michael@0: if (!obj) { michael@0: JS_ReportError(cx, "Permission denied to access object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* If that produced an outer window, innerize it. */ michael@0: obj = GetInnerObject(cx, obj); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: /* If that didn't produce a global object, it's an error. */ michael@0: if (!obj->is()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "argument", "not a global object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: bool michael@0: Debugger::addDebuggee(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.addDebuggee", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg); michael@0: Rooted global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); michael@0: if (!global) michael@0: return false; michael@0: michael@0: if (!dbg->addDebuggeeGlobal(cx, global)) michael@0: return false; michael@0: michael@0: RootedValue v(cx, ObjectValue(*global)); michael@0: if (!dbg->wrapDebuggeeValue(cx, &v)) michael@0: return false; michael@0: args.rval().set(v); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg); michael@0: for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) { michael@0: // Invalidate a zone at a time to avoid doing a zone-wide CellIter michael@0: // per compartment. michael@0: AutoDebugModeInvalidation invalidate(zone); michael@0: michael@0: for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { michael@0: if (c == dbg->object->compartment() || c->options().invisibleToDebugger()) michael@0: continue; michael@0: c->zone()->scheduledForDestruction = false; michael@0: GlobalObject *global = c->maybeGlobal(); michael@0: if (global) { michael@0: Rooted rg(cx, global); michael@0: if (!dbg->addDebuggeeGlobal(cx, rg, invalidate)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.removeDebuggee", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg); michael@0: GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]); michael@0: if (!global) michael@0: return false; michael@0: if (dbg->debuggees.has(global)) { michael@0: if (!dbg->removeDebuggeeGlobal(cx, global, nullptr, nullptr)) michael@0: return false; michael@0: } michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg); michael@0: michael@0: for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) { michael@0: if (!dbg->removeDebuggeeGlobal(cx, e.front(), nullptr, &e)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.hasDebuggee", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg); michael@0: GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]); michael@0: if (!global) michael@0: return false; michael@0: args.rval().setBoolean(!!dbg->debuggees.lookup(global)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::getDebuggees(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg); michael@0: RootedObject arrobj(cx, NewDenseAllocatedArray(cx, dbg->debuggees.count())); michael@0: if (!arrobj) michael@0: return false; michael@0: arrobj->ensureDenseInitializedLength(cx, 0, dbg->debuggees.count()); michael@0: unsigned i = 0; michael@0: for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) { michael@0: RootedValue v(cx, ObjectValue(*e.front())); michael@0: if (!dbg->wrapDebuggeeValue(cx, &v)) michael@0: return false; michael@0: arrobj->setDenseElement(i++, v); michael@0: } michael@0: args.rval().setObject(*arrobj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg); michael@0: michael@0: /* Since there may be multiple contexts, use AllFramesIter. */ michael@0: for (AllFramesIter i(cx); !i.done(); ++i) { michael@0: if (dbg->observesFrame(i)) { michael@0: // Ensure that Ion frames are rematerialized. Only rematerialized michael@0: // Ion frames may be used as AbstractFramePtrs. michael@0: if (i.isIon() && !i.ensureHasRematerializedFrame()) michael@0: return false; michael@0: AbstractFramePtr frame = i.abstractFramePtr(); michael@0: ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED); michael@0: while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame) michael@0: ++iter; michael@0: return dbg->getScriptFrame(cx, iter, args.rval()); michael@0: } michael@0: } michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg); michael@0: for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) michael@0: r.front()->compartment()->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), michael@0: dbg, NullPtr()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Check that the arguments, if any, are cross-compartment wrappers. */ michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: const Value &arg = args[i]; michael@0: if (!arg.isObject()) michael@0: return ReportObjectRequired(cx); michael@0: JSObject *argobj = &arg.toObject(); michael@0: if (!argobj->is()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CCW_REQUIRED, michael@0: "Debugger"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Get Debugger.prototype. */ michael@0: RootedValue v(cx); michael@0: RootedObject callee(cx, &args.callee()); michael@0: if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &v)) michael@0: return false; michael@0: RootedObject proto(cx, &v.toObject()); michael@0: JS_ASSERT(proto->getClass() == &Debugger::jsclass); michael@0: /* michael@0: * Make the new Debugger object. Each one has a reference to michael@0: * Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The michael@0: * rest of the reserved slots are for hooks; they default to undefined. michael@0: */ michael@0: RootedObject obj(cx, NewObjectWithGivenProto(cx, &Debugger::jsclass, proto, nullptr)); michael@0: if (!obj) michael@0: return false; michael@0: for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++) michael@0: obj->setReservedSlot(slot, proto->getReservedSlot(slot)); michael@0: /* Create the Debugger.Memory instance accessible by the michael@0: * |Debugger.prototype.memory| getter. */ michael@0: Value memoryProto = obj->getReservedSlot(JSSLOT_DEBUG_MEMORY_PROTO); michael@0: RootedObject memory(cx, NewObjectWithGivenProto(cx, &DebuggerMemory::class_, michael@0: &memoryProto.toObject(), nullptr)); michael@0: if (!memory) michael@0: return false; michael@0: obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, ObjectValue(*memory)); michael@0: michael@0: /* Construct the underlying C++ object. */ michael@0: Debugger *dbg = cx->new_(cx, obj.get()); michael@0: if (!dbg) michael@0: return false; michael@0: if (!dbg->init(cx)) { michael@0: js_delete(dbg); michael@0: return false; michael@0: } michael@0: obj->setPrivate(dbg); michael@0: /* Now the JSObject owns the js::Debugger instance, so we needn't delete it. */ michael@0: michael@0: /* Add the initial debuggees, if any. */ michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: Rooted michael@0: debuggee(cx, &args[i].toObject().as().private_().toObject().global()); michael@0: if (!dbg->addDebuggeeGlobal(cx, debuggee)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::addDebuggeeGlobal(JSContext *cx, Handle global) michael@0: { michael@0: AutoDebugModeInvalidation invalidate(global->compartment()); michael@0: return addDebuggeeGlobal(cx, global, invalidate); michael@0: } michael@0: michael@0: bool michael@0: Debugger::addDebuggeeGlobal(JSContext *cx, michael@0: Handle global, michael@0: AutoDebugModeInvalidation &invalidate) michael@0: { michael@0: if (debuggees.has(global)) michael@0: return true; michael@0: michael@0: // Callers should generally be unable to get a reference to a debugger- michael@0: // invisible global in order to pass it to addDebuggee. But this is possible michael@0: // with certain testing aides we expose in the shell, so just make addDebuggee michael@0: // throw in that case. michael@0: JSCompartment *debuggeeCompartment = global->compartment(); michael@0: if (debuggeeCompartment->options().invisibleToDebugger()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_DEBUG_CANT_DEBUG_GLOBAL); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Check for cycles. If global's compartment is reachable from this michael@0: * Debugger object's compartment by following debuggee-to-debugger links, michael@0: * then adding global would create a cycle. (Typically nobody is debugging michael@0: * the debugger, in which case we zip through this code without looping.) michael@0: */ michael@0: Vector visited(cx); michael@0: if (!visited.append(object->compartment())) michael@0: return false; michael@0: for (size_t i = 0; i < visited.length(); i++) { michael@0: JSCompartment *c = visited[i]; michael@0: if (c == debuggeeCompartment) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Find all compartments containing debuggers debugging global objects michael@0: * in c. Add those compartments to visited. michael@0: */ michael@0: for (GlobalObjectSet::Range r = c->getDebuggees().all(); !r.empty(); r.popFront()) { michael@0: GlobalObject::DebuggerVector *v = r.front()->getDebuggers(); michael@0: for (Debugger **p = v->begin(); p != v->end(); p++) { michael@0: JSCompartment *next = (*p)->object->compartment(); michael@0: if (Find(visited, next) == visited.end() && !visited.append(next)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Each debugger-debuggee relation must be stored in up to three places. michael@0: * JSCompartment::addDebuggee enables debug mode if needed. michael@0: */ michael@0: AutoCompartment ac(cx, global); michael@0: GlobalObject::DebuggerVector *v = GlobalObject::getOrCreateDebuggers(cx, global); michael@0: if (!v || !v->append(this)) { michael@0: js_ReportOutOfMemory(cx); michael@0: } else { michael@0: if (!debuggees.put(global)) { michael@0: js_ReportOutOfMemory(cx); michael@0: } else { michael@0: if (global->getDebuggers()->length() > 1) michael@0: return true; michael@0: if (debuggeeCompartment->addDebuggee(cx, global, invalidate)) michael@0: return true; michael@0: michael@0: /* Maintain consistency on error. */ michael@0: debuggees.remove(global); michael@0: } michael@0: JS_ASSERT(v->back() == this); michael@0: v->popBack(); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: Debugger::cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global, michael@0: AutoDebugModeInvalidation &invalidate, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum) michael@0: { michael@0: /* michael@0: * Each debuggee is in two HashSets: one for its compartment and one for michael@0: * its debugger (this). The caller might be enumerating either set; if so, michael@0: * use HashSet::Enum::removeFront rather than HashSet::remove below, to michael@0: * avoid invalidating the live enumerator. michael@0: */ michael@0: JS_ASSERT(global->compartment()->getDebuggees().has(global)); michael@0: JS_ASSERT_IF(compartmentEnum, compartmentEnum->front() == global); michael@0: JS_ASSERT(debuggees.has(global)); michael@0: JS_ASSERT_IF(debugEnum, debugEnum->front() == global); michael@0: michael@0: /* michael@0: * FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame michael@0: * objects referring to a particular JS stack frame. This is hard if michael@0: * Debugger objects that are no longer debugging the relevant global might michael@0: * have live Frame objects. So we take the easy way out and kill them here. michael@0: * This is a bug, since it's observable and contrary to the spec. One michael@0: * possible fix would be to put such objects into a compartment-wide bag michael@0: * which slowPathOnLeaveFrame would have to examine. michael@0: */ michael@0: for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) { michael@0: AbstractFramePtr frame = e.front().key(); michael@0: JSObject *frameobj = e.front().value(); michael@0: if (&frame.script()->global() == global) { michael@0: DebuggerFrame_freeScriptFrameIterData(fop, frameobj); michael@0: DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj); michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: michael@0: GlobalObject::DebuggerVector *v = global->getDebuggers(); michael@0: Debugger **p; michael@0: for (p = v->begin(); p != v->end(); p++) { michael@0: if (*p == this) michael@0: break; michael@0: } michael@0: JS_ASSERT(p != v->end()); michael@0: michael@0: /* michael@0: * The relation must be removed from up to three places: *v and debuggees michael@0: * for sure, and possibly the compartment's debuggee set. michael@0: */ michael@0: v->erase(p); michael@0: if (debugEnum) michael@0: debugEnum->removeFront(); michael@0: else michael@0: debuggees.remove(global); michael@0: michael@0: /* Remove all breakpoints for the debuggee. */ michael@0: Breakpoint *nextbp; michael@0: for (Breakpoint *bp = firstBreakpoint(); bp; bp = nextbp) { michael@0: nextbp = bp->nextInDebugger(); michael@0: if (bp->site->script->compartment() == global->compartment()) michael@0: bp->destroy(fop); michael@0: } michael@0: JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint()); michael@0: } michael@0: michael@0: bool michael@0: Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum) michael@0: { michael@0: AutoDebugModeInvalidation invalidate(global->compartment()); michael@0: return removeDebuggeeGlobal(cx, global, invalidate, compartmentEnum, debugEnum); michael@0: } michael@0: michael@0: bool michael@0: Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, michael@0: AutoDebugModeInvalidation &invalidate, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum) michael@0: { michael@0: cleanupDebuggeeGlobalBeforeRemoval(cx->runtime()->defaultFreeOp(), global, michael@0: invalidate, compartmentEnum, debugEnum); michael@0: michael@0: // The debuggee needs to be removed from the compartment last to save a root. michael@0: if (global->getDebuggers()->empty()) michael@0: return global->compartment()->removeDebuggee(cx, global, invalidate, compartmentEnum); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum) michael@0: { michael@0: AutoDebugModeInvalidation invalidate(global->compartment()); michael@0: removeDebuggeeGlobalUnderGC(fop, global, invalidate, compartmentEnum, debugEnum); michael@0: } michael@0: michael@0: void michael@0: Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, michael@0: AutoDebugModeInvalidation &invalidate, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum) michael@0: { michael@0: cleanupDebuggeeGlobalBeforeRemoval(fop, global, invalidate, compartmentEnum, debugEnum); michael@0: michael@0: /* michael@0: * The debuggee needs to be removed from the compartment last, as this can michael@0: * trigger GCs if the compartment's debug mode is being changed, and the michael@0: * global cannot be rooted on the stack without a cx. michael@0: */ michael@0: if (global->getDebuggers()->empty()) michael@0: global->compartment()->removeDebuggeeUnderGC(fop, global, invalidate, compartmentEnum); michael@0: } michael@0: michael@0: /* michael@0: * A class for parsing 'findScripts' query arguments and searching for michael@0: * scripts that match the criteria they represent. michael@0: */ michael@0: class Debugger::ScriptQuery { michael@0: public: michael@0: /* Construct a ScriptQuery to use matching scripts for |dbg|. */ michael@0: ScriptQuery(JSContext *cx, Debugger *dbg): michael@0: cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx), displayURL(cx), michael@0: displayURLChars(nullptr), innermostForCompartment(cx->runtime()) michael@0: {} michael@0: michael@0: /* michael@0: * Initialize this ScriptQuery. Raise an error and return false if we michael@0: * haven't enough memory. michael@0: */ michael@0: bool init() { michael@0: if (!compartments.init() || michael@0: !innermostForCompartment.init()) michael@0: { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Parse the query object |query|, and prepare to match only the scripts michael@0: * it specifies. michael@0: */ michael@0: bool parseQuery(HandleObject query) { michael@0: /* michael@0: * Check for a 'global' property, which limits the results to those michael@0: * scripts scoped to a particular global object. michael@0: */ michael@0: RootedValue global(cx); michael@0: if (!JSObject::getProperty(cx, query, query, cx->names().global, &global)) michael@0: return false; michael@0: if (global.isUndefined()) { michael@0: matchAllDebuggeeGlobals(); michael@0: } else { michael@0: GlobalObject *globalObject = debugger->unwrapDebuggeeArgument(cx, global); michael@0: if (!globalObject) michael@0: return false; michael@0: michael@0: /* michael@0: * If the given global isn't a debuggee, just leave the set of michael@0: * acceptable globals empty; we'll return no scripts. michael@0: */ michael@0: if (debugger->debuggees.has(globalObject)) { michael@0: if (!matchSingleGlobal(globalObject)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Check for a 'url' property. */ michael@0: if (!JSObject::getProperty(cx, query, query, cx->names().url, &url)) michael@0: return false; michael@0: if (!url.isUndefined() && !url.isString()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "query object's 'url' property", "neither undefined nor a string"); michael@0: return false; michael@0: } michael@0: michael@0: /* Check for a 'line' property. */ michael@0: RootedValue lineProperty(cx); michael@0: if (!JSObject::getProperty(cx, query, query, cx->names().line, &lineProperty)) michael@0: return false; michael@0: if (lineProperty.isUndefined()) { michael@0: hasLine = false; michael@0: } else if (lineProperty.isNumber()) { michael@0: if (url.isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_QUERY_LINE_WITHOUT_URL); michael@0: return false; michael@0: } michael@0: double doubleLine = lineProperty.toNumber(); michael@0: if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE); michael@0: return false; michael@0: } michael@0: hasLine = true; michael@0: line = doubleLine; michael@0: } else { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "query object's 'line' property", michael@0: "neither undefined nor an integer"); michael@0: return false; michael@0: } michael@0: michael@0: /* Check for an 'innermost' property. */ michael@0: PropertyName *innermostName = cx->names().innermost; michael@0: RootedValue innermostProperty(cx); michael@0: if (!JSObject::getProperty(cx, query, query, innermostName, &innermostProperty)) michael@0: return false; michael@0: innermost = ToBoolean(innermostProperty); michael@0: if (innermost) { michael@0: /* Technically, we need only check hasLine, but this is clearer. */ michael@0: if (url.isUndefined() || !hasLine) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Check for a 'displayURL' property. */ michael@0: if (!JSObject::getProperty(cx, query, query, cx->names().displayURL, &displayURL)) michael@0: return false; michael@0: if (!displayURL.isUndefined() && !displayURL.isString()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "query object's 'displayURL' property", michael@0: "neither undefined nor a string"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* Set up this ScriptQuery appropriately for a missing query argument. */ michael@0: bool omittedQuery() { michael@0: url.setUndefined(); michael@0: hasLine = false; michael@0: innermost = false; michael@0: displayURLChars = nullptr; michael@0: return matchAllDebuggeeGlobals(); michael@0: } michael@0: michael@0: /* michael@0: * Search all relevant compartments and the stack for scripts matching michael@0: * this query, and append the matching scripts to |vector|. michael@0: */ michael@0: bool findScripts(AutoScriptVector *v) { michael@0: if (!prepareQuery()) michael@0: return false; michael@0: michael@0: JSCompartment *singletonComp = nullptr; michael@0: if (compartments.count() == 1) michael@0: singletonComp = compartments.all().front(); michael@0: michael@0: /* Search each compartment for debuggee scripts. */ michael@0: vector = v; michael@0: oom = false; michael@0: IterateScripts(cx->runtime(), singletonComp, this, considerScript); michael@0: if (oom) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * For most queries, we just accumulate results in 'vector' as we find michael@0: * them. But if this is an 'innermost' query, then we've accumulated the michael@0: * results in the 'innermostForCompartment' map. In that case, we now need to michael@0: * walk that map and populate 'vector'. michael@0: */ michael@0: if (innermost) { michael@0: for (CompartmentToScriptMap::Range r = innermostForCompartment.all(); michael@0: !r.empty(); michael@0: r.popFront()) { michael@0: if (!v->append(r.front().value())) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: private: michael@0: /* The context in which we should do our work. */ michael@0: JSContext *cx; michael@0: michael@0: /* The debugger for which we conduct queries. */ michael@0: Debugger *debugger; michael@0: michael@0: typedef HashSet, RuntimeAllocPolicy> michael@0: CompartmentSet; michael@0: michael@0: /* A script must be in one of these compartments to match the query. */ michael@0: CompartmentSet compartments; michael@0: michael@0: /* If this is a string, matching scripts have urls equal to it. */ michael@0: RootedValue url; michael@0: michael@0: /* url as a C string. */ michael@0: JSAutoByteString urlCString; michael@0: michael@0: /* If this is a string, matching scripts' sources have displayURLs equal to michael@0: * it. */ michael@0: RootedValue displayURL; michael@0: michael@0: /* displayURL as a jschar* */ michael@0: const jschar *displayURLChars; michael@0: size_t displayURLLength; michael@0: michael@0: /* True if the query contained a 'line' property. */ michael@0: bool hasLine; michael@0: michael@0: /* The line matching scripts must cover. */ michael@0: unsigned int line; michael@0: michael@0: /* True if the query has an 'innermost' property whose value is true. */ michael@0: bool innermost; michael@0: michael@0: typedef HashMap, RuntimeAllocPolicy> michael@0: CompartmentToScriptMap; michael@0: michael@0: /* michael@0: * For 'innermost' queries, a map from compartments to the innermost script michael@0: * we've seen so far in that compartment. (Template instantiation code size michael@0: * explosion ho!) michael@0: */ michael@0: CompartmentToScriptMap innermostForCompartment; michael@0: michael@0: /* The vector to which to append the scripts found. */ michael@0: AutoScriptVector *vector; michael@0: michael@0: /* Indicates whether OOM has occurred while matching. */ michael@0: bool oom; michael@0: michael@0: bool addCompartment(JSCompartment *comp) { michael@0: { michael@0: // All scripts in the debuggee compartment must be visible, so michael@0: // delazify everything. michael@0: AutoCompartment ac(cx, comp); michael@0: if (!comp->ensureDelazifyScriptsForDebugMode(cx)) michael@0: return false; michael@0: } michael@0: return compartments.put(comp); michael@0: } michael@0: michael@0: /* Arrange for this ScriptQuery to match only scripts that run in |global|. */ michael@0: bool matchSingleGlobal(GlobalObject *global) { michael@0: JS_ASSERT(compartments.count() == 0); michael@0: if (!addCompartment(global->compartment())) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Arrange for this ScriptQuery to match all scripts running in debuggee michael@0: * globals. michael@0: */ michael@0: bool matchAllDebuggeeGlobals() { michael@0: JS_ASSERT(compartments.count() == 0); michael@0: /* Build our compartment set from the debugger's set of debuggee globals. */ michael@0: for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) { michael@0: if (!addCompartment(r.front()->compartment())) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Given that parseQuery or omittedQuery has been called, prepare to match michael@0: * scripts. Set urlCString and displayURLChars as appropriate. michael@0: */ michael@0: bool prepareQuery() { michael@0: /* Compute urlCString and displayURLChars, if a url or displayURL was michael@0: * given respectively. */ michael@0: if (url.isString()) { michael@0: if (!urlCString.encodeLatin1(cx, url.toString())) michael@0: return false; michael@0: } michael@0: if (displayURL.isString()) { michael@0: JSString *s = displayURL.toString(); michael@0: displayURLChars = s->getChars(cx); michael@0: displayURLLength = s->length(); michael@0: if (!displayURLChars) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void considerScript(JSRuntime *rt, void *data, JSScript *script) { michael@0: ScriptQuery *self = static_cast(data); michael@0: self->consider(script); michael@0: } michael@0: michael@0: /* michael@0: * If |script| matches this query, append it to |vector| or place it in michael@0: * |innermostForCompartment|, as appropriate. Set |oom| if an out of memory michael@0: * condition occurred. michael@0: */ michael@0: void consider(JSScript *script) { michael@0: // We check for presence of script->code() because it is possible that michael@0: // the script was created and thus exposed to GC, but *not* fully michael@0: // initialized from fullyInit{FromEmitter,Trivial} due to errors. michael@0: if (oom || script->selfHosted() || !script->code()) michael@0: return; michael@0: JSCompartment *compartment = script->compartment(); michael@0: if (!compartments.has(compartment)) michael@0: return; michael@0: if (urlCString.ptr()) { michael@0: bool gotFilename = false; michael@0: if (script->filename() && strcmp(script->filename(), urlCString.ptr()) == 0) michael@0: gotFilename = true; michael@0: michael@0: bool gotSourceURL = false; michael@0: if (!gotFilename && script->scriptSource()->introducerFilename() && michael@0: strcmp(script->scriptSource()->introducerFilename(), urlCString.ptr()) == 0) michael@0: { michael@0: gotSourceURL = true; michael@0: } michael@0: if (!gotFilename && !gotSourceURL) michael@0: return; michael@0: } michael@0: if (hasLine) { michael@0: if (line < script->lineno() || script->lineno() + js_GetScriptLineExtent(script) < line) michael@0: return; michael@0: } michael@0: if (displayURLChars) { michael@0: if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL()) michael@0: return; michael@0: const jschar *s = script->scriptSource()->displayURL(); michael@0: if (CompareChars(s, js_strlen(s), displayURLChars, displayURLLength) != 0) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (innermost) { michael@0: /* michael@0: * For 'innermost' queries, we don't place scripts in |vector| right michael@0: * away; we may later find another script that is nested inside this michael@0: * one. Instead, we record the innermost script we've found so far michael@0: * for each compartment in innermostForCompartment, and only michael@0: * populate |vector| at the bottom of findScripts, when we've michael@0: * traversed all the scripts. michael@0: * michael@0: * So: check this script against the innermost one we've found so michael@0: * far (if any), as recorded in innermostForCompartment, and replace michael@0: * that if it's better. michael@0: */ michael@0: CompartmentToScriptMap::AddPtr p = innermostForCompartment.lookupForAdd(compartment); michael@0: if (p) { michael@0: /* Is our newly found script deeper than the last one we found? */ michael@0: JSScript *incumbent = p->value(); michael@0: if (script->staticLevel() > incumbent->staticLevel()) michael@0: p->value() = script; michael@0: } else { michael@0: /* michael@0: * This is the first matching script we've encountered for this michael@0: * compartment, so it is thus the innermost such script. michael@0: */ michael@0: if (!innermostForCompartment.add(p, compartment, script)) { michael@0: oom = true; michael@0: return; michael@0: } michael@0: } michael@0: } else { michael@0: /* Record this matching script in the results vector. */ michael@0: if (!vector->append(script)) { michael@0: oom = true; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: return; michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg); michael@0: michael@0: ScriptQuery query(cx, dbg); michael@0: if (!query.init()) michael@0: return false; michael@0: michael@0: if (args.length() >= 1) { michael@0: RootedObject queryObject(cx, NonNullObject(cx, args[0])); michael@0: if (!queryObject || !query.parseQuery(queryObject)) michael@0: return false; michael@0: } else { michael@0: if (!query.omittedQuery()) michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Accumulate the scripts in an AutoScriptVector, instead of creating michael@0: * the JS array as we go, because we mustn't allocate JS objects or GC michael@0: * while we use the CellIter. michael@0: */ michael@0: AutoScriptVector scripts(cx); michael@0: michael@0: if (!query.findScripts(&scripts)) michael@0: return false; michael@0: michael@0: RootedObject result(cx, NewDenseAllocatedArray(cx, scripts.length())); michael@0: if (!result) michael@0: return false; michael@0: michael@0: result->ensureDenseInitializedLength(cx, 0, scripts.length()); michael@0: michael@0: for (size_t i = 0; i < scripts.length(); i++) { michael@0: JSObject *scriptObject = dbg->wrapScript(cx, scripts.handleAt(i)); michael@0: if (!scriptObject) michael@0: return false; michael@0: result->setDenseElement(i, ObjectValue(*scriptObject)); michael@0: } michael@0: michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg); michael@0: michael@0: RootedObject result(cx, NewDenseEmptyArray(cx)); michael@0: if (!result) michael@0: return false; michael@0: michael@0: for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) { michael@0: if (c->options().invisibleToDebugger()) michael@0: continue; michael@0: michael@0: c->zone()->scheduledForDestruction = false; michael@0: michael@0: GlobalObject *global = c->maybeGlobal(); michael@0: michael@0: if (cx->runtime()->isSelfHostingGlobal(global)) michael@0: continue; michael@0: michael@0: if (global) { michael@0: /* michael@0: * We pulled |global| out of nowhere, so it's possible that it was michael@0: * marked gray by XPConnect. Since we're now exposing it to JS code, michael@0: * we need to mark it black. michael@0: */ michael@0: JS::ExposeGCThingToActiveJS(global, JSTRACE_OBJECT); michael@0: michael@0: RootedValue globalValue(cx, ObjectValue(*global)); michael@0: if (!dbg->wrapDebuggeeValue(cx, &globalValue)) michael@0: return false; michael@0: if (!NewbornArrayPush(cx, result, globalValue)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.makeGlobalObjectReference", 1); michael@0: THIS_DEBUGGER(cx, argc, vp, "makeGlobalObjectReference", args, dbg); michael@0: michael@0: Rooted global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); michael@0: if (!global) michael@0: return false; michael@0: michael@0: args.rval().setObject(*global); michael@0: return dbg->wrapDebuggeeValue(cx, args.rval()); michael@0: } michael@0: michael@0: const JSPropertySpec Debugger::properties[] = { michael@0: JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0), michael@0: JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement, michael@0: Debugger::setOnDebuggerStatement, 0), michael@0: JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind, michael@0: Debugger::setOnExceptionUnwind, 0), michael@0: JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript, 0), michael@0: JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0), michael@0: JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0), michael@0: JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook, michael@0: Debugger::setUncaughtExceptionHook, 0), michael@0: JS_PSG("memory", Debugger::getMemory, 0), michael@0: JS_PS_END michael@0: }; michael@0: const JSFunctionSpec Debugger::methods[] = { michael@0: JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0), michael@0: JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0), michael@0: JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0), michael@0: JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0), michael@0: JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0), michael@0: JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0), michael@0: JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0), michael@0: JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 1, 0), michael@0: JS_FN("findScripts", Debugger::findScripts, 1, 0), michael@0: JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0), michael@0: JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: /*** Debugger.Script *****************************************************************************/ michael@0: michael@0: static inline JSScript * michael@0: GetScriptReferent(JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->getClass() == &DebuggerScript_class); michael@0: return static_cast(obj->getPrivate()); michael@0: } michael@0: michael@0: static void michael@0: DebuggerScript_trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: /* This comes from a private pointer, so no barrier needed. */ michael@0: if (JSScript *script = GetScriptReferent(obj)) { michael@0: MarkCrossCompartmentScriptUnbarriered(trc, obj, &script, "Debugger.Script referent"); michael@0: obj->setPrivateUnbarriered(script); michael@0: } michael@0: } michael@0: michael@0: const Class DebuggerScript_class = { michael@0: "Script", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: DebuggerScript_trace michael@0: }; michael@0: michael@0: JSObject * michael@0: Debugger::newDebuggerScript(JSContext *cx, HandleScript script) michael@0: { michael@0: assertSameCompartment(cx, object.get()); michael@0: michael@0: JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject(); michael@0: JS_ASSERT(proto); michael@0: JSObject *scriptobj = NewObjectWithGivenProto(cx, &DebuggerScript_class, proto, nullptr, TenuredObject); michael@0: if (!scriptobj) michael@0: return nullptr; michael@0: scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object)); michael@0: scriptobj->setPrivateGCThing(script); michael@0: michael@0: return scriptobj; michael@0: } michael@0: michael@0: JSObject * michael@0: Debugger::wrapScript(JSContext *cx, HandleScript script) michael@0: { michael@0: assertSameCompartment(cx, object.get()); michael@0: JS_ASSERT(cx->compartment() != script->compartment()); michael@0: DependentAddPtr p(cx, scripts, script); michael@0: if (!p) { michael@0: JSObject *scriptobj = newDebuggerScript(cx, script); michael@0: if (!scriptobj) michael@0: return nullptr; michael@0: michael@0: if (!p.add(cx, scripts, script, scriptobj)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: michael@0: CrossCompartmentKey key(CrossCompartmentKey::DebuggerScript, object, script); michael@0: if (!object->compartment()->putWrapper(cx, key, ObjectValue(*scriptobj))) { michael@0: scripts.remove(script); michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(GetScriptReferent(p->value()) == script); michael@0: return p->value(); michael@0: } michael@0: michael@0: static JSObject * michael@0: DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const char *fnname) michael@0: { michael@0: if (!v.isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return nullptr; michael@0: } michael@0: JSObject *thisobj = &v.toObject(); michael@0: if (thisobj->getClass() != &DebuggerScript_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: clsname, fnname, thisobj->getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Check for Debugger.Script.prototype, which is of class DebuggerScript_class michael@0: * but whose script is null. michael@0: */ michael@0: if (!GetScriptReferent(thisobj)) { michael@0: JS_ASSERT(!GetScriptReferent(thisobj)); michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: clsname, fnname, "prototype object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: return thisobj; michael@0: } michael@0: michael@0: static JSObject * michael@0: DebuggerScript_checkThis(JSContext *cx, const CallArgs &args, const char *fnname) michael@0: { michael@0: return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname); michael@0: } michael@0: michael@0: #define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: RootedObject obj(cx, DebuggerScript_checkThis(cx, args, fnname)); \ michael@0: if (!obj) \ michael@0: return false; \ michael@0: Rooted script(cx, GetScriptReferent(obj)) michael@0: michael@0: static bool michael@0: DebuggerScript_getUrl(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get url)", args, obj, script); michael@0: michael@0: if (script->filename()) { michael@0: JSString *str; michael@0: if (script->scriptSource()->introducerFilename()) michael@0: str = js_NewStringCopyZ(cx, script->scriptSource()->introducerFilename()); michael@0: else michael@0: str = js_NewStringCopyZ(cx, script->filename()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: } else { michael@0: args.rval().setNull(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getStartLine(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get startLine)", args, obj, script); michael@0: args.rval().setNumber(uint32_t(script->lineno())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getLineCount(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get lineCount)", args, obj, script); michael@0: michael@0: unsigned maxLine = js_GetScriptLineExtent(script); michael@0: args.rval().setNumber(double(maxLine)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get source)", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: michael@0: RootedScriptSource source(cx, &UncheckedUnwrap(script->sourceObject())->as()); michael@0: RootedObject sourceObject(cx, dbg->wrapSource(cx, source)); michael@0: if (!sourceObject) michael@0: return false; michael@0: michael@0: args.rval().setObject(*sourceObject); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getSourceStart(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceStart)", args, obj, script); michael@0: args.rval().setNumber(uint32_t(script->sourceStart())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getSourceLength(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceEnd)", args, obj, script); michael@0: args.rval().setNumber(uint32_t(script->sourceEnd() - script->sourceStart())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getStaticLevel(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get staticLevel)", args, obj, script); michael@0: args.rval().setNumber(uint32_t(script->staticLevel())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getSourceMapUrl(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceMapURL)", args, obj, script); michael@0: michael@0: ScriptSource *source = script->scriptSource(); michael@0: JS_ASSERT(source); michael@0: michael@0: if (source->hasSourceMapURL()) { michael@0: JSString *str = JS_NewUCStringCopyZ(cx, source->sourceMapURL()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: } else { michael@0: args.rval().setNull(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getGlobal(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get global)", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: michael@0: RootedValue v(cx, ObjectValue(script->global())); michael@0: if (!dbg->wrapDebuggeeValue(cx, &v)) michael@0: return false; michael@0: args.rval().set(v); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: michael@0: RootedObject result(cx, NewDenseEmptyArray(cx)); michael@0: if (!result) michael@0: return false; michael@0: if (script->hasObjects()) { michael@0: /* michael@0: * script->savedCallerFun indicates that this is a direct eval script michael@0: * and the calling function is stored as script->objects()->vector[0]. michael@0: * It is not really a child script of this script, so skip it using michael@0: * innerObjectsStart(). michael@0: */ michael@0: ObjectArray *objects = script->objects(); michael@0: RootedFunction fun(cx); michael@0: RootedScript funScript(cx); michael@0: RootedObject obj(cx), s(cx); michael@0: for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) { michael@0: obj = objects->vector[i]; michael@0: if (obj->is()) { michael@0: fun = &obj->as(); michael@0: funScript = GetOrCreateFunctionScript(cx, fun); michael@0: if (!funScript) michael@0: return false; michael@0: s = dbg->wrapScript(cx, funScript); michael@0: if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ScriptOffset(JSContext *cx, JSScript *script, const Value &v, size_t *offsetp) michael@0: { michael@0: double d; michael@0: size_t off; michael@0: michael@0: bool ok = v.isNumber(); michael@0: if (ok) { michael@0: d = v.toNumber(); michael@0: off = size_t(d); michael@0: } michael@0: if (!ok || off != d || !IsValidBytecodeOffset(cx, script, off)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET); michael@0: return false; michael@0: } michael@0: *offsetp = off; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getOffsetLine(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Script.getOffsetLine", 1); michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script); michael@0: size_t offset; michael@0: if (!ScriptOffset(cx, script, args[0], &offset)) michael@0: return false; michael@0: unsigned lineno = JS_PCToLineNumber(cx, script, script->offsetToPC(offset)); michael@0: args.rval().setNumber(lineno); michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class BytecodeRangeWithPosition : private BytecodeRange michael@0: { michael@0: public: michael@0: using BytecodeRange::empty; michael@0: using BytecodeRange::frontPC; michael@0: using BytecodeRange::frontOpcode; michael@0: using BytecodeRange::frontOffset; michael@0: michael@0: BytecodeRangeWithPosition(JSContext *cx, JSScript *script) michael@0: : BytecodeRange(cx, script), lineno(script->lineno()), column(0), michael@0: sn(script->notes()), snpc(script->code()) michael@0: { michael@0: if (!SN_IS_TERMINATOR(sn)) michael@0: snpc += SN_DELTA(sn); michael@0: updatePosition(); michael@0: while (frontPC() != script->main()) michael@0: popFront(); michael@0: } michael@0: michael@0: void popFront() { michael@0: BytecodeRange::popFront(); michael@0: if (!empty()) michael@0: updatePosition(); michael@0: } michael@0: michael@0: size_t frontLineNumber() const { return lineno; } michael@0: size_t frontColumnNumber() const { return column; } michael@0: michael@0: private: michael@0: void updatePosition() { michael@0: /* michael@0: * Determine the current line number by reading all source notes up to michael@0: * and including the current offset. michael@0: */ michael@0: while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) { michael@0: SrcNoteType type = (SrcNoteType) SN_TYPE(sn); michael@0: if (type == SRC_COLSPAN) { michael@0: ptrdiff_t colspan = js_GetSrcNoteOffset(sn, 0); michael@0: michael@0: if (colspan >= SN_COLSPAN_DOMAIN / 2) michael@0: colspan -= SN_COLSPAN_DOMAIN; michael@0: JS_ASSERT(ptrdiff_t(column) + colspan >= 0); michael@0: column += colspan; michael@0: } if (type == SRC_SETLINE) { michael@0: lineno = size_t(js_GetSrcNoteOffset(sn, 0)); michael@0: column = 0; michael@0: } else if (type == SRC_NEWLINE) { michael@0: lineno++; michael@0: column = 0; michael@0: } michael@0: michael@0: sn = SN_NEXT(sn); michael@0: snpc += SN_DELTA(sn); michael@0: } michael@0: } michael@0: michael@0: size_t lineno; michael@0: size_t column; michael@0: jssrcnote *sn; michael@0: jsbytecode *snpc; michael@0: }; michael@0: michael@0: /* michael@0: * FlowGraphSummary::populate(cx, script) computes a summary of script's michael@0: * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}. michael@0: * michael@0: * An instruction on a given line is an entry point for that line if it can be michael@0: * reached from (an instruction on) a different line. We distinguish between the michael@0: * following cases: michael@0: * - hasNoEdges: michael@0: * The instruction cannot be reached, so the instruction is not an entry michael@0: * point for the line it is on. michael@0: * - hasSingleEdge: michael@0: * - hasMultipleEdgesFromSingleLine: michael@0: * The instruction can be reached from a single line. If this line is michael@0: * different from the line the instruction is on, the instruction is an michael@0: * entry point for that line. michael@0: * - hasMultipleEdgesFromMultipleLines: michael@0: * The instruction can be reached from multiple lines. At least one of michael@0: * these lines is guaranteed to be different from the line the instruction michael@0: * is on, so the instruction is an entry point for that line. michael@0: * michael@0: * Similarly, an instruction on a given position (line/column pair) is an michael@0: * entry point for that position if it can be reached from (an instruction on) a michael@0: * different position. Again, we distinguish between the following cases: michael@0: * - hasNoEdges: michael@0: * The instruction cannot be reached, so the instruction is not an entry michael@0: * point for the position it is on. michael@0: * - hasSingleEdge: michael@0: * The instruction can be reached from a single position. If this line is michael@0: * different from the position the instruction is on, the instruction is michael@0: * an entry point for that position. michael@0: * - hasMultipleEdgesFromSingleLine: michael@0: * - hasMultipleEdgesFromMultipleLines: michael@0: * The instruction can be reached from multiple positions. At least one michael@0: * of these positions is guaranteed to be different from the position the michael@0: * instruction is on, so the instruction is an entry point for that michael@0: * position. michael@0: */ michael@0: class FlowGraphSummary { michael@0: public: michael@0: class Entry { michael@0: public: michael@0: static Entry createWithNoEdges() { michael@0: return Entry(SIZE_MAX, 0); michael@0: } michael@0: michael@0: static Entry createWithSingleEdge(size_t lineno, size_t column) { michael@0: return Entry(lineno, column); michael@0: } michael@0: michael@0: static Entry createWithMultipleEdgesFromSingleLine(size_t lineno) { michael@0: return Entry(lineno, SIZE_MAX); michael@0: } michael@0: michael@0: static Entry createWithMultipleEdgesFromMultipleLines() { michael@0: return Entry(SIZE_MAX, SIZE_MAX); michael@0: } michael@0: michael@0: Entry() {} michael@0: michael@0: bool hasNoEdges() const { michael@0: return lineno_ == SIZE_MAX && column_ != SIZE_MAX; michael@0: } michael@0: michael@0: bool hasSingleEdge() const { michael@0: return lineno_ != SIZE_MAX && column_ != SIZE_MAX; michael@0: } michael@0: michael@0: bool hasMultipleEdgesFromSingleLine() const { michael@0: return lineno_ != SIZE_MAX && column_ == SIZE_MAX; michael@0: } michael@0: michael@0: bool hasMultipleEdgesFromMultipleLines() const { michael@0: return lineno_ == SIZE_MAX && column_ == SIZE_MAX; michael@0: } michael@0: michael@0: bool operator==(const Entry &other) const { michael@0: return lineno_ == other.lineno_ && column_ == other.column_; michael@0: } michael@0: michael@0: bool operator!=(const Entry &other) const { michael@0: return lineno_ != other.lineno_ || column_ != other.column_; michael@0: } michael@0: michael@0: size_t lineno() const { michael@0: return lineno_; michael@0: } michael@0: michael@0: size_t column() const { michael@0: return column_; michael@0: } michael@0: michael@0: private: michael@0: Entry(size_t lineno, size_t column) : lineno_(lineno), column_(column) {} michael@0: michael@0: size_t lineno_; michael@0: size_t column_; michael@0: }; michael@0: michael@0: FlowGraphSummary(JSContext *cx) : entries_(cx) {} michael@0: michael@0: Entry &operator[](size_t index) { michael@0: return entries_[index]; michael@0: } michael@0: michael@0: bool populate(JSContext *cx, JSScript *script) { michael@0: if (!entries_.growBy(script->length())) michael@0: return false; michael@0: unsigned mainOffset = script->pcToOffset(script->main()); michael@0: entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines(); michael@0: for (size_t i = mainOffset + 1; i < script->length(); i++) michael@0: entries_[i] = Entry::createWithNoEdges(); michael@0: michael@0: size_t prevLineno = script->lineno(); michael@0: size_t prevColumn = 0; michael@0: JSOp prevOp = JSOP_NOP; michael@0: for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { michael@0: size_t lineno = r.frontLineNumber(); michael@0: size_t column = r.frontColumnNumber(); michael@0: JSOp op = r.frontOpcode(); michael@0: michael@0: if (FlowsIntoNext(prevOp)) michael@0: addEdge(prevLineno, prevColumn, r.frontOffset()); michael@0: michael@0: if (js_CodeSpec[op].type() == JOF_JUMP) { michael@0: addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC())); michael@0: } else if (op == JSOP_TABLESWITCH) { michael@0: jsbytecode *pc = r.frontPC(); michael@0: size_t offset = r.frontOffset(); michael@0: ptrdiff_t step = JUMP_OFFSET_LEN; michael@0: size_t defaultOffset = offset + GET_JUMP_OFFSET(pc); michael@0: pc += step; michael@0: addEdge(lineno, column, defaultOffset); michael@0: michael@0: int32_t low = GET_JUMP_OFFSET(pc); michael@0: pc += JUMP_OFFSET_LEN; michael@0: int ncases = GET_JUMP_OFFSET(pc) - low + 1; michael@0: pc += JUMP_OFFSET_LEN; michael@0: michael@0: for (int i = 0; i < ncases; i++) { michael@0: size_t target = offset + GET_JUMP_OFFSET(pc); michael@0: addEdge(lineno, column, target); michael@0: pc += step; michael@0: } michael@0: } michael@0: michael@0: prevLineno = lineno; michael@0: prevColumn = column; michael@0: prevOp = op; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: private: michael@0: void addEdge(size_t sourceLineno, size_t sourceColumn, size_t targetOffset) { michael@0: if (entries_[targetOffset].hasNoEdges()) michael@0: entries_[targetOffset] = Entry::createWithSingleEdge(sourceLineno, sourceColumn); michael@0: else if (entries_[targetOffset].lineno() != sourceLineno) michael@0: entries_[targetOffset] = Entry::createWithMultipleEdgesFromMultipleLines(); michael@0: else if (entries_[targetOffset].column() != sourceColumn) michael@0: entries_[targetOffset] = Entry::createWithMultipleEdgesFromSingleLine(sourceLineno); michael@0: } michael@0: michael@0: Vector entries_; michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: static bool michael@0: DebuggerScript_getAllOffsets(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script); michael@0: michael@0: /* michael@0: * First pass: determine which offsets in this script are jump targets and michael@0: * which line numbers jump to them. michael@0: */ michael@0: FlowGraphSummary flowData(cx); michael@0: if (!flowData.populate(cx, script)) michael@0: return false; michael@0: michael@0: /* Second pass: build the result array. */ michael@0: RootedObject result(cx, NewDenseEmptyArray(cx)); michael@0: if (!result) michael@0: return false; michael@0: for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { michael@0: size_t offset = r.frontOffset(); michael@0: size_t lineno = r.frontLineNumber(); michael@0: michael@0: /* Make a note, if the current instruction is an entry point for the current line. */ michael@0: if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) { michael@0: /* Get the offsets array for this line. */ michael@0: RootedObject offsets(cx); michael@0: RootedValue offsetsv(cx); michael@0: michael@0: RootedId id(cx, INT_TO_JSID(lineno)); michael@0: michael@0: bool found; michael@0: if (!js::HasOwnProperty(cx, result, id, &found)) michael@0: return false; michael@0: if (found && !JSObject::getGeneric(cx, result, result, id, &offsetsv)) michael@0: return false; michael@0: michael@0: if (offsetsv.isObject()) { michael@0: offsets = &offsetsv.toObject(); michael@0: } else { michael@0: JS_ASSERT(offsetsv.isUndefined()); michael@0: michael@0: /* michael@0: * Create an empty offsets array for this line. michael@0: * Store it in the result array. michael@0: */ michael@0: RootedId id(cx); michael@0: RootedValue v(cx, NumberValue(lineno)); michael@0: offsets = NewDenseEmptyArray(cx); michael@0: if (!offsets || michael@0: !ValueToId(cx, v, &id)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: RootedValue value(cx, ObjectValue(*offsets)); michael@0: if (!JSObject::defineGeneric(cx, result, id, value)) michael@0: return false; michael@0: } michael@0: michael@0: /* Append the current offset to the offsets array. */ michael@0: if (!NewbornArrayPush(cx, offsets, NumberValue(offset))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getAllColumnOffsets(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllColumnOffsets", args, obj, script); michael@0: michael@0: /* michael@0: * First pass: determine which offsets in this script are jump targets and michael@0: * which positions jump to them. michael@0: */ michael@0: FlowGraphSummary flowData(cx); michael@0: if (!flowData.populate(cx, script)) michael@0: return false; michael@0: michael@0: /* Second pass: build the result array. */ michael@0: RootedObject result(cx, NewDenseEmptyArray(cx)); michael@0: if (!result) michael@0: return false; michael@0: for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { michael@0: size_t lineno = r.frontLineNumber(); michael@0: size_t column = r.frontColumnNumber(); michael@0: size_t offset = r.frontOffset(); michael@0: michael@0: /* Make a note, if the current instruction is an entry point for the current position. */ michael@0: if (!flowData[offset].hasNoEdges() && michael@0: (flowData[offset].lineno() != lineno || michael@0: flowData[offset].column() != column)) { michael@0: RootedObject entry(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!entry) michael@0: return false; michael@0: michael@0: RootedId id(cx, NameToId(cx->names().lineNumber)); michael@0: RootedValue value(cx, NumberValue(lineno)); michael@0: if (!JSObject::defineGeneric(cx, entry, id, value)) michael@0: return false; michael@0: michael@0: value = NumberValue(column); michael@0: if (!JSObject::defineProperty(cx, entry, cx->names().columnNumber, value)) michael@0: return false; michael@0: michael@0: id = NameToId(cx->names().offset); michael@0: value = NumberValue(offset); michael@0: if (!JSObject::defineGeneric(cx, entry, id, value)) michael@0: return false; michael@0: michael@0: if (!NewbornArrayPush(cx, result, ObjectValue(*entry))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getLineOffsets(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script); michael@0: REQUIRE_ARGC("Debugger.Script.getLineOffsets", 1); michael@0: michael@0: /* Parse lineno argument. */ michael@0: RootedValue linenoValue(cx, args[0]); michael@0: size_t lineno; michael@0: if (!ToNumber(cx, &linenoValue)) michael@0: return false; michael@0: { michael@0: double d = linenoValue.toNumber(); michael@0: lineno = size_t(d); michael@0: if (lineno != d) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * First pass: determine which offsets in this script are jump targets and michael@0: * which line numbers jump to them. michael@0: */ michael@0: FlowGraphSummary flowData(cx); michael@0: if (!flowData.populate(cx, script)) michael@0: return false; michael@0: michael@0: /* Second pass: build the result array. */ michael@0: RootedObject result(cx, NewDenseEmptyArray(cx)); michael@0: if (!result) michael@0: return false; michael@0: for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { michael@0: size_t offset = r.frontOffset(); michael@0: michael@0: /* If the op at offset is an entry point, append offset to result. */ michael@0: if (r.frontLineNumber() == lineno && michael@0: !flowData[offset].hasNoEdges() && michael@0: flowData[offset].lineno() != lineno) michael@0: { michael@0: if (!NewbornArrayPush(cx, result, NumberValue(offset))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesFrame(AbstractFramePtr frame) const michael@0: { michael@0: return observesScript(frame.script()); michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesFrame(const ScriptFrameIter &iter) const michael@0: { michael@0: return observesScript(iter.script()); michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesScript(JSScript *script) const michael@0: { michael@0: if (!enabled) michael@0: return false; michael@0: return observesGlobal(&script->global()) && (!script->selfHosted() || michael@0: SelfHostedFramesVisible()); michael@0: } michael@0: michael@0: /* static */ bool michael@0: Debugger::replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to, michael@0: ScriptFrameIter &iter) michael@0: { michael@0: for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) { michael@0: RootedObject frameobj(cx, r.frontFrame()); michael@0: Debugger *dbg = r.frontDebugger(); michael@0: JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj)); michael@0: michael@0: // Update frame object's ScriptFrameIter::data pointer. michael@0: DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj); michael@0: ScriptFrameIter::Data *data = iter.copyData(); michael@0: if (!data) michael@0: return false; michael@0: frameobj->setPrivate(data); michael@0: michael@0: // Remove the old entry before mutating the HashMap. michael@0: r.removeFrontFrame(); michael@0: michael@0: // Add the frame object with |to| as key. michael@0: if (!dbg->frames.putNew(to, frameobj)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to) michael@0: { michael@0: ScriptFrameIter iter(cx); michael@0: JS_ASSERT(iter.abstractFramePtr() == to); michael@0: return replaceFrameGuts(cx, from, to, iter); michael@0: } michael@0: michael@0: /* static */ bool michael@0: Debugger::handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to) michael@0: { michael@0: // When we return to a bailed-out Ion real frame, we must update all michael@0: // Debugger.Frames that refer to its inline frames. However, since we michael@0: // can't pop individual inline frames off the stack (we can only pop the michael@0: // real frame that contains them all, as a unit), we cannot assume that michael@0: // the frame we're dealing with is the top frame. Advance the iterator michael@0: // across any inlined frames younger than |to|, the baseline frame michael@0: // reconstructed during bailout from the Ion frame corresponding to michael@0: // |from|. michael@0: ScriptFrameIter iter(cx); michael@0: while (iter.abstractFramePtr() != to) michael@0: ++iter; michael@0: return replaceFrameGuts(cx, from, to, iter); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2); michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: michael@0: if (!dbg->observesScript(script)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING); michael@0: return false; michael@0: } michael@0: michael@0: size_t offset; michael@0: if (!ScriptOffset(cx, script, args[0], &offset)) michael@0: return false; michael@0: michael@0: JSObject *handler = NonNullObject(cx, args[1]); michael@0: if (!handler) michael@0: return false; michael@0: michael@0: jsbytecode *pc = script->offsetToPC(offset); michael@0: BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc); michael@0: if (!site) michael@0: return false; michael@0: site->inc(cx->runtime()->defaultFreeOp()); michael@0: if (cx->runtime()->new_(dbg, site, handler)) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: site->dec(cx->runtime()->defaultFreeOp()); michael@0: site->destroyIfEmpty(cx->runtime()->defaultFreeOp()); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_getBreakpoints(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: michael@0: jsbytecode *pc; michael@0: if (args.length() > 0) { michael@0: size_t offset; michael@0: if (!ScriptOffset(cx, script, args[0], &offset)) michael@0: return false; michael@0: pc = script->offsetToPC(offset); michael@0: } else { michael@0: pc = nullptr; michael@0: } michael@0: michael@0: RootedObject arr(cx, NewDenseEmptyArray(cx)); michael@0: if (!arr) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < script->length(); i++) { michael@0: BreakpointSite *site = script->getBreakpointSite(script->offsetToPC(i)); michael@0: if (site && (!pc || site->pc == pc)) { michael@0: for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) { michael@0: if (bp->debugger == dbg && michael@0: !NewbornArrayPush(cx, arr, ObjectValue(*bp->getHandler()))) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: args.rval().setObject(*arr); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_clearBreakpoint(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1); michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: michael@0: JSObject *handler = NonNullObject(cx, args[0]); michael@0: if (!handler) michael@0: return false; michael@0: michael@0: script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, handler); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script); michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); michael@0: script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, nullptr); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_isInCatchScope(JSContext *cx, unsigned argc, Value* vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Script.isInCatchScope", 1); michael@0: THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "isInCatchScope", args, obj, script); michael@0: michael@0: size_t offset; michael@0: if (!ScriptOffset(cx, script, args[0], &offset)) michael@0: return false; michael@0: michael@0: /* michael@0: * Try note ranges are relative to the mainOffset of the script, so adjust michael@0: * offset accordingly. michael@0: */ michael@0: offset -= script->mainOffset(); michael@0: michael@0: args.rval().setBoolean(false); michael@0: if (script->hasTrynotes()) { michael@0: JSTryNote* tnBegin = script->trynotes()->vector; michael@0: JSTryNote* tnEnd = tnBegin + script->trynotes()->length; michael@0: while (tnBegin != tnEnd) { michael@0: if (tnBegin->start <= offset && michael@0: offset <= tnBegin->start + tnBegin->length && michael@0: tnBegin->kind == JSTRY_CATCH) michael@0: { michael@0: args.rval().setBoolean(true); michael@0: break; michael@0: } michael@0: ++tnBegin; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerScript_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, michael@0: "Debugger.Script"); michael@0: return false; michael@0: } michael@0: michael@0: static const JSPropertySpec DebuggerScript_properties[] = { michael@0: JS_PSG("url", DebuggerScript_getUrl, 0), michael@0: JS_PSG("startLine", DebuggerScript_getStartLine, 0), michael@0: JS_PSG("lineCount", DebuggerScript_getLineCount, 0), michael@0: JS_PSG("source", DebuggerScript_getSource, 0), michael@0: JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0), michael@0: JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0), michael@0: JS_PSG("staticLevel", DebuggerScript_getStaticLevel, 0), michael@0: JS_PSG("sourceMapURL", DebuggerScript_getSourceMapUrl, 0), michael@0: JS_PSG("global", DebuggerScript_getGlobal, 0), michael@0: JS_PS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec DebuggerScript_methods[] = { michael@0: JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0), michael@0: JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0), michael@0: JS_FN("getAllColumnOffsets", DebuggerScript_getAllColumnOffsets, 0, 0), michael@0: JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0), michael@0: JS_FN("getOffsetLine", DebuggerScript_getOffsetLine, 0, 0), michael@0: JS_FN("setBreakpoint", DebuggerScript_setBreakpoint, 2, 0), michael@0: JS_FN("getBreakpoints", DebuggerScript_getBreakpoints, 1, 0), michael@0: JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0), michael@0: JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0), michael@0: JS_FN("isInCatchScope", DebuggerScript_isInCatchScope, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: /*** Debugger.Source *****************************************************************************/ michael@0: michael@0: static inline ScriptSourceObject * michael@0: GetSourceReferent(JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->getClass() == &DebuggerSource_class); michael@0: return static_cast(obj->getPrivate()); michael@0: } michael@0: michael@0: static void michael@0: DebuggerSource_trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: /* michael@0: * There is a barrier on private pointers, so the Unbarriered marking michael@0: * is okay. michael@0: */ michael@0: if (JSObject *referent = GetSourceReferent(obj)) { michael@0: MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Source referent"); michael@0: obj->setPrivateUnbarriered(referent); michael@0: } michael@0: } michael@0: michael@0: const Class DebuggerSource_class = { michael@0: "Source", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: DebuggerSource_trace michael@0: }; michael@0: michael@0: JSObject * michael@0: Debugger::newDebuggerSource(JSContext *cx, HandleScriptSource source) michael@0: { michael@0: assertSameCompartment(cx, object.get()); michael@0: michael@0: JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject(); michael@0: JS_ASSERT(proto); michael@0: JSObject *sourceobj = NewObjectWithGivenProto(cx, &DebuggerSource_class, proto, nullptr, TenuredObject); michael@0: if (!sourceobj) michael@0: return nullptr; michael@0: sourceobj->setReservedSlot(JSSLOT_DEBUGSOURCE_OWNER, ObjectValue(*object)); michael@0: sourceobj->setPrivateGCThing(source); michael@0: michael@0: return sourceobj; michael@0: } michael@0: michael@0: JSObject * michael@0: Debugger::wrapSource(JSContext *cx, HandleScriptSource source) michael@0: { michael@0: assertSameCompartment(cx, object.get()); michael@0: JS_ASSERT(cx->compartment() != source->compartment()); michael@0: DependentAddPtr p(cx, sources, source); michael@0: if (!p) { michael@0: JSObject *sourceobj = newDebuggerSource(cx, source); michael@0: if (!sourceobj) michael@0: return nullptr; michael@0: michael@0: if (!p.add(cx, sources, source, sourceobj)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: michael@0: CrossCompartmentKey key(CrossCompartmentKey::DebuggerSource, object, source); michael@0: if (!object->compartment()->putWrapper(cx, key, ObjectValue(*sourceobj))) { michael@0: sources.remove(source); michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(GetSourceReferent(p->value()) == source); michael@0: return p->value(); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, michael@0: "Debugger.Source"); michael@0: return false; michael@0: } michael@0: michael@0: static JSObject * michael@0: DebuggerSource_checkThis(JSContext *cx, const CallArgs &args, const char *fnname) michael@0: { michael@0: if (!args.thisv().isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSObject *thisobj = &args.thisv().toObject(); michael@0: if (thisobj->getClass() != &DebuggerSource_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Source", fnname, thisobj->getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!GetSourceReferent(thisobj)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Frame", fnname, "prototype object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: return thisobj; michael@0: } michael@0: michael@0: #define THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, fnname, args, obj, sourceObject) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: RootedObject obj(cx, DebuggerSource_checkThis(cx, args, fnname)); \ michael@0: if (!obj) \ michael@0: return false; \ michael@0: RootedScriptSource sourceObject(cx, GetSourceReferent(obj)); \ michael@0: if (!sourceObject) \ michael@0: return false; michael@0: michael@0: static bool michael@0: DebuggerSource_getText(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get text)", args, obj, sourceObject); michael@0: michael@0: ScriptSource *ss = sourceObject->source(); michael@0: bool hasSourceData = ss->hasSourceData(); michael@0: if (!ss->hasSourceData() && !JSScript::loadSource(cx, ss, &hasSourceData)) michael@0: return false; michael@0: michael@0: JSString *str = hasSourceData ? ss->substring(cx, 0, ss->length()) michael@0: : js_NewStringCopyZ(cx, "[no source]"); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getUrl(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject); michael@0: michael@0: ScriptSource *ss = sourceObject->source(); michael@0: if (ss->filename()) { michael@0: JSString *str = js_NewStringCopyZ(cx, ss->filename()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: } else { michael@0: args.rval().setNull(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getDisplayURL(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject); michael@0: michael@0: ScriptSource *ss = sourceObject->source(); michael@0: JS_ASSERT(ss); michael@0: michael@0: if (ss->hasDisplayURL()) { michael@0: JSString *str = JS_NewUCStringCopyZ(cx, ss->displayURL()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: } else { michael@0: args.rval().setNull(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getElement(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get element)", args, obj, sourceObject); michael@0: michael@0: if (sourceObject->element()) { michael@0: args.rval().setObjectOrNull(sourceObject->element()); michael@0: if (!Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval())) michael@0: return false; michael@0: } else { michael@0: args.rval().setUndefined(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getElementProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get elementAttributeName)", args, obj, sourceObject); michael@0: args.rval().set(sourceObject->elementAttributeName()); michael@0: return Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getIntroductionScript(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionScript)", args, obj, sourceObject); michael@0: michael@0: RootedScript script(cx, sourceObject->introductionScript()); michael@0: if (script) { michael@0: RootedObject scriptDO(cx, Debugger::fromChildJSObject(obj)->wrapScript(cx, script)); michael@0: if (!scriptDO) michael@0: return false; michael@0: args.rval().setObject(*scriptDO); michael@0: } else { michael@0: args.rval().setUndefined(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getIntroductionOffset(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionOffset)", args, obj, sourceObject); michael@0: michael@0: // Regardless of what's recorded in the ScriptSourceObject and michael@0: // ScriptSource, only hand out the introduction offset if we also have michael@0: // the script within which it applies. michael@0: ScriptSource *ss = sourceObject->source(); michael@0: if (ss->hasIntroductionOffset() && sourceObject->introductionScript()) michael@0: args.rval().setInt32(ss->introductionOffset()); michael@0: else michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerSource_getIntroductionType(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionType)", args, obj, sourceObject); michael@0: michael@0: ScriptSource *ss = sourceObject->source(); michael@0: if (ss->hasIntroductionType()) { michael@0: JSString *str = js_NewStringCopyZ(cx, ss->introductionType()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: } else { michael@0: args.rval().setUndefined(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static const JSPropertySpec DebuggerSource_properties[] = { michael@0: JS_PSG("text", DebuggerSource_getText, 0), michael@0: JS_PSG("url", DebuggerSource_getUrl, 0), michael@0: JS_PSG("element", DebuggerSource_getElement, 0), michael@0: JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0), michael@0: JS_PSG("introductionScript", DebuggerSource_getIntroductionScript, 0), michael@0: JS_PSG("introductionOffset", DebuggerSource_getIntroductionOffset, 0), michael@0: JS_PSG("introductionType", DebuggerSource_getIntroductionType, 0), michael@0: JS_PSG("elementAttributeName", DebuggerSource_getElementProperty, 0), michael@0: JS_PS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec DebuggerSource_methods[] = { michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: /*** Debugger.Frame ******************************************************************************/ michael@0: michael@0: static void michael@0: UpdateFrameIterPc(FrameIter &iter) michael@0: { michael@0: if (iter.abstractFramePtr().isRematerializedFrame()) { michael@0: #ifdef DEBUG michael@0: // Rematerialized frames don't need their pc updated. The reason we michael@0: // need to update pc is because we might get the same Debugger.Frame michael@0: // object for multiple re-entries into debugger code from debuggee michael@0: // code. This reentrancy is not possible with rematerialized frames, michael@0: // because when returning to debuggee code, we would have bailed out michael@0: // to baseline. michael@0: // michael@0: // We walk the stack to assert that it doesn't need updating. michael@0: jit::RematerializedFrame *frame = iter.abstractFramePtr().asRematerializedFrame(); michael@0: jit::IonJSFrameLayout *jsFrame = (jit::IonJSFrameLayout *)frame->top(); michael@0: jit::JitActivation *activation = iter.activation()->asJit(); michael@0: michael@0: ActivationIterator activationIter(activation->cx()->runtime()); michael@0: while (activationIter.activation() != activation) michael@0: ++activationIter; michael@0: michael@0: jit::JitFrameIterator jitIter(activationIter); michael@0: while (!jitIter.isIonJS() || jitIter.jsFrame() != jsFrame) michael@0: ++jitIter; michael@0: michael@0: jit::InlineFrameIterator ionInlineIter(activation->cx(), &jitIter); michael@0: while (ionInlineIter.frameNo() != frame->frameNo()) michael@0: ++ionInlineIter; michael@0: michael@0: MOZ_ASSERT(ionInlineIter.pc() == iter.pc()); michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: iter.updatePcQuadratic(); michael@0: } michael@0: michael@0: static void michael@0: DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj) michael@0: { michael@0: AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->getPrivate()); michael@0: if (frame.isScriptFrameIterData()) michael@0: fop->delete_((ScriptFrameIter::Data *) frame.raw()); michael@0: obj->setPrivate(nullptr); michael@0: } michael@0: michael@0: static void michael@0: DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame, michael@0: JSObject *frameobj) michael@0: { michael@0: /* If this frame has an onStep handler, decrement the script's count. */ michael@0: if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined()) michael@0: frame.script()->decrementStepModeCount(fop); michael@0: } michael@0: michael@0: static void michael@0: DebuggerFrame_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: DebuggerFrame_freeScriptFrameIterData(fop, obj); michael@0: } michael@0: michael@0: const Class DebuggerFrame_class = { michael@0: "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, DebuggerFrame_finalize michael@0: }; michael@0: michael@0: static JSObject * michael@0: CheckThisFrame(JSContext *cx, const CallArgs &args, const char *fnname, bool checkLive) michael@0: { michael@0: if (!args.thisv().isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return nullptr; michael@0: } michael@0: JSObject *thisobj = &args.thisv().toObject(); michael@0: if (thisobj->getClass() != &DebuggerFrame_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Frame", fnname, thisobj->getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Forbid Debugger.Frame.prototype, which is of class DebuggerFrame_class michael@0: * but isn't really a working Debugger.Frame object. The prototype object michael@0: * is distinguished by having a nullptr private value. Also, forbid popped michael@0: * frames. michael@0: */ michael@0: if (!thisobj->getPrivate()) { michael@0: if (thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Frame", fnname, "prototype object"); michael@0: return nullptr; michael@0: } michael@0: if (checkLive) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, michael@0: "Debugger.Frame"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: return thisobj; michael@0: } michael@0: michael@0: /* michael@0: * To make frequently fired hooks like onEnterFrame more performant, michael@0: * Debugger.Frame methods should not create a ScriptFrameIter unless it michael@0: * absolutely needs to. That is, unless the method has to call a method on michael@0: * ScriptFrameIter that's otherwise not available on AbstractFramePtr. michael@0: * michael@0: * When a Debugger.Frame is first created, its private slot is set to the michael@0: * AbstractFramePtr itself. The first time the users asks for a michael@0: * ScriptFrameIter, we construct one, have it settle on the frame pointed to michael@0: * by the AbstractFramePtr and cache its internal Data in the Debugger.Frame michael@0: * object's private slot. Subsequent uses of the Debugger.Frame object will michael@0: * always create a ScriptFrameIter from the cached Data. michael@0: * michael@0: * Methods that only need the AbstractFramePtr should use THIS_FRAME. michael@0: * Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER. michael@0: */ michael@0: michael@0: #define THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: RootedObject thisobj(cx, CheckThisFrame(cx, args, fnname, true)); \ michael@0: if (!thisobj) \ michael@0: return false michael@0: michael@0: #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame) \ michael@0: THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \ michael@0: AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \ michael@0: if (frame.isScriptFrameIterData()) { \ michael@0: ScriptFrameIter iter(*(ScriptFrameIter::Data *)(frame.raw())); \ michael@0: frame = iter.abstractFramePtr(); \ michael@0: } michael@0: michael@0: #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter) \ michael@0: THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \ michael@0: Maybe maybeIter; \ michael@0: { \ michael@0: AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \ michael@0: if (f.isScriptFrameIterData()) { \ michael@0: maybeIter.construct(*(ScriptFrameIter::Data *)(f.raw())); \ michael@0: } else { \ michael@0: maybeIter.construct(cx, ScriptFrameIter::ALL_CONTEXTS, \ michael@0: ScriptFrameIter::GO_THROUGH_SAVED); \ michael@0: ScriptFrameIter &iter = maybeIter.ref(); \ michael@0: while (iter.isIon() || iter.abstractFramePtr() != f) \ michael@0: ++iter; \ michael@0: AbstractFramePtr data = iter.copyDataAsAbstractFramePtr(); \ michael@0: if (!data) \ michael@0: return false; \ michael@0: thisobj->setPrivate(data.raw()); \ michael@0: } \ michael@0: } \ michael@0: ScriptFrameIter &iter = maybeIter.ref() michael@0: michael@0: #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg) \ michael@0: THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame); \ michael@0: Debugger *dbg = Debugger::fromChildJSObject(thisobj) michael@0: michael@0: #define THIS_FRAME_OWNER_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter, dbg) \ michael@0: THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter); \ michael@0: Debugger *dbg = Debugger::fromChildJSObject(thisobj) michael@0: michael@0: static bool michael@0: DebuggerFrame_getType(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get type", args, thisobj, frame); michael@0: michael@0: /* michael@0: * Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the michael@0: * order of checks here is significant. michael@0: */ michael@0: args.rval().setString(frame.isEvalFrame() michael@0: ? cx->names().eval michael@0: : frame.isGlobalFrame() michael@0: ? cx->names().global michael@0: : cx->names().call); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getImplementation(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get implementation", args, thisobj, frame); michael@0: michael@0: const char *s; michael@0: if (frame.isBaselineFrame()) michael@0: s = "baseline"; michael@0: else if (frame.isRematerializedFrame()) michael@0: s = "ion"; michael@0: else michael@0: s = "interpreter"; michael@0: michael@0: JSAtom *str = Atomize(cx, s, strlen(s)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg); michael@0: michael@0: Rooted env(cx); michael@0: { michael@0: AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain()); michael@0: UpdateFrameIterPc(iter); michael@0: env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc()); michael@0: if (!env) michael@0: return false; michael@0: } michael@0: michael@0: return dbg->wrapEnvironment(cx, env, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getCallee(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get callee", args, thisobj, frame); michael@0: RootedValue calleev(cx, frame.isNonEvalFunctionFrame() ? frame.calleev() : NullValue()); michael@0: if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &calleev)) michael@0: return false; michael@0: args.rval().set(calleev); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getGenerator(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get generator", args, thisobj, frame); michael@0: args.rval().setBoolean(frame.isGeneratorFrame()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getConstructing(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_ITER(cx, argc, vp, "get constructing", args, thisobj, _, iter); michael@0: args.rval().setBoolean(iter.isFunctionFrame() && iter.isConstructing()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getThis(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter); michael@0: RootedValue thisv(cx); michael@0: { michael@0: AutoCompartment ac(cx, iter.scopeChain()); michael@0: if (!iter.computeThis(cx)) michael@0: return false; michael@0: thisv = iter.computedThisValue(); michael@0: } michael@0: if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &thisv)) michael@0: return false; michael@0: args.rval().set(thisv); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getOlder(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter); michael@0: Debugger *dbg = Debugger::fromChildJSObject(thisobj); michael@0: michael@0: for (++iter; !iter.done(); ++iter) { michael@0: if (dbg->observesFrame(iter)) { michael@0: if (iter.isIon() && !iter.ensureHasRematerializedFrame()) michael@0: return false; michael@0: return dbg->getScriptFrame(cx, iter, args.rval()); michael@0: } michael@0: } michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: const Class DebuggerArguments_class = { michael@0: "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub michael@0: }; michael@0: michael@0: /* The getter used for each element of frame.arguments. See DebuggerFrame_getArguments. */ michael@0: static bool michael@0: DebuggerArguments_getArg(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: int32_t i = args.callee().as().getExtendedSlot(0).toInt32(); michael@0: michael@0: /* Check that the this value is an Arguments object. */ michael@0: if (!args.thisv().isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return false; michael@0: } michael@0: RootedObject argsobj(cx, &args.thisv().toObject()); michael@0: if (argsobj->getClass() != &DebuggerArguments_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Arguments", "getArgument", argsobj->getClass()->name); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Put the Debugger.Frame into the this-value slot, then use THIS_FRAME michael@0: * to check that it is still live and get the fp. michael@0: */ michael@0: args.setThis(argsobj->getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME)); michael@0: THIS_FRAME(cx, argc, vp, "get argument", ca2, thisobj, frame); michael@0: michael@0: /* michael@0: * Since getters can be extracted and applied to other objects, michael@0: * there is no guarantee this object has an ith argument. michael@0: */ michael@0: JS_ASSERT(i >= 0); michael@0: RootedValue arg(cx); michael@0: RootedScript script(cx); michael@0: if (unsigned(i) < frame.numActualArgs()) { michael@0: script = frame.script(); michael@0: if (unsigned(i) < frame.numFormalArgs() && script->formalIsAliased(i)) { michael@0: for (AliasedFormalIter fi(script); ; fi++) { michael@0: if (fi.frameIndex() == unsigned(i)) { michael@0: arg = frame.callObj().aliasedVar(fi); michael@0: break; michael@0: } michael@0: } michael@0: } else if (script->argsObjAliasesFormals() && frame.hasArgsObj()) { michael@0: arg = frame.argsObj().arg(i); michael@0: } else { michael@0: arg = frame.unaliasedActual(i, DONT_CHECK_ALIASING); michael@0: } michael@0: } else { michael@0: arg.setUndefined(); michael@0: } michael@0: michael@0: if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg)) michael@0: return false; michael@0: args.rval().set(arg); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getArguments(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get arguments", args, thisobj, frame); michael@0: Value argumentsv = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS); michael@0: if (!argumentsv.isUndefined()) { michael@0: JS_ASSERT(argumentsv.isObjectOrNull()); michael@0: args.rval().set(argumentsv); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject argsobj(cx); michael@0: if (frame.hasArgs()) { michael@0: /* Create an arguments object. */ michael@0: Rooted global(cx, &args.callee().global()); michael@0: JSObject *proto = GlobalObject::getOrCreateArrayPrototype(cx, global); michael@0: if (!proto) michael@0: return false; michael@0: argsobj = NewObjectWithGivenProto(cx, &DebuggerArguments_class, proto, global); michael@0: if (!argsobj) michael@0: return false; michael@0: SetReservedSlot(argsobj, JSSLOT_DEBUGARGUMENTS_FRAME, ObjectValue(*thisobj)); michael@0: michael@0: JS_ASSERT(frame.numActualArgs() <= 0x7fffffff); michael@0: unsigned fargc = frame.numActualArgs(); michael@0: RootedValue fargcVal(cx, Int32Value(fargc)); michael@0: if (!DefineNativeProperty(cx, argsobj, cx->names().length, michael@0: fargcVal, nullptr, nullptr, michael@0: JSPROP_PERMANENT | JSPROP_READONLY)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: Rooted id(cx); michael@0: for (unsigned i = 0; i < fargc; i++) { michael@0: RootedFunction getobj(cx); michael@0: getobj = NewFunction(cx, js::NullPtr(), DebuggerArguments_getArg, 0, michael@0: JSFunction::NATIVE_FUN, global, js::NullPtr(), michael@0: JSFunction::ExtendedFinalizeKind); michael@0: if (!getobj) michael@0: return false; michael@0: id = INT_TO_JSID(i); michael@0: if (!getobj || michael@0: !DefineNativeProperty(cx, argsobj, id, UndefinedHandleValue, michael@0: JS_DATA_TO_FUNC_PTR(PropertyOp, getobj.get()), nullptr, michael@0: JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER)) michael@0: { michael@0: return false; michael@0: } michael@0: getobj->setExtendedSlot(0, Int32Value(i)); michael@0: } michael@0: } else { michael@0: argsobj = nullptr; michael@0: } michael@0: args.rval().setObjectOrNull(argsobj); michael@0: thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, args.rval()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getScript(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get script", args, thisobj, frame); michael@0: Debugger *debug = Debugger::fromChildJSObject(thisobj); michael@0: michael@0: RootedObject scriptObject(cx); michael@0: if (frame.isFunctionFrame() && !frame.isEvalFrame()) { michael@0: RootedFunction callee(cx, frame.callee()); michael@0: if (callee->isInterpreted()) { michael@0: RootedScript script(cx, callee->nonLazyScript()); michael@0: scriptObject = debug->wrapScript(cx, script); michael@0: if (!scriptObject) michael@0: return false; michael@0: } michael@0: } else { michael@0: /* michael@0: * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script michael@0: * frames. michael@0: */ michael@0: RootedScript script(cx, frame.script()); michael@0: scriptObject = debug->wrapScript(cx, script); michael@0: if (!scriptObject) michael@0: return false; michael@0: } michael@0: args.rval().setObjectOrNull(scriptObject); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getOffset(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter); michael@0: JSScript *script = iter.script(); michael@0: UpdateFrameIterPc(iter); michael@0: jsbytecode *pc = iter.pc(); michael@0: size_t offset = script->pcToOffset(pc); michael@0: args.rval().setNumber(double(offset)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getLive(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSObject *thisobj = CheckThisFrame(cx, args, "get live", false); michael@0: if (!thisobj) michael@0: return false; michael@0: bool hasFrame = !!thisobj->getPrivate(); michael@0: args.rval().setBoolean(hasFrame); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsValidHook(const Value &v) michael@0: { michael@0: return v.isUndefined() || (v.isObject() && v.toObject().isCallable()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getOnStep(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, frame); michael@0: (void) frame; // Silence GCC warning michael@0: Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER); michael@0: JS_ASSERT(IsValidHook(handler)); michael@0: args.rval().set(handler); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_setOnStep(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Frame.set onStep", 1); michael@0: THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame); michael@0: if (!IsValidHook(args[0])) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED); michael@0: return false; michael@0: } michael@0: michael@0: Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER); michael@0: if (!args[0].isUndefined() && prior.isUndefined()) { michael@0: // Single stepping toggled off->on. michael@0: AutoCompartment ac(cx, frame.scopeChain()); michael@0: if (!frame.script()->incrementStepModeCount(cx)) michael@0: return false; michael@0: } else if (args[0].isUndefined() && !prior.isUndefined()) { michael@0: // Single stepping toggled on->off. michael@0: frame.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp()); michael@0: } michael@0: michael@0: /* Now that the step mode switch has succeeded, we can install the handler. */ michael@0: thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_getOnPop(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME(cx, argc, vp, "get onPop", args, thisobj, frame); michael@0: (void) frame; // Silence GCC warning michael@0: Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER); michael@0: JS_ASSERT(IsValidHook(handler)); michael@0: args.rval().set(handler); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_setOnPop(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Frame.set onPop", 1); michael@0: THIS_FRAME(cx, argc, vp, "set onPop", args, thisobj, frame); michael@0: (void) frame; // Silence GCC warning michael@0: if (!IsValidHook(args[0])) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED); michael@0: return false; michael@0: } michael@0: michael@0: thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Evaluate |chars[0..length-1]| in the environment |env|, treating that michael@0: * source as appearing starting at |lineno| in |filename|. Store the return michael@0: * value in |*rval|. Use |thisv| as the 'this' value. michael@0: * michael@0: * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env| michael@0: * must be either |frame|'s DebugScopeObject, or some extension of that michael@0: * environment; either way, |frame|'s scope is where newly declared variables michael@0: * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|. michael@0: */ michael@0: bool michael@0: js::EvaluateInEnv(JSContext *cx, Handle env, HandleValue thisv, AbstractFramePtr frame, michael@0: ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno, michael@0: MutableHandleValue rval) michael@0: { michael@0: assertSameCompartment(cx, env, frame); michael@0: JS_ASSERT_IF(frame, thisv.get() == frame.thisValue()); michael@0: michael@0: JS_ASSERT(!IsPoisonedPtr(chars.get())); michael@0: michael@0: /* michael@0: * NB: This function breaks the assumption that the compiler can see all michael@0: * calls and properly compute a static level. In practice, any non-zero michael@0: * static level will suffice. michael@0: */ michael@0: CompileOptions options(cx); michael@0: options.setCompileAndGo(true) michael@0: .setForEval(true) michael@0: .setNoScriptRval(false) michael@0: .setFileAndLine(filename, lineno) michael@0: .setCanLazilyParse(false) michael@0: .setIntroductionType("debugger eval"); michael@0: RootedScript callerScript(cx, frame ? frame.script() : nullptr); michael@0: SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership); michael@0: RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript, michael@0: options, srcBuf, michael@0: /* source = */ nullptr, michael@0: /* staticLevel = */ frame ? 1 : 0)); michael@0: if (!script) michael@0: return false; michael@0: michael@0: script->setActiveEval(); michael@0: ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG; michael@0: return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address()); michael@0: } michael@0: michael@0: enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false }; michael@0: michael@0: static bool michael@0: DebuggerGenericEval(JSContext *cx, const char *fullMethodName, const Value &code, michael@0: EvalBindings evalWithBindings, HandleValue bindings, HandleValue options, michael@0: MutableHandleValue vp, Debugger *dbg, HandleObject scope, michael@0: ScriptFrameIter *iter) michael@0: { michael@0: /* Either we're specifying the frame, or a global. */ michael@0: JS_ASSERT_IF(iter, !scope); michael@0: JS_ASSERT_IF(!iter, scope && scope->is()); michael@0: michael@0: /* Check the first argument, the eval code string. */ michael@0: if (!code.isString()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, michael@0: fullMethodName, "string", InformalValueTypeName(code)); michael@0: return false; michael@0: } michael@0: Rooted flat(cx, code.toString()->ensureFlat(cx)); michael@0: if (!flat) michael@0: return false; michael@0: michael@0: /* michael@0: * Gather keys and values of bindings, if any. This must be done in the michael@0: * debugger compartment, since that is where any exceptions must be michael@0: * thrown. michael@0: */ michael@0: AutoIdVector keys(cx); michael@0: AutoValueVector values(cx); michael@0: if (evalWithBindings) { michael@0: RootedObject bindingsobj(cx, NonNullObject(cx, bindings)); michael@0: if (!bindingsobj || michael@0: !GetPropertyNames(cx, bindingsobj, JSITER_OWNONLY, &keys) || michael@0: !values.growBy(keys.length())) michael@0: { michael@0: return false; michael@0: } michael@0: for (size_t i = 0; i < keys.length(); i++) { michael@0: MutableHandleValue valp = values.handleAt(i); michael@0: if (!JSObject::getGeneric(cx, bindingsobj, bindingsobj, keys.handleAt(i), valp) || michael@0: !dbg->unwrapDebuggeeValue(cx, valp)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Set options from object if provided. */ michael@0: JSAutoByteString url_bytes; michael@0: char *url = nullptr; michael@0: unsigned lineNumber = 1; michael@0: michael@0: if (options.isObject()) { michael@0: RootedObject opts(cx, &options.toObject()); michael@0: RootedValue v(cx); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "url", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: RootedString url_str(cx, ToString(cx, v)); michael@0: if (!url_str) michael@0: return false; michael@0: url = url_bytes.encodeLatin1(cx, url_str); michael@0: if (!url) michael@0: return false; michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "lineNumber", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: uint32_t lineno; michael@0: if (!ToUint32(cx, v, &lineno)) michael@0: return false; michael@0: lineNumber = lineno; michael@0: } michael@0: } michael@0: michael@0: Maybe ac; michael@0: if (iter) michael@0: ac.construct(cx, iter->scopeChain()); michael@0: else michael@0: ac.construct(cx, scope); michael@0: michael@0: RootedValue thisv(cx); michael@0: Rooted env(cx); michael@0: if (iter) { michael@0: /* ExecuteInEnv requires 'fp' to have a computed 'this" value. */ michael@0: if (!iter->computeThis(cx)) michael@0: return false; michael@0: thisv = iter->computedThisValue(); michael@0: env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc()); michael@0: if (!env) michael@0: return false; michael@0: } else { michael@0: /* michael@0: * Use the global as 'this'. If the global is an inner object, it michael@0: * should have a thisObject hook that returns the appropriate outer michael@0: * object. michael@0: */ michael@0: JSObject *thisObj = JSObject::thisObject(cx, scope); michael@0: if (!thisObj) michael@0: return false; michael@0: thisv = ObjectValue(*thisObj); michael@0: env = scope; michael@0: } michael@0: michael@0: /* If evalWithBindings, create the inner environment. */ michael@0: if (evalWithBindings) { michael@0: /* TODO - This should probably be a Call object, like ES5 strict eval. */ michael@0: env = NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, env); michael@0: if (!env) michael@0: return false; michael@0: RootedId id(cx); michael@0: for (size_t i = 0; i < keys.length(); i++) { michael@0: id = keys[i]; michael@0: MutableHandleValue val = values.handleAt(i); michael@0: if (!cx->compartment()->wrap(cx, val) || michael@0: !DefineNativeProperty(cx, env, id, val, nullptr, nullptr, 0)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Run the code and produce the completion value. */ michael@0: RootedValue rval(cx); michael@0: JS::Anchor anchor(flat); michael@0: AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr(); michael@0: bool ok = EvaluateInEnv(cx, env, thisv, frame, michael@0: ConstTwoByteChars(flat->chars(), flat->length()), michael@0: flat->length(), url ? url : "debugger eval code", lineNumber, &rval); michael@0: return dbg->receiveCompletionValue(ac, ok, rval, vp); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter); michael@0: REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1); michael@0: Debugger *dbg = Debugger::fromChildJSObject(thisobj); michael@0: UpdateFrameIterPc(iter); michael@0: return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval", michael@0: args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue, michael@0: args.get(1), args.rval(), dbg, js::NullPtr(), &iter); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter); michael@0: REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2); michael@0: Debugger *dbg = Debugger::fromChildJSObject(thisobj); michael@0: UpdateFrameIterPc(iter); michael@0: return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings", michael@0: args[0], EvalHasExtraBindings, args[1], args.get(2), michael@0: args.rval(), dbg, js::NullPtr(), &iter); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerFrame_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, michael@0: "Debugger.Frame"); michael@0: return false; michael@0: } michael@0: michael@0: static const JSPropertySpec DebuggerFrame_properties[] = { michael@0: JS_PSG("arguments", DebuggerFrame_getArguments, 0), michael@0: JS_PSG("callee", DebuggerFrame_getCallee, 0), michael@0: JS_PSG("constructing", DebuggerFrame_getConstructing, 0), michael@0: JS_PSG("environment", DebuggerFrame_getEnvironment, 0), michael@0: JS_PSG("generator", DebuggerFrame_getGenerator, 0), michael@0: JS_PSG("live", DebuggerFrame_getLive, 0), michael@0: JS_PSG("offset", DebuggerFrame_getOffset, 0), michael@0: JS_PSG("older", DebuggerFrame_getOlder, 0), michael@0: JS_PSG("script", DebuggerFrame_getScript, 0), michael@0: JS_PSG("this", DebuggerFrame_getThis, 0), michael@0: JS_PSG("type", DebuggerFrame_getType, 0), michael@0: JS_PSG("implementation", DebuggerFrame_getImplementation, 0), michael@0: JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0), michael@0: JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0), michael@0: JS_PS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec DebuggerFrame_methods[] = { michael@0: JS_FN("eval", DebuggerFrame_eval, 1, 0), michael@0: JS_FN("evalWithBindings", DebuggerFrame_evalWithBindings, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: /*** Debugger.Object *****************************************************************************/ michael@0: michael@0: static void michael@0: DebuggerObject_trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: /* michael@0: * There is a barrier on private pointers, so the Unbarriered marking michael@0: * is okay. michael@0: */ michael@0: if (JSObject *referent = (JSObject *) obj->getPrivate()) { michael@0: MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Object referent"); michael@0: obj->setPrivateUnbarriered(referent); michael@0: } michael@0: } michael@0: michael@0: const Class DebuggerObject_class = { michael@0: "Object", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: DebuggerObject_trace michael@0: }; michael@0: michael@0: static JSObject * michael@0: DebuggerObject_checkThis(JSContext *cx, const CallArgs &args, const char *fnname) michael@0: { michael@0: if (!args.thisv().isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return nullptr; michael@0: } michael@0: JSObject *thisobj = &args.thisv().toObject(); michael@0: if (thisobj->getClass() != &DebuggerObject_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Object", fnname, thisobj->getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Forbid Debugger.Object.prototype, which is of class DebuggerObject_class michael@0: * but isn't a real working Debugger.Object. The prototype object is michael@0: * distinguished by having no referent. michael@0: */ michael@0: if (!thisobj->getPrivate()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Object", fnname, "prototype object"); michael@0: return nullptr; michael@0: } michael@0: return thisobj; michael@0: } michael@0: michael@0: #define THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname)); \ michael@0: if (!obj) \ michael@0: return false; \ michael@0: obj = (JSObject *) obj->getPrivate(); \ michael@0: JS_ASSERT(obj) michael@0: michael@0: #define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname)); \ michael@0: if (!obj) \ michael@0: return false; \ michael@0: Debugger *dbg = Debugger::fromChildJSObject(obj); \ michael@0: obj = (JSObject *) obj->getPrivate(); \ michael@0: JS_ASSERT(obj) michael@0: michael@0: static bool michael@0: DebuggerObject_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, michael@0: "Debugger.Object"); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getProto(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get proto", args, dbg, refobj); michael@0: RootedObject proto(cx); michael@0: { michael@0: AutoCompartment ac(cx, refobj); michael@0: if (!JSObject::getProto(cx, refobj, &proto)) michael@0: return false; michael@0: } michael@0: RootedValue protov(cx, ObjectOrNullValue(proto)); michael@0: if (!dbg->wrapDebuggeeValue(cx, &protov)) michael@0: return false; michael@0: args.rval().set(protov); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getClass(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get class", args, refobj); michael@0: const char *className; michael@0: { michael@0: AutoCompartment ac(cx, refobj); michael@0: className = JSObject::className(cx, refobj); michael@0: } michael@0: JSAtom *str = Atomize(cx, className, strlen(className)); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getCallable(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get callable", args, refobj); michael@0: args.rval().setBoolean(refobj->isCallable()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getName(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get name", args, dbg, obj); michael@0: if (!obj->is()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: JSString *name = obj->as().atom(); michael@0: if (!name) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedValue namev(cx, StringValue(name)); michael@0: if (!dbg->wrapDebuggeeValue(cx, &namev)) michael@0: return false; michael@0: args.rval().set(namev); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getDisplayName(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get display name", args, dbg, obj); michael@0: if (!obj->is()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: JSString *name = obj->as().displayAtom(); michael@0: if (!name) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedValue namev(cx, StringValue(name)); michael@0: if (!dbg->wrapDebuggeeValue(cx, &namev)) michael@0: return false; michael@0: args.rval().set(namev); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getParameterNames(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get parameterNames", args, dbg, obj); michael@0: if (!obj->is()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedFunction fun(cx, &obj->as()); michael@0: michael@0: /* Only hand out parameter info for debuggee functions. */ michael@0: if (!dbg->observesGlobal(&fun->global())) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject result(cx, NewDenseAllocatedArray(cx, fun->nargs())); michael@0: if (!result) michael@0: return false; michael@0: result->ensureDenseInitializedLength(cx, 0, fun->nargs()); michael@0: michael@0: if (fun->isInterpreted()) { michael@0: RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); michael@0: if (!script) michael@0: return false; michael@0: michael@0: JS_ASSERT(fun->nargs() == script->bindings.numArgs()); michael@0: michael@0: if (fun->nargs() > 0) { michael@0: BindingVector bindings(cx); michael@0: if (!FillBindingVector(script, &bindings)) michael@0: return false; michael@0: for (size_t i = 0; i < fun->nargs(); i++) { michael@0: Value v; michael@0: if (bindings[i].name()->length() == 0) michael@0: v = UndefinedValue(); michael@0: else michael@0: v = StringValue(bindings[i].name()); michael@0: result->setDenseElement(i, v); michael@0: } michael@0: } michael@0: } else { michael@0: for (size_t i = 0; i < fun->nargs(); i++) michael@0: result->setDenseElement(i, UndefinedValue()); michael@0: } michael@0: michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getScript(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get script", args, dbg, obj); michael@0: michael@0: if (!obj->is()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedFunction fun(cx, &obj->as()); michael@0: if (fun->isBuiltin()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); michael@0: if (!script) michael@0: return false; michael@0: michael@0: /* Only hand out debuggee scripts. */ michael@0: if (!dbg->observesScript(script)) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject scriptObject(cx, dbg->wrapScript(cx, script)); michael@0: if (!scriptObject) michael@0: return false; michael@0: michael@0: args.rval().setObject(*scriptObject); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getEnvironment(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj); michael@0: michael@0: /* Don't bother switching compartments just to check obj's type and get its env. */ michael@0: if (!obj->is() || !obj->as().isInterpreted()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: /* Only hand out environments of debuggee functions. */ michael@0: if (!dbg->observesGlobal(&obj->global())) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: Rooted env(cx); michael@0: { michael@0: AutoCompartment ac(cx, obj); michael@0: RootedFunction fun(cx, &obj->as()); michael@0: env = GetDebugScopeForFunction(cx, fun); michael@0: if (!env) michael@0: return false; michael@0: } michael@0: michael@0: return dbg->wrapEnvironment(cx, env, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getGlobal(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get global", args, dbg, obj); michael@0: michael@0: RootedValue v(cx, ObjectValue(obj->global())); michael@0: if (!dbg->wrapDebuggeeValue(cx, &v)) michael@0: return false; michael@0: args.rval().set(v); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyDescriptor", args, dbg, obj); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args.get(0), &id)) michael@0: return false; michael@0: michael@0: /* Bug: This can cause the debuggee to run! */ michael@0: Rooted desc(cx); michael@0: { michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: if (!cx->compartment()->wrapId(cx, id.address())) michael@0: return false; michael@0: michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: } michael@0: michael@0: if (desc.object()) { michael@0: /* Rewrap the debuggee values in desc for the debugger. */ michael@0: if (!dbg->wrapDebuggeeValue(cx, desc.value())) michael@0: return false; michael@0: michael@0: if (desc.hasGetterObject()) { michael@0: RootedValue get(cx, ObjectOrNullValue(desc.getterObject())); michael@0: if (!dbg->wrapDebuggeeValue(cx, &get)) michael@0: return false; michael@0: desc.setGetterObject(get.toObjectOrNull()); michael@0: } michael@0: if (desc.hasSetterObject()) { michael@0: RootedValue set(cx, ObjectOrNullValue(desc.setterObject())); michael@0: if (!dbg->wrapDebuggeeValue(cx, &set)) michael@0: return false; michael@0: desc.setSetterObject(set.toObjectOrNull()); michael@0: } michael@0: } michael@0: michael@0: return NewPropertyDescriptorObject(cx, desc, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyNames", args, dbg, obj); michael@0: michael@0: AutoIdVector keys(cx); michael@0: { michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys)) michael@0: return false; michael@0: } michael@0: michael@0: AutoValueVector vals(cx); michael@0: if (!vals.resize(keys.length())) michael@0: return false; michael@0: michael@0: for (size_t i = 0, len = keys.length(); i < len; i++) { michael@0: jsid id = keys[i]; michael@0: if (JSID_IS_INT(id)) { michael@0: JSString *str = Int32ToString(cx, JSID_TO_INT(id)); michael@0: if (!str) michael@0: return false; michael@0: vals[i].setString(str); michael@0: } else if (JSID_IS_ATOM(id)) { michael@0: vals[i].setString(JSID_TO_STRING(id)); michael@0: if (!cx->compartment()->wrap(cx, vals.handleAt(i))) michael@0: return false; michael@0: } else { michael@0: vals[i].setObject(*JSID_TO_OBJECT(id)); michael@0: if (!dbg->wrapDebuggeeValue(cx, vals.handleAt(i))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin()); michael@0: if (!aobj) michael@0: return false; michael@0: args.rval().setObject(*aobj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_defineProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperty", args, dbg, obj); michael@0: REQUIRE_ARGC("Debugger.Object.defineProperty", 2); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, args[0], &id)) michael@0: return false; michael@0: michael@0: AutoPropDescArrayRooter descs(cx); michael@0: if (!descs.reserve(3)) // desc, unwrappedDesc, rewrappedDesc michael@0: return false; michael@0: PropDesc *desc = descs.append(); michael@0: if (!desc || !desc->initialize(cx, args[1], false)) michael@0: return false; michael@0: desc->clearPd(); michael@0: michael@0: PropDesc *unwrappedDesc = descs.append(); michael@0: if (!unwrappedDesc || !desc->unwrapDebuggerObjectsInto(cx, dbg, obj, unwrappedDesc)) michael@0: return false; michael@0: if (!unwrappedDesc->checkGetter(cx) || !unwrappedDesc->checkSetter(cx)) michael@0: return false; michael@0: michael@0: { michael@0: PropDesc *rewrappedDesc = descs.append(); michael@0: if (!rewrappedDesc) michael@0: return false; michael@0: RootedId wrappedId(cx); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: if (!unwrappedDesc->wrapInto(cx, obj, id, wrappedId.address(), rewrappedDesc)) michael@0: return false; michael@0: michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: bool dummy; michael@0: if (!DefineProperty(cx, obj, wrappedId, *rewrappedDesc, true, &dummy)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_defineProperties(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperties", args, dbg, obj); michael@0: REQUIRE_ARGC("Debugger.Object.defineProperties", 1); michael@0: michael@0: RootedValue arg(cx, args[0]); michael@0: RootedObject props(cx, ToObject(cx, arg)); michael@0: if (!props) michael@0: return false; michael@0: michael@0: AutoIdVector ids(cx); michael@0: AutoPropDescArrayRooter descs(cx); michael@0: if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs)) michael@0: return false; michael@0: size_t n = ids.length(); michael@0: michael@0: AutoPropDescArrayRooter unwrappedDescs(cx); michael@0: for (size_t i = 0; i < n; i++) { michael@0: if (!unwrappedDescs.append()) michael@0: return false; michael@0: if (!descs[i].unwrapDebuggerObjectsInto(cx, dbg, obj, &unwrappedDescs[i])) michael@0: return false; michael@0: if (!unwrappedDescs[i].checkGetter(cx) || !unwrappedDescs[i].checkSetter(cx)) michael@0: return false; michael@0: } michael@0: michael@0: { michael@0: AutoIdVector rewrappedIds(cx); michael@0: AutoPropDescArrayRooter rewrappedDescs(cx); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: RootedId id(cx); michael@0: for (size_t i = 0; i < n; i++) { michael@0: if (!rewrappedIds.append(JSID_VOID) || !rewrappedDescs.append()) michael@0: return false; michael@0: id = ids[i]; michael@0: if (!unwrappedDescs[i].wrapInto(cx, obj, id, &rewrappedIds[i], &rewrappedDescs[i])) michael@0: return false; michael@0: } michael@0: michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: for (size_t i = 0; i < n; i++) { michael@0: bool dummy; michael@0: if (!DefineProperty(cx, obj, rewrappedIds.handleAt(i), michael@0: rewrappedDescs[i], true, &dummy)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * This does a non-strict delete, as a matter of API design. The case where the michael@0: * property is non-configurable isn't necessarily exceptional here. michael@0: */ michael@0: static bool michael@0: DebuggerObject_deleteProperty(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "deleteProperty", args, dbg, obj); michael@0: RootedValue nameArg(cx, args.get(0)); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: if (!cx->compartment()->wrap(cx, &nameArg)) michael@0: return false; michael@0: michael@0: bool succeeded; michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: if (!JSObject::deleteByValue(cx, obj, nameArg, &succeeded)) michael@0: return false; michael@0: args.rval().setBoolean(succeeded); michael@0: return true; michael@0: } michael@0: michael@0: enum SealHelperOp { Seal, Freeze, PreventExtensions }; michael@0: michael@0: static bool michael@0: DebuggerObject_sealHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op, const char *name) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: bool ok; michael@0: if (op == Seal) { michael@0: ok = JSObject::seal(cx, obj); michael@0: } else if (op == Freeze) { michael@0: ok = JSObject::freeze(cx, obj); michael@0: } else { michael@0: JS_ASSERT(op == PreventExtensions); michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (!extensible) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: ok = JSObject::preventExtensions(cx, obj); michael@0: } michael@0: if (!ok) michael@0: return false; michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_seal(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DebuggerObject_sealHelper(cx, argc, vp, Seal, "seal"); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_freeze(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DebuggerObject_sealHelper(cx, argc, vp, Freeze, "freeze"); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_preventExtensions(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DebuggerObject_sealHelper(cx, argc, vp, PreventExtensions, "preventExtensions"); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_isSealedHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op, michael@0: const char *name) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj); michael@0: michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: bool r; michael@0: if (op == Seal) { michael@0: if (!JSObject::isSealed(cx, obj, &r)) michael@0: return false; michael@0: } else if (op == Freeze) { michael@0: if (!JSObject::isFrozen(cx, obj, &r)) michael@0: return false; michael@0: } else { michael@0: if (!JSObject::isExtensible(cx, obj, &r)) michael@0: return false; michael@0: } michael@0: args.rval().setBoolean(r); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_isSealed(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DebuggerObject_isSealedHelper(cx, argc, vp, Seal, "isSealed"); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_isFrozen(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DebuggerObject_isSealedHelper(cx, argc, vp, Freeze, "isFrozen"); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_isExtensible(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DebuggerObject_isSealedHelper(cx, argc, vp, PreventExtensions, "isExtensible"); michael@0: } michael@0: michael@0: enum ApplyOrCallMode { ApplyMode, CallMode }; michael@0: michael@0: static bool michael@0: ApplyOrCall(JSContext *cx, unsigned argc, Value *vp, ApplyOrCallMode mode) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "apply", args, dbg, obj); michael@0: michael@0: /* michael@0: * Any JS exceptions thrown must be in the debugger compartment, so do michael@0: * sanity checks and fallible conversions before entering the debuggee. michael@0: */ michael@0: RootedValue calleev(cx, ObjectValue(*obj)); michael@0: if (!obj->isCallable()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Object", "apply", obj->getClass()->name); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Unwrap Debugger.Objects. This happens in the debugger's compartment since michael@0: * that is where any exceptions must be reported. michael@0: */ michael@0: RootedValue thisv(cx, args.get(0)); michael@0: if (!dbg->unwrapDebuggeeValue(cx, &thisv)) michael@0: return false; michael@0: unsigned callArgc = 0; michael@0: Value *callArgv = nullptr; michael@0: AutoValueVector argv(cx); michael@0: if (mode == ApplyMode) { michael@0: if (args.length() >= 2 && !args[1].isNullOrUndefined()) { michael@0: if (!args[1].isObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_APPLY_ARGS, michael@0: js_apply_str); michael@0: return false; michael@0: } michael@0: RootedObject argsobj(cx, &args[1].toObject()); michael@0: if (!GetLengthProperty(cx, argsobj, &callArgc)) michael@0: return false; michael@0: callArgc = unsigned(Min(callArgc, ARGS_LENGTH_MAX)); michael@0: if (!argv.growBy(callArgc) || !GetElements(cx, argsobj, callArgc, argv.begin())) michael@0: return false; michael@0: callArgv = argv.begin(); michael@0: } michael@0: } else { michael@0: callArgc = args.length() > 0 ? unsigned(Min(args.length() - 1, ARGS_LENGTH_MAX)) : 0; michael@0: callArgv = args.array() + 1; michael@0: } michael@0: michael@0: AutoArrayRooter callArgvRooter(cx, callArgc, callArgv); michael@0: for (unsigned i = 0; i < callArgc; i++) { michael@0: if (!dbg->unwrapDebuggeeValue(cx, callArgvRooter.handleAt(i))) michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Enter the debuggee compartment and rewrap all input value for that compartment. michael@0: * (Rewrapping always takes place in the destination compartment.) michael@0: */ michael@0: Maybe ac; michael@0: ac.construct(cx, obj); michael@0: if (!cx->compartment()->wrap(cx, &calleev) || !cx->compartment()->wrap(cx, &thisv)) michael@0: return false; michael@0: michael@0: RootedValue arg(cx); michael@0: for (unsigned i = 0; i < callArgc; i++) { michael@0: if (!cx->compartment()->wrap(cx, callArgvRooter.handleAt(i))) michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Call the function. Use receiveCompletionValue to return to the debugger michael@0: * compartment and populate args.rval(). michael@0: */ michael@0: RootedValue rval(cx); michael@0: bool ok = Invoke(cx, thisv, calleev, callArgc, callArgv, &rval); michael@0: return dbg->receiveCompletionValue(ac, ok, rval, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_apply(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return ApplyOrCall(cx, argc, vp, ApplyMode); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_call(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return ApplyOrCall(cx, argc, vp, CallMode); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_makeDebuggeeValue(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Object.prototype.makeDebuggeeValue", 1); michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "makeDebuggeeValue", args, dbg, referent); michael@0: michael@0: RootedValue arg0(cx, args[0]); michael@0: michael@0: /* Non-objects are already debuggee values. */ michael@0: if (arg0.isObject()) { michael@0: // Enter this Debugger.Object's referent's compartment, and wrap the michael@0: // argument as appropriate for references from there. michael@0: { michael@0: AutoCompartment ac(cx, referent); michael@0: if (!cx->compartment()->wrap(cx, &arg0)) michael@0: return false; michael@0: } michael@0: michael@0: // Back in the debugger's compartment, produce a new Debugger.Object michael@0: // instance referring to the wrapped argument. michael@0: if (!dbg->wrapDebuggeeValue(cx, &arg0)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().set(arg0); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: RequireGlobalObject(JSContext *cx, HandleValue dbgobj, HandleObject referent) michael@0: { michael@0: RootedObject obj(cx, referent); michael@0: michael@0: if (!obj->is()) { michael@0: const char *isWrapper = ""; michael@0: const char *isWindowProxy = ""; michael@0: michael@0: /* Help the poor programmer by pointing out wrappers around globals... */ michael@0: if (obj->is()) { michael@0: obj = js::UncheckedUnwrap(obj); michael@0: isWrapper = "a wrapper around "; michael@0: } michael@0: michael@0: /* ... and WindowProxies around Windows. */ michael@0: if (IsOuterObject(obj)) { michael@0: obj = JS_ObjectToInnerObject(cx, obj); michael@0: isWindowProxy = "a WindowProxy referring to "; michael@0: } michael@0: michael@0: if (obj->is()) { michael@0: js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_WRAPPER_IN_WAY, michael@0: JSDVG_SEARCH_STACK, dbgobj, js::NullPtr(), michael@0: isWrapper, isWindowProxy); michael@0: } else { michael@0: js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT, michael@0: JSDVG_SEARCH_STACK, dbgobj, js::NullPtr(), michael@0: "a global object", nullptr); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_evalInGlobal(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobal", 1); michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobal", args, dbg, referent); michael@0: if (!RequireGlobalObject(cx, args.thisv(), referent)) michael@0: return false; michael@0: michael@0: return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobal", michael@0: args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue, michael@0: args.get(1), args.rval(), dbg, referent, nullptr); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_evalInGlobalWithBindings(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobalWithBindings", 2); michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobalWithBindings", args, dbg, referent); michael@0: if (!RequireGlobalObject(cx, args.thisv(), referent)) michael@0: return false; michael@0: michael@0: return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobalWithBindings", michael@0: args[0], EvalHasExtraBindings, args[1], args.get(2), michael@0: args.rval(), dbg, referent, nullptr); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_unwrap(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "unwrap", args, dbg, referent); michael@0: JSObject *unwrapped = UnwrapOneChecked(referent); michael@0: if (!unwrapped) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: args.rval().setObject(*unwrapped); michael@0: if (!dbg->wrapDebuggeeValue(cx, args.rval())) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerObject_unsafeDereference(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "unsafeDereference", args, referent); michael@0: args.rval().setObject(*referent); michael@0: if (!cx->compartment()->wrap(cx, args.rval())) michael@0: return false; michael@0: michael@0: // Wrapping should outerize inner objects. michael@0: JS_ASSERT(!IsInnerObject(&args.rval().toObject())); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static const JSPropertySpec DebuggerObject_properties[] = { michael@0: JS_PSG("proto", DebuggerObject_getProto, 0), michael@0: JS_PSG("class", DebuggerObject_getClass, 0), michael@0: JS_PSG("callable", DebuggerObject_getCallable, 0), michael@0: JS_PSG("name", DebuggerObject_getName, 0), michael@0: JS_PSG("displayName", DebuggerObject_getDisplayName, 0), michael@0: JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0), michael@0: JS_PSG("script", DebuggerObject_getScript, 0), michael@0: JS_PSG("environment", DebuggerObject_getEnvironment, 0), michael@0: JS_PSG("global", DebuggerObject_getGlobal, 0), michael@0: JS_PS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec DebuggerObject_methods[] = { michael@0: JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0), michael@0: JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0), michael@0: JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0), michael@0: JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0), michael@0: JS_FN("deleteProperty", DebuggerObject_deleteProperty, 1, 0), michael@0: JS_FN("seal", DebuggerObject_seal, 0, 0), michael@0: JS_FN("freeze", DebuggerObject_freeze, 0, 0), michael@0: JS_FN("preventExtensions", DebuggerObject_preventExtensions, 0, 0), michael@0: JS_FN("isSealed", DebuggerObject_isSealed, 0, 0), michael@0: JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0), michael@0: JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0), michael@0: JS_FN("apply", DebuggerObject_apply, 0, 0), michael@0: JS_FN("call", DebuggerObject_call, 0, 0), michael@0: JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0), michael@0: JS_FN("evalInGlobal", DebuggerObject_evalInGlobal, 1, 0), michael@0: JS_FN("evalInGlobalWithBindings", DebuggerObject_evalInGlobalWithBindings, 2, 0), michael@0: JS_FN("unwrap", DebuggerObject_unwrap, 0, 0), michael@0: JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: /*** Debugger.Environment ************************************************************************/ michael@0: michael@0: static void michael@0: DebuggerEnv_trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: /* michael@0: * There is a barrier on private pointers, so the Unbarriered marking michael@0: * is okay. michael@0: */ michael@0: if (Env *referent = (JSObject *) obj->getPrivate()) { michael@0: MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Environment referent"); michael@0: obj->setPrivateUnbarriered(referent); michael@0: } michael@0: } michael@0: michael@0: const Class DebuggerEnv_class = { michael@0: "Environment", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: DebuggerEnv_trace michael@0: }; michael@0: michael@0: static JSObject * michael@0: DebuggerEnv_checkThis(JSContext *cx, const CallArgs &args, const char *fnname, michael@0: bool requireDebuggee = true) michael@0: { michael@0: if (!args.thisv().isObject()) { michael@0: ReportObjectRequired(cx); michael@0: return nullptr; michael@0: } michael@0: JSObject *thisobj = &args.thisv().toObject(); michael@0: if (thisobj->getClass() != &DebuggerEnv_class) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Environment", fnname, thisobj->getClass()->name); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Forbid Debugger.Environment.prototype, which is of class DebuggerEnv_class michael@0: * but isn't a real working Debugger.Environment. The prototype object is michael@0: * distinguished by having no referent. michael@0: */ michael@0: if (!thisobj->getPrivate()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, michael@0: "Debugger.Environment", fnname, "prototype object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Forbid access to Debugger.Environment objects that are not debuggee michael@0: * environments. michael@0: */ michael@0: if (requireDebuggee) { michael@0: Rooted env(cx, static_cast(thisobj->getPrivate())); michael@0: if (!Debugger::fromChildJSObject(thisobj)->observesGlobal(&env->global())) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGEE, michael@0: "Debugger.Environment", "environment"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: return thisobj; michael@0: } michael@0: michael@0: #define THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env) \ michael@0: CallArgs args = CallArgsFromVp(argc, vp); \ michael@0: JSObject *envobj = DebuggerEnv_checkThis(cx, args, fnname); \ michael@0: if (!envobj) \ michael@0: return false; \ michael@0: Rooted env(cx, static_cast(envobj->getPrivate())); \ michael@0: JS_ASSERT(env); \ michael@0: JS_ASSERT(!env->is()) michael@0: michael@0: #define THIS_DEBUGENV_OWNER(cx, argc, vp, fnname, args, envobj, env, dbg) \ michael@0: THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env); \ michael@0: Debugger *dbg = Debugger::fromChildJSObject(envobj) michael@0: michael@0: static bool michael@0: DebuggerEnv_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, michael@0: "Debugger.Environment"); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: IsDeclarative(Env *env) michael@0: { michael@0: return env->is() && env->as().isForDeclarative(); michael@0: } michael@0: michael@0: static bool michael@0: IsWith(Env *env) michael@0: { michael@0: return env->is() && michael@0: env->as().scope().is(); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_getType(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env); michael@0: michael@0: /* Don't bother switching compartments just to check env's class. */ michael@0: const char *s; michael@0: if (IsDeclarative(env)) michael@0: s = "declarative"; michael@0: else if (IsWith(env)) michael@0: s = "with"; michael@0: else michael@0: s = "object"; michael@0: michael@0: JSAtom *str = Atomize(cx, s, strlen(s), InternAtom); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_getParent(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "get parent", args, envobj, env, dbg); michael@0: michael@0: /* Don't bother switching compartments just to get env's parent. */ michael@0: Rooted parent(cx, env->enclosingScope()); michael@0: return dbg->wrapEnvironment(cx, parent, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_getObject(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg); michael@0: michael@0: /* michael@0: * Don't bother switching compartments just to check env's class and michael@0: * possibly get its proto. michael@0: */ michael@0: if (IsDeclarative(env)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NO_SCOPE_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: JSObject *obj; michael@0: if (IsWith(env)) { michael@0: obj = &env->as().scope().as().object(); michael@0: } else { michael@0: obj = env; michael@0: JS_ASSERT(!obj->is()); michael@0: } michael@0: michael@0: args.rval().setObject(*obj); michael@0: if (!dbg->wrapDebuggeeValue(cx, args.rval())) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_getCallee(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "get callee", args, envobj, env, dbg); michael@0: michael@0: args.rval().setNull(); michael@0: michael@0: if (!env->is()) michael@0: return true; michael@0: michael@0: JSObject &scope = env->as().scope(); michael@0: if (!scope.is()) michael@0: return true; michael@0: michael@0: CallObject &callobj = scope.as(); michael@0: if (callobj.isForEval()) michael@0: return true; michael@0: michael@0: args.rval().setObject(callobj.callee()); michael@0: if (!dbg->wrapDebuggeeValue(cx, args.rval())) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_getInspectable(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSObject *envobj = DebuggerEnv_checkThis(cx, args, "get inspectable", false); michael@0: if (!envobj) michael@0: return false; michael@0: Rooted env(cx, static_cast(envobj->getPrivate())); michael@0: JS_ASSERT(env); michael@0: JS_ASSERT(!env->is()); michael@0: michael@0: Debugger *dbg = Debugger::fromChildJSObject(envobj); michael@0: michael@0: args.rval().setBoolean(dbg->observesGlobal(&env->global())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "names", args, envobj, env, dbg); michael@0: michael@0: AutoIdVector keys(cx); michael@0: { michael@0: Maybe ac; michael@0: ac.construct(cx, env); michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: if (!GetPropertyNames(cx, env, JSITER_HIDDEN, &keys)) michael@0: return false; michael@0: } michael@0: michael@0: RootedObject arr(cx, NewDenseEmptyArray(cx)); michael@0: if (!arr) michael@0: return false; michael@0: RootedId id(cx); michael@0: for (size_t i = 0, len = keys.length(); i < len; i++) { michael@0: id = keys[i]; michael@0: if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) { michael@0: if (!cx->compartment()->wrapId(cx, id.address())) michael@0: return false; michael@0: if (!NewbornArrayPush(cx, arr, StringValue(JSID_TO_STRING(id)))) michael@0: return false; michael@0: } michael@0: } michael@0: args.rval().setObject(*arr); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Environment.find", 1); michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "find", args, envobj, env, dbg); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToIdentifier(cx, args[0], &id)) michael@0: return false; michael@0: michael@0: { michael@0: Maybe ac; michael@0: ac.construct(cx, env); michael@0: if (!cx->compartment()->wrapId(cx, id.address())) michael@0: return false; michael@0: michael@0: /* This can trigger resolve hooks. */ michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: RootedShape prop(cx); michael@0: RootedObject pobj(cx); michael@0: for (; env && !prop; env = env->enclosingScope()) { michael@0: if (!JSObject::lookupGeneric(cx, env, id, &pobj, &prop)) michael@0: return false; michael@0: if (prop) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return dbg->wrapEnvironment(cx, env, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_getVariable(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Environment.getVariable", 1); michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "getVariable", args, envobj, env, dbg); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToIdentifier(cx, args[0], &id)) michael@0: return false; michael@0: michael@0: RootedValue v(cx); michael@0: { michael@0: Maybe ac; michael@0: ac.construct(cx, env); michael@0: if (!cx->compartment()->wrapId(cx, id.address())) michael@0: return false; michael@0: michael@0: /* This can trigger getters. */ michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: michael@0: // For DebugScopeObjects, we get sentinel values for optimized out michael@0: // slots and arguments instead of throwing (the default behavior). michael@0: // michael@0: // See wrapDebuggeeValue for how the sentinel values are wrapped. michael@0: if (env->is()) { michael@0: if (!env->as().getMaybeSentinelValue(cx, id, &v)) michael@0: return false; michael@0: } else { michael@0: if (!JSObject::getGeneric(cx, env, env, id, &v)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!dbg->wrapDebuggeeValue(cx, &v)) michael@0: return false; michael@0: args.rval().set(v); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DebuggerEnv_setVariable(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: REQUIRE_ARGC("Debugger.Environment.setVariable", 2); michael@0: THIS_DEBUGENV_OWNER(cx, argc, vp, "setVariable", args, envobj, env, dbg); michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToIdentifier(cx, args[0], &id)) michael@0: return false; michael@0: michael@0: RootedValue v(cx, args[1]); michael@0: if (!dbg->unwrapDebuggeeValue(cx, &v)) michael@0: return false; michael@0: michael@0: { michael@0: Maybe ac; michael@0: ac.construct(cx, env); michael@0: if (!cx->compartment()->wrapId(cx, id.address()) || !cx->compartment()->wrap(cx, &v)) michael@0: return false; michael@0: michael@0: /* This can trigger setters. */ michael@0: ErrorCopier ec(ac, dbg->toJSObject()); michael@0: michael@0: /* Make sure the environment actually has the specified binding. */ michael@0: bool has; michael@0: if (!JSObject::hasProperty(cx, env, id, &has)) michael@0: return false; michael@0: if (!has) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_VARIABLE_NOT_FOUND); michael@0: return false; michael@0: } michael@0: michael@0: /* Just set the property. */ michael@0: if (!JSObject::setGeneric(cx, env, env, id, &v, true)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static const JSPropertySpec DebuggerEnv_properties[] = { michael@0: JS_PSG("type", DebuggerEnv_getType, 0), michael@0: JS_PSG("object", DebuggerEnv_getObject, 0), michael@0: JS_PSG("parent", DebuggerEnv_getParent, 0), michael@0: JS_PSG("callee", DebuggerEnv_getCallee, 0), michael@0: JS_PSG("inspectable", DebuggerEnv_getInspectable, 0), michael@0: JS_PS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec DebuggerEnv_methods[] = { michael@0: JS_FN("names", DebuggerEnv_names, 0, 0), michael@0: JS_FN("find", DebuggerEnv_find, 1, 0), michael@0: JS_FN("getVariable", DebuggerEnv_getVariable, 1, 0), michael@0: JS_FN("setVariable", DebuggerEnv_setVariable, 2, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: michael@0: /*** Glue ****************************************************************************************/ michael@0: michael@0: extern JS_PUBLIC_API(bool) michael@0: JS_DefineDebuggerObject(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedObject michael@0: objProto(cx), michael@0: debugCtor(cx), michael@0: debugProto(cx), michael@0: frameProto(cx), michael@0: scriptProto(cx), michael@0: sourceProto(cx), michael@0: objectProto(cx), michael@0: envProto(cx), michael@0: memoryProto(cx); michael@0: objProto = obj->as().getOrCreateObjectPrototype(cx); michael@0: if (!objProto) michael@0: return false; michael@0: debugProto = js_InitClass(cx, obj, michael@0: objProto, &Debugger::jsclass, Debugger::construct, michael@0: 1, Debugger::properties, Debugger::methods, nullptr, nullptr, michael@0: debugCtor.address()); michael@0: if (!debugProto) michael@0: return false; michael@0: michael@0: frameProto = js_InitClass(cx, debugCtor, objProto, &DebuggerFrame_class, michael@0: DebuggerFrame_construct, 0, michael@0: DebuggerFrame_properties, DebuggerFrame_methods, michael@0: nullptr, nullptr); michael@0: if (!frameProto) michael@0: return false; michael@0: michael@0: scriptProto = js_InitClass(cx, debugCtor, objProto, &DebuggerScript_class, michael@0: DebuggerScript_construct, 0, michael@0: DebuggerScript_properties, DebuggerScript_methods, michael@0: nullptr, nullptr); michael@0: if (!scriptProto) michael@0: return false; michael@0: michael@0: sourceProto = js_InitClass(cx, debugCtor, sourceProto, &DebuggerSource_class, michael@0: DebuggerSource_construct, 0, michael@0: DebuggerSource_properties, DebuggerSource_methods, michael@0: nullptr, nullptr); michael@0: if (!sourceProto) michael@0: return false; michael@0: michael@0: objectProto = js_InitClass(cx, debugCtor, objProto, &DebuggerObject_class, michael@0: DebuggerObject_construct, 0, michael@0: DebuggerObject_properties, DebuggerObject_methods, michael@0: nullptr, nullptr); michael@0: if (!objectProto) michael@0: return false; michael@0: envProto = js_InitClass(cx, debugCtor, objProto, &DebuggerEnv_class, michael@0: DebuggerEnv_construct, 0, michael@0: DebuggerEnv_properties, DebuggerEnv_methods, michael@0: nullptr, nullptr); michael@0: if (!envProto) michael@0: return false; michael@0: memoryProto = js_InitClass(cx, debugCtor, objProto, &DebuggerMemory::class_, michael@0: DebuggerMemory::construct, 0, DebuggerMemory::properties, michael@0: DebuggerMemory::methods, nullptr, nullptr); michael@0: if (!memoryProto) michael@0: return false; michael@0: michael@0: debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto)); michael@0: debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto)); michael@0: debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto)); michael@0: debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO, ObjectValue(*sourceProto)); michael@0: debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto)); michael@0: debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO, ObjectValue(*memoryProto)); michael@0: return true; michael@0: }