js/src/vm/Debugger.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "vm/Debugger-inl.h"
     9 #include "jscntxt.h"
    10 #include "jscompartment.h"
    11 #include "jshashutil.h"
    12 #include "jsnum.h"
    13 #include "jsobj.h"
    14 #include "jswrapper.h"
    15 #include "frontend/BytecodeCompiler.h"
    16 #include "gc/Marking.h"
    17 #include "jit/BaselineJIT.h"
    18 #include "js/Vector.h"
    19 #include "vm/ArgumentsObject.h"
    20 #include "vm/DebuggerMemory.h"
    21 #include "vm/WrapperObject.h"
    22 #include "jsgcinlines.h"
    23 #include "jsobjinlines.h"
    24 #include "jsopcodeinlines.h"
    25 #include "jsscriptinlines.h"
    26 #include "vm/ObjectImpl-inl.h"
    27 #include "vm/Stack-inl.h"
    29 using namespace js;
    31 using js::frontend::IsIdentifier;
    32 using mozilla::ArrayLength;
    33 using mozilla::Maybe;
    36 /*** Forward declarations ************************************************************************/
    38 extern const Class DebuggerFrame_class;
    40 enum {
    41     JSSLOT_DEBUGFRAME_OWNER,
    42     JSSLOT_DEBUGFRAME_ARGUMENTS,
    43     JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
    44     JSSLOT_DEBUGFRAME_ONPOP_HANDLER,
    45     JSSLOT_DEBUGFRAME_COUNT
    46 };
    48 extern const Class DebuggerArguments_class;
    50 enum {
    51     JSSLOT_DEBUGARGUMENTS_FRAME,
    52     JSSLOT_DEBUGARGUMENTS_COUNT
    53 };
    55 extern const Class DebuggerEnv_class;
    57 enum {
    58     JSSLOT_DEBUGENV_OWNER,
    59     JSSLOT_DEBUGENV_COUNT
    60 };
    62 extern const Class DebuggerObject_class;
    64 enum {
    65     JSSLOT_DEBUGOBJECT_OWNER,
    66     JSSLOT_DEBUGOBJECT_COUNT
    67 };
    69 extern const Class DebuggerScript_class;
    71 enum {
    72     JSSLOT_DEBUGSCRIPT_OWNER,
    73     JSSLOT_DEBUGSCRIPT_COUNT
    74 };
    76 extern const Class DebuggerSource_class;
    78 enum {
    79     JSSLOT_DEBUGSOURCE_OWNER,
    80     JSSLOT_DEBUGSOURCE_COUNT
    81 };
    84 /*** Utils ***************************************************************************************/
    86 static bool
    87 ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required)
    88 {
    89     JS_ASSERT(required > 0);
    90     JS_ASSERT(required <= 10);
    91     char s[2];
    92     s[0] = '0' + (required - 1);
    93     s[1] = '\0';
    94     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
    95                          name, s, required == 2 ? "" : "s");
    96     return false;
    97 }
    99 static inline bool
   100 EnsureFunctionHasScript(JSContext *cx, HandleFunction fun)
   101 {
   102     if (fun->isInterpretedLazy()) {
   103         AutoCompartment ac(cx, fun);
   104         return !!fun->getOrCreateScript(cx);
   105     }
   106     return true;
   107 }
   109 static inline JSScript *
   110 GetOrCreateFunctionScript(JSContext *cx, HandleFunction fun)
   111 {
   112     MOZ_ASSERT(fun->isInterpreted());
   113     if (!EnsureFunctionHasScript(cx, fun))
   114         return nullptr;
   115     return fun->nonLazyScript();
   116 }
   118 #define REQUIRE_ARGC(name, n)                                                 \
   119     JS_BEGIN_MACRO                                                            \
   120         if (argc < (n))                                                       \
   121             return ReportMoreArgsNeeded(cx, name, n);                         \
   122     JS_END_MACRO
   124 static bool
   125 ReportObjectRequired(JSContext *cx)
   126 {
   127     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
   128     return false;
   129 }
   131 static bool
   132 ValueToIdentifier(JSContext *cx, HandleValue v, MutableHandleId id)
   133 {
   134     if (!ValueToId<CanGC>(cx, v, id))
   135         return false;
   136     if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) {
   137         RootedValue val(cx, v);
   138         js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
   139                                  JSDVG_SEARCH_STACK, val, js::NullPtr(), "not an identifier",
   140                                  nullptr);
   141         return false;
   142     }
   143     return true;
   144 }
   146 /*
   147  * A range of all the Debugger.Frame objects for a particular AbstractFramePtr.
   148  *
   149  * FIXME This checks only current debuggers, so it relies on a hack in
   150  * Debugger::removeDebuggeeGlobal to make sure only current debuggers
   151  * have Frame objects with .live === true.
   152  */
   153 class Debugger::FrameRange
   154 {
   155     AbstractFramePtr frame;
   157     /* The debuggers in |fp|'s compartment, or nullptr if there are none. */
   158     GlobalObject::DebuggerVector *debuggers;
   160     /*
   161      * The index of the front Debugger.Frame's debugger in debuggers.
   162      * nextDebugger < debuggerCount if and only if the range is not empty.
   163      */
   164     size_t debuggerCount, nextDebugger;
   166     /*
   167      * If the range is not empty, this is front Debugger.Frame's entry in its
   168      * debugger's frame table.
   169      */
   170     FrameMap::Ptr entry;
   172   public:
   173     /*
   174      * Return a range containing all Debugger.Frame instances referring to
   175      * |fp|. |global| is |fp|'s global object; if nullptr or omitted, we
   176      * compute it ourselves from |fp|.
   177      *
   178      * We keep an index into the compartment's debugger list, and a
   179      * FrameMap::Ptr into the current debugger's frame map. Thus, if the set of
   180      * debuggers in |fp|'s compartment changes, this range becomes invalid.
   181      * Similarly, if stack frames are added to or removed from frontDebugger(),
   182      * then the range's front is invalid until popFront is called.
   183      */
   184     FrameRange(AbstractFramePtr frame, GlobalObject *global = nullptr)
   185       : frame(frame)
   186     {
   187         nextDebugger = 0;
   189         /* Find our global, if we were not given one. */
   190         if (!global)
   191             global = &frame.script()->global();
   193         /* The frame and global must match. */
   194         JS_ASSERT(&frame.script()->global() == global);
   196         /* Find the list of debuggers we'll iterate over. There may be none. */
   197         debuggers = global->getDebuggers();
   198         if (debuggers) {
   199             debuggerCount = debuggers->length();
   200             findNext();
   201         } else {
   202             debuggerCount = 0;
   203         }
   204     }
   206     bool empty() const {
   207         return nextDebugger >= debuggerCount;
   208     }
   210     JSObject *frontFrame() const {
   211         JS_ASSERT(!empty());
   212         return entry->value();
   213     }
   215     Debugger *frontDebugger() const {
   216         JS_ASSERT(!empty());
   217         return (*debuggers)[nextDebugger];
   218     }
   220     /*
   221      * Delete the front frame from its Debugger's frame map. After this call,
   222      * the range's front is invalid until popFront is called.
   223      */
   224     void removeFrontFrame() const {
   225         JS_ASSERT(!empty());
   226         frontDebugger()->frames.remove(entry);
   227     }
   229     void popFront() {
   230         JS_ASSERT(!empty());
   231         nextDebugger++;
   232         findNext();
   233     }
   235   private:
   236     /*
   237      * Either make this range refer to the first appropriate Debugger.Frame at
   238      * or after nextDebugger, or make it empty.
   239      */
   240     void findNext() {
   241         while (!empty()) {
   242             Debugger *dbg = (*debuggers)[nextDebugger];
   243             entry = dbg->frames.lookup(frame);
   244             if (entry)
   245                 break;
   246             nextDebugger++;
   247         }
   248     }
   249 };
   251 /*** Breakpoints *********************************************************************************/
   253 BreakpointSite::BreakpointSite(JSScript *script, jsbytecode *pc)
   254   : script(script), pc(pc), enabledCount(0),
   255     trapHandler(nullptr), trapClosure(UndefinedValue())
   256 {
   257     JS_ASSERT(!script->hasBreakpointsAt(pc));
   258     JS_INIT_CLIST(&breakpoints);
   259 }
   261 void
   262 BreakpointSite::recompile(FreeOp *fop)
   263 {
   264 #ifdef JS_ION
   265     if (script->hasBaselineScript())
   266         script->baselineScript()->toggleDebugTraps(script, pc);
   267 #endif
   268 }
   270 void
   271 BreakpointSite::inc(FreeOp *fop)
   272 {
   273     enabledCount++;
   274     if (enabledCount == 1 && !trapHandler)
   275         recompile(fop);
   276 }
   278 void
   279 BreakpointSite::dec(FreeOp *fop)
   280 {
   281     JS_ASSERT(enabledCount > 0);
   282     enabledCount--;
   283     if (enabledCount == 0 && !trapHandler)
   284         recompile(fop);
   285 }
   287 void
   288 BreakpointSite::setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure)
   289 {
   290     trapHandler = handler;
   291     trapClosure = closure;
   293     if (enabledCount == 0)
   294         recompile(fop);
   295 }
   297 void
   298 BreakpointSite::clearTrap(FreeOp *fop, JSTrapHandler *handlerp, Value *closurep)
   299 {
   300     if (handlerp)
   301         *handlerp = trapHandler;
   302     if (closurep)
   303         *closurep = trapClosure;
   305     trapHandler = nullptr;
   306     trapClosure = UndefinedValue();
   307     if (enabledCount == 0) {
   308         if (!fop->runtime()->isHeapBusy()) {
   309             /* If the GC is running then the script is being destroyed. */
   310             recompile(fop);
   311         }
   312         destroyIfEmpty(fop);
   313     }
   314 }
   316 void
   317 BreakpointSite::destroyIfEmpty(FreeOp *fop)
   318 {
   319     if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler)
   320         script->destroyBreakpointSite(fop, pc);
   321 }
   323 Breakpoint *
   324 BreakpointSite::firstBreakpoint() const
   325 {
   326     if (JS_CLIST_IS_EMPTY(&breakpoints))
   327         return nullptr;
   328     return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints));
   329 }
   331 bool
   332 BreakpointSite::hasBreakpoint(Breakpoint *bp)
   333 {
   334     for (Breakpoint *p = firstBreakpoint(); p; p = p->nextInSite())
   335         if (p == bp)
   336             return true;
   337     return false;
   338 }
   340 Breakpoint::Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler)
   341     : debugger(debugger), site(site), handler(handler)
   342 {
   343     JS_ASSERT(handler->compartment() == debugger->object->compartment());
   344     JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints);
   345     JS_APPEND_LINK(&siteLinks, &site->breakpoints);
   346 }
   348 Breakpoint *
   349 Breakpoint::fromDebuggerLinks(JSCList *links)
   350 {
   351     return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, debuggerLinks));
   352 }
   354 Breakpoint *
   355 Breakpoint::fromSiteLinks(JSCList *links)
   356 {
   357     return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, siteLinks));
   358 }
   360 void
   361 Breakpoint::destroy(FreeOp *fop)
   362 {
   363     if (debugger->enabled)
   364         site->dec(fop);
   365     JS_REMOVE_LINK(&debuggerLinks);
   366     JS_REMOVE_LINK(&siteLinks);
   367     site->destroyIfEmpty(fop);
   368     fop->delete_(this);
   369 }
   371 Breakpoint *
   372 Breakpoint::nextInDebugger()
   373 {
   374     JSCList *link = JS_NEXT_LINK(&debuggerLinks);
   375     return (link == &debugger->breakpoints) ? nullptr : fromDebuggerLinks(link);
   376 }
   378 Breakpoint *
   379 Breakpoint::nextInSite()
   380 {
   381     JSCList *link = JS_NEXT_LINK(&siteLinks);
   382     return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link);
   383 }
   385 /*** Debugger hook dispatch **********************************************************************/
   387 Debugger::Debugger(JSContext *cx, JSObject *dbg)
   388   : object(dbg), uncaughtExceptionHook(nullptr), enabled(true),
   389     frames(cx->runtime()), scripts(cx), sources(cx), objects(cx), environments(cx)
   390 {
   391     assertSameCompartment(cx, dbg);
   393     cx->runtime()->debuggerList.insertBack(this);
   394     JS_INIT_CLIST(&breakpoints);
   395     JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
   396 }
   398 Debugger::~Debugger()
   399 {
   400     JS_ASSERT_IF(debuggees.initialized(), debuggees.empty());
   402     /*
   403      * Since the inactive state for this link is a singleton cycle, it's always
   404      * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not.
   405      *
   406      * We don't have to worry about locking here since Debugger is not
   407      * background finalized.
   408      */
   409     JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink);
   410 }
   412 bool
   413 Debugger::init(JSContext *cx)
   414 {
   415     bool ok = debuggees.init() &&
   416               frames.init() &&
   417               scripts.init() &&
   418               sources.init() &&
   419               objects.init() &&
   420               environments.init();
   421     if (!ok)
   422         js_ReportOutOfMemory(cx);
   423     return ok;
   424 }
   426 Debugger *
   427 Debugger::fromJSObject(JSObject *obj)
   428 {
   429     JS_ASSERT(js::GetObjectClass(obj) == &jsclass);
   430     return (Debugger *) obj->getPrivate();
   431 }
   433 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER));
   434 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSOURCE_OWNER));
   435 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER));
   436 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGENV_OWNER));
   438 Debugger *
   439 Debugger::fromChildJSObject(JSObject *obj)
   440 {
   441     JS_ASSERT(obj->getClass() == &DebuggerFrame_class ||
   442               obj->getClass() == &DebuggerScript_class ||
   443               obj->getClass() == &DebuggerSource_class ||
   444               obj->getClass() == &DebuggerObject_class ||
   445               obj->getClass() == &DebuggerEnv_class);
   446     JSObject *dbgobj = &obj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject();
   447     return fromJSObject(dbgobj);
   448 }
   450 bool
   451 Debugger::getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame,
   452                                  const ScriptFrameIter *maybeIter, MutableHandleValue vp)
   453 {
   454     MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == frame);
   456     FrameMap::AddPtr p = frames.lookupForAdd(frame);
   457     if (!p) {
   458         /* Create and populate the Debugger.Frame object. */
   459         JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject();
   460         JSObject *frameobj =
   461             NewObjectWithGivenProto(cx, &DebuggerFrame_class, proto, nullptr);
   462         if (!frameobj)
   463             return false;
   465         // Eagerly copy ScriptFrameIter data if we've already walked the
   466         // stack.
   467         if (maybeIter) {
   468             AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
   469             if (!data)
   470                 return false;
   471             frameobj->setPrivate(data.raw());
   472         } else {
   473             frameobj->setPrivate(frame.raw());
   474         }
   476         frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
   478         if (!frames.add(p, frame, frameobj)) {
   479             js_ReportOutOfMemory(cx);
   480             return false;
   481         }
   482     }
   483     vp.setObject(*p->value());
   484     return true;
   485 }
   487 JSObject *
   488 Debugger::getHook(Hook hook) const
   489 {
   490     JS_ASSERT(hook >= 0 && hook < HookCount);
   491     const Value &v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook);
   492     return v.isUndefined() ? nullptr : &v.toObject();
   493 }
   495 bool
   496 Debugger::hasAnyLiveHooks() const
   497 {
   498     if (!enabled)
   499         return false;
   501     if (getHook(OnDebuggerStatement) ||
   502         getHook(OnExceptionUnwind) ||
   503         getHook(OnNewScript) ||
   504         getHook(OnEnterFrame))
   505     {
   506         return true;
   507     }
   509     /* If any breakpoints are in live scripts, return true. */
   510     for (Breakpoint *bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
   511         if (IsScriptMarked(&bp->site->script))
   512             return true;
   513     }
   515     for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
   516         JSObject *frameObj = r.front().value();
   517         if (!frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() ||
   518             !frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined())
   519             return true;
   520     }
   522     return false;
   523 }
   525 JSTrapStatus
   526 Debugger::slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp)
   527 {
   528     /* Build the list of recipients. */
   529     AutoValueVector triggered(cx);
   530     Handle<GlobalObject*> global = cx->global();
   532     if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
   533         for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
   534             Debugger *dbg = *p;
   535             if (dbg->observesFrame(frame) && dbg->observesEnterFrame() &&
   536                 !triggered.append(ObjectValue(*dbg->toJSObject())))
   537             {
   538                 return JSTRAP_ERROR;
   539             }
   540         }
   541     }
   543     /* Deliver the event, checking again as in dispatchHook. */
   544     for (Value *p = triggered.begin(); p != triggered.end(); p++) {
   545         Debugger *dbg = Debugger::fromJSObject(&p->toObject());
   546         if (dbg->debuggees.has(global) && dbg->observesEnterFrame()) {
   547             JSTrapStatus status = dbg->fireEnterFrame(cx, frame, vp);
   548             if (status != JSTRAP_CONTINUE)
   549                 return status;
   550         }
   551     }
   553     return JSTRAP_CONTINUE;
   554 }
   556 static void
   557 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame,
   558                                                      JSObject *frameobj);
   560 static void
   561 DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj);
   563 /*
   564  * Handle leaving a frame with debuggers watching. |frameOk| indicates whether
   565  * the frame is exiting normally or abruptly. Set |cx|'s exception and/or
   566  * |cx->fp()|'s return value, and return a new success value.
   567  */
   568 bool
   569 Debugger::slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool frameOk)
   570 {
   571     Handle<GlobalObject*> global = cx->global();
   573     /* Save the frame's completion value. */
   574     JSTrapStatus status;
   575     RootedValue value(cx);
   576     Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value);
   578     /* Build a list of the recipients. */
   579     AutoObjectVector frames(cx);
   580     for (FrameRange r(frame, global); !r.empty(); r.popFront()) {
   581         if (!frames.append(r.frontFrame())) {
   582             cx->clearPendingException();
   583             return false;
   584         }
   585     }
   587     /* For each Debugger.Frame, fire its onPop handler, if any. */
   588     for (JSObject **p = frames.begin(); p != frames.end(); p++) {
   589         RootedObject frameobj(cx, *p);
   590         Debugger *dbg = Debugger::fromChildJSObject(frameobj);
   592         if (dbg->enabled &&
   593             !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) {
   594             RootedValue handler(cx, frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER));
   596             Maybe<AutoCompartment> ac;
   597             ac.construct(cx, dbg->object);
   599             RootedValue completion(cx);
   600             if (!dbg->newCompletionValue(cx, status, value, &completion)) {
   601                 status = dbg->handleUncaughtException(ac, false);
   602                 break;
   603             }
   605             /* Call the onPop handler. */
   606             RootedValue rval(cx);
   607             bool hookOk = Invoke(cx, ObjectValue(*frameobj), handler, 1, completion.address(),
   608                                  &rval);
   609             RootedValue nextValue(cx);
   610             JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval, &nextValue);
   612             /*
   613              * At this point, we are back in the debuggee compartment, and any error has
   614              * been wrapped up as a completion value.
   615              */
   616             JS_ASSERT(cx->compartment() == global->compartment());
   617             JS_ASSERT(!cx->isExceptionPending());
   619             /* JSTRAP_CONTINUE means "make no change". */
   620             if (nextStatus != JSTRAP_CONTINUE) {
   621                 status = nextStatus;
   622                 value = nextValue;
   623             }
   624         }
   625     }
   627     /*
   628      * Clean up all Debugger.Frame instances. Use a fresh FrameRange, as one
   629      * debugger's onPop handler could have caused another debugger to create its
   630      * own Debugger.Frame instance.
   631      */
   632     for (FrameRange r(frame, global); !r.empty(); r.popFront()) {
   633         RootedObject frameobj(cx, r.frontFrame());
   634         Debugger *dbg = r.frontDebugger();
   635         JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
   637         FreeOp *fop = cx->runtime()->defaultFreeOp();
   638         DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
   639         DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
   641         dbg->frames.remove(frame);
   642     }
   644     /*
   645      * If this is an eval frame, then from the debugger's perspective the
   646      * script is about to be destroyed. Remove any breakpoints in it.
   647      */
   648     if (frame.isEvalFrame()) {
   649         RootedScript script(cx, frame.script());
   650         script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), nullptr, nullptr);
   651     }
   653     /* Establish (status, value) as our resumption value. */
   654     switch (status) {
   655       case JSTRAP_RETURN:
   656         frame.setReturnValue(value);
   657         return true;
   659       case JSTRAP_THROW:
   660         cx->setPendingException(value);
   661         return false;
   663       case JSTRAP_ERROR:
   664         JS_ASSERT(!cx->isExceptionPending());
   665         return false;
   667       default:
   668         MOZ_ASSUME_UNREACHABLE("bad final trap status");
   669     }
   670 }
   672 bool
   673 Debugger::wrapEnvironment(JSContext *cx, Handle<Env*> env, MutableHandleValue rval)
   674 {
   675     if (!env) {
   676         rval.setNull();
   677         return true;
   678     }
   680     /*
   681      * DebuggerEnv should only wrap a debug scope chain obtained (transitively)
   682      * from GetDebugScopeFor(Frame|Function).
   683      */
   684     JS_ASSERT(!env->is<ScopeObject>());
   686     JSObject *envobj;
   687     DependentAddPtr<ObjectWeakMap> p(cx, environments, env);
   688     if (p) {
   689         envobj = p->value();
   690     } else {
   691         /* Create a new Debugger.Environment for env. */
   692         JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject();
   693         envobj = NewObjectWithGivenProto(cx, &DebuggerEnv_class, proto, nullptr, TenuredObject);
   694         if (!envobj)
   695             return false;
   696         envobj->setPrivateGCThing(env);
   697         envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object));
   698         if (!p.add(cx, environments, env, envobj)) {
   699             js_ReportOutOfMemory(cx);
   700             return false;
   701         }
   703         CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env);
   704         if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) {
   705             environments.remove(env);
   706             js_ReportOutOfMemory(cx);
   707             return false;
   708         }
   709     }
   710     rval.setObject(*envobj);
   711     return true;
   712 }
   714 bool
   715 Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp)
   716 {
   717     assertSameCompartment(cx, object.get());
   719     if (vp.isObject()) {
   720         RootedObject obj(cx, &vp.toObject());
   722         if (obj->is<JSFunction>()) {
   723             RootedFunction fun(cx, &obj->as<JSFunction>());
   724             if (!EnsureFunctionHasScript(cx, fun))
   725                 return false;
   726         }
   728         DependentAddPtr<ObjectWeakMap> p(cx, objects, obj);
   729         if (p) {
   730             vp.setObject(*p->value());
   731         } else {
   732             /* Create a new Debugger.Object for obj. */
   733             JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject();
   734             JSObject *dobj =
   735                 NewObjectWithGivenProto(cx, &DebuggerObject_class, proto, nullptr, TenuredObject);
   736             if (!dobj)
   737                 return false;
   738             dobj->setPrivateGCThing(obj);
   739             dobj->setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*object));
   741             if (!p.add(cx, objects, obj, dobj)) {
   742                 js_ReportOutOfMemory(cx);
   743                 return false;
   744             }
   746             if (obj->compartment() != object->compartment()) {
   747                 CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj);
   748                 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) {
   749                     objects.remove(obj);
   750                     js_ReportOutOfMemory(cx);
   751                     return false;
   752                 }
   753             }
   755             vp.setObject(*dobj);
   756         }
   757     } else if (vp.isMagic()) {
   758         RootedObject optObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
   759         if (!optObj)
   760             return false;
   762         // We handle two sentinel values: missing arguments (overloading
   763         // JS_OPTIMIZED_ARGUMENTS) and optimized out slots (JS_OPTIMIZED_OUT).
   764         // Other magic values should not have escaped.
   765         PropertyName *name;
   766         if (vp.whyMagic() == JS_OPTIMIZED_ARGUMENTS) {
   767             name = cx->names().missingArguments;
   768         } else {
   769             MOZ_ASSERT(vp.whyMagic() == JS_OPTIMIZED_OUT);
   770             name = cx->names().optimizedOut;
   771         }
   773         RootedValue trueVal(cx, BooleanValue(true));
   774         if (!JSObject::defineProperty(cx, optObj, name, trueVal))
   775             return false;
   777         vp.setObject(*optObj);
   778     } else if (!cx->compartment()->wrap(cx, vp)) {
   779         vp.setUndefined();
   780         return false;
   781     }
   783     return true;
   784 }
   786 bool
   787 Debugger::unwrapDebuggeeValue(JSContext *cx, MutableHandleValue vp)
   788 {
   789     assertSameCompartment(cx, object.get(), vp);
   790     if (vp.isObject()) {
   791         JSObject *dobj = &vp.toObject();
   792         if (dobj->getClass() != &DebuggerObject_class) {
   793             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
   794                                  "Debugger", "Debugger.Object", dobj->getClass()->name);
   795             return false;
   796         }
   798         Value owner = dobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
   799         if (owner.isUndefined() || &owner.toObject() != object) {
   800             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
   801                                  owner.isUndefined()
   802                                  ? JSMSG_DEBUG_OBJECT_PROTO
   803                                  : JSMSG_DEBUG_OBJECT_WRONG_OWNER);
   804             return false;
   805         }
   807         vp.setObject(*static_cast<JSObject*>(dobj->getPrivate()));
   808     }
   809     return true;
   810 }
   812 JSTrapStatus
   813 Debugger::handleUncaughtExceptionHelper(Maybe<AutoCompartment> &ac,
   814                                         MutableHandleValue *vp, bool callHook)
   815 {
   816     JSContext *cx = ac.ref().context()->asJSContext();
   817     if (cx->isExceptionPending()) {
   818         if (callHook && uncaughtExceptionHook) {
   819             RootedValue exc(cx);
   820             if (!cx->getPendingException(&exc))
   821                 return JSTRAP_ERROR;
   822             cx->clearPendingException();
   823             RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
   824             RootedValue rv(cx);
   825             if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv))
   826                 return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE;
   827         }
   829         if (cx->isExceptionPending()) {
   830             JS_ReportPendingException(cx);
   831             cx->clearPendingException();
   832         }
   833     }
   834     ac.destroy();
   835     return JSTRAP_ERROR;
   836 }
   838 JSTrapStatus
   839 Debugger::handleUncaughtException(Maybe<AutoCompartment> &ac, MutableHandleValue vp, bool callHook)
   840 {
   841     return handleUncaughtExceptionHelper(ac, &vp, callHook);
   842 }
   844 JSTrapStatus
   845 Debugger::handleUncaughtException(Maybe<AutoCompartment> &ac, bool callHook)
   846 {
   847     return handleUncaughtExceptionHelper(ac, nullptr, callHook);
   848 }
   850 void
   851 Debugger::resultToCompletion(JSContext *cx, bool ok, const Value &rv,
   852                              JSTrapStatus *status, MutableHandleValue value)
   853 {
   854     JS_ASSERT_IF(ok, !cx->isExceptionPending());
   856     if (ok) {
   857         *status = JSTRAP_RETURN;
   858         value.set(rv);
   859     } else if (cx->isExceptionPending()) {
   860         *status = JSTRAP_THROW;
   861         if (!cx->getPendingException(value))
   862             *status = JSTRAP_ERROR;
   863         cx->clearPendingException();
   864     } else {
   865         *status = JSTRAP_ERROR;
   866         value.setUndefined();
   867     }
   868 }
   870 bool
   871 Debugger::newCompletionValue(JSContext *cx, JSTrapStatus status, Value value_,
   872                              MutableHandleValue result)
   873 {
   874     /*
   875      * We must be in the debugger's compartment, since that's where we want
   876      * to construct the completion value.
   877      */
   878     assertSameCompartment(cx, object.get());
   880     RootedId key(cx);
   881     RootedValue value(cx, value_);
   883     switch (status) {
   884       case JSTRAP_RETURN:
   885         key = NameToId(cx->names().return_);
   886         break;
   888       case JSTRAP_THROW:
   889         key = NameToId(cx->names().throw_);
   890         break;
   892       case JSTRAP_ERROR:
   893         result.setNull();
   894         return true;
   896       default:
   897         MOZ_ASSUME_UNREACHABLE("bad status passed to Debugger::newCompletionValue");
   898     }
   900     /* Common tail for JSTRAP_RETURN and JSTRAP_THROW. */
   901     RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
   902     if (!obj ||
   903         !wrapDebuggeeValue(cx, &value) ||
   904         !DefineNativeProperty(cx, obj, key, value, JS_PropertyStub, JS_StrictPropertyStub,
   905                               JSPROP_ENUMERATE))
   906     {
   907         return false;
   908     }
   910     result.setObject(*obj);
   911     return true;
   912 }
   914 bool
   915 Debugger::receiveCompletionValue(Maybe<AutoCompartment> &ac, bool ok,
   916                                  HandleValue val,
   917                                  MutableHandleValue vp)
   918 {
   919     JSContext *cx = ac.ref().context()->asJSContext();
   921     JSTrapStatus status;
   922     RootedValue value(cx);
   923     resultToCompletion(cx, ok, val, &status, &value);
   924     ac.destroy();
   925     return newCompletionValue(cx, status, value, vp);
   926 }
   928 JSTrapStatus
   929 Debugger::parseResumptionValue(Maybe<AutoCompartment> &ac, bool ok, const Value &rv, MutableHandleValue vp,
   930                                bool callHook)
   931 {
   932     vp.setUndefined();
   933     if (!ok)
   934         return handleUncaughtException(ac, vp, callHook);
   935     if (rv.isUndefined()) {
   936         ac.destroy();
   937         return JSTRAP_CONTINUE;
   938     }
   939     if (rv.isNull()) {
   940         ac.destroy();
   941         return JSTRAP_ERROR;
   942     }
   944     /* Check that rv is {return: val} or {throw: val}. */
   945     JSContext *cx = ac.ref().context()->asJSContext();
   946     Rooted<JSObject*> obj(cx);
   947     RootedShape shape(cx);
   948     RootedId returnId(cx, NameToId(cx->names().return_));
   949     RootedId throwId(cx, NameToId(cx->names().throw_));
   950     bool okResumption = rv.isObject();
   951     if (okResumption) {
   952         obj = &rv.toObject();
   953         okResumption = obj->is<JSObject>();
   954     }
   955     if (okResumption) {
   956         shape = obj->lastProperty();
   957         okResumption = shape->previous() &&
   958              !shape->previous()->previous() &&
   959              (shape->propid() == returnId || shape->propid() == throwId) &&
   960              shape->isDataDescriptor();
   961     }
   962     if (!okResumption) {
   963         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_RESUMPTION);
   964         return handleUncaughtException(ac, vp, callHook);
   965     }
   967     RootedValue v(cx, vp.get());
   968     if (!NativeGet(cx, obj, obj, shape, &v) || !unwrapDebuggeeValue(cx, &v))
   969         return handleUncaughtException(ac, &v, callHook);
   971     ac.destroy();
   972     if (!cx->compartment()->wrap(cx, &v)) {
   973         vp.setUndefined();
   974         return JSTRAP_ERROR;
   975     }
   976     vp.set(v);
   978     return shape->propid() == returnId ? JSTRAP_RETURN : JSTRAP_THROW;
   979 }
   981 static bool
   982 CallMethodIfPresent(JSContext *cx, HandleObject obj, const char *name, int argc, Value *argv,
   983                     MutableHandleValue rval)
   984 {
   985     rval.setUndefined();
   986     JSAtom *atom = Atomize(cx, name, strlen(name));
   987     if (!atom)
   988         return false;
   990     RootedId id(cx, AtomToId(atom));
   991     RootedValue fval(cx);
   992     return JSObject::getGeneric(cx, obj, obj, id, &fval) &&
   993            (!js_IsCallable(fval) || Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval));
   994 }
   996 JSTrapStatus
   997 Debugger::fireDebuggerStatement(JSContext *cx, MutableHandleValue vp)
   998 {
   999     RootedObject hook(cx, getHook(OnDebuggerStatement));
  1000     JS_ASSERT(hook);
  1001     JS_ASSERT(hook->isCallable());
  1003     Maybe<AutoCompartment> ac;
  1004     ac.construct(cx, object);
  1006     ScriptFrameIter iter(cx);
  1008     RootedValue scriptFrame(cx);
  1009     if (!getScriptFrame(cx, iter, &scriptFrame))
  1010         return handleUncaughtException(ac, false);
  1012     RootedValue rv(cx);
  1013     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv);
  1014     return parseResumptionValue(ac, ok, rv, vp);
  1017 JSTrapStatus
  1018 Debugger::fireExceptionUnwind(JSContext *cx, MutableHandleValue vp)
  1020     RootedObject hook(cx, getHook(OnExceptionUnwind));
  1021     JS_ASSERT(hook);
  1022     JS_ASSERT(hook->isCallable());
  1024     RootedValue exc(cx);
  1025     if (!cx->getPendingException(&exc))
  1026         return JSTRAP_ERROR;
  1027     cx->clearPendingException();
  1029     Maybe<AutoCompartment> ac;
  1030     ac.construct(cx, object);
  1032     JS::AutoValueArray<2> argv(cx);
  1033     argv[0].setUndefined();
  1034     argv[1].set(exc);
  1036     ScriptFrameIter iter(cx);
  1037     if (!getScriptFrame(cx, iter, argv[0]) || !wrapDebuggeeValue(cx, argv[1]))
  1038         return handleUncaughtException(ac, false);
  1040     RootedValue rv(cx);
  1041     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 2, argv.begin(), &rv);
  1042     JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp);
  1043     if (st == JSTRAP_CONTINUE)
  1044         cx->setPendingException(exc);
  1045     return st;
  1048 JSTrapStatus
  1049 Debugger::fireEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp)
  1051     RootedObject hook(cx, getHook(OnEnterFrame));
  1052     JS_ASSERT(hook);
  1053     JS_ASSERT(hook->isCallable());
  1055     Maybe<AutoCompartment> ac;
  1056     ac.construct(cx, object);
  1058     RootedValue scriptFrame(cx);
  1059     if (!getScriptFrame(cx, frame, &scriptFrame))
  1060         return handleUncaughtException(ac, false);
  1062     RootedValue rv(cx);
  1063     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv);
  1064     return parseResumptionValue(ac, ok, rv, vp);
  1067 void
  1068 Debugger::fireNewScript(JSContext *cx, HandleScript script)
  1070     RootedObject hook(cx, getHook(OnNewScript));
  1071     JS_ASSERT(hook);
  1072     JS_ASSERT(hook->isCallable());
  1074     Maybe<AutoCompartment> ac;
  1075     ac.construct(cx, object);
  1077     JSObject *dsobj = wrapScript(cx, script);
  1078     if (!dsobj) {
  1079         handleUncaughtException(ac, false);
  1080         return;
  1083     RootedValue scriptObject(cx, ObjectValue(*dsobj));
  1084     RootedValue rv(cx);
  1085     if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptObject.address(), &rv))
  1086         handleUncaughtException(ac, true);
  1089 JSTrapStatus
  1090 Debugger::dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which)
  1092     JS_ASSERT(which == OnDebuggerStatement || which == OnExceptionUnwind);
  1094     /*
  1095      * Determine which debuggers will receive this event, and in what order.
  1096      * Make a copy of the list, since the original is mutable and we will be
  1097      * calling into arbitrary JS.
  1099      * Note: In the general case, 'triggered' contains references to objects in
  1100      * different compartments--every compartment *except* this one.
  1101      */
  1102     AutoValueVector triggered(cx);
  1103     Handle<GlobalObject*> global = cx->global();
  1104     if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
  1105         for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
  1106             Debugger *dbg = *p;
  1107             if (dbg->enabled && dbg->getHook(which)) {
  1108                 if (!triggered.append(ObjectValue(*dbg->toJSObject())))
  1109                     return JSTRAP_ERROR;
  1114     /*
  1115      * Deliver the event to each debugger, checking again to make sure it
  1116      * should still be delivered.
  1117      */
  1118     for (Value *p = triggered.begin(); p != triggered.end(); p++) {
  1119         Debugger *dbg = Debugger::fromJSObject(&p->toObject());
  1120         if (dbg->debuggees.has(global) && dbg->enabled && dbg->getHook(which)) {
  1121             JSTrapStatus st = (which == OnDebuggerStatement)
  1122                               ? dbg->fireDebuggerStatement(cx, vp)
  1123                               : dbg->fireExceptionUnwind(cx, vp);
  1124             if (st != JSTRAP_CONTINUE)
  1125                 return st;
  1128     return JSTRAP_CONTINUE;
  1131 static bool
  1132 AddNewScriptRecipients(GlobalObject::DebuggerVector *src, HandleScript script,
  1133                        AutoValueVector *dest)
  1135     bool wasEmpty = dest->length() == 0;
  1136     for (Debugger **p = src->begin(); p != src->end(); p++) {
  1137         Debugger *dbg = *p;
  1138         Value v = ObjectValue(*dbg->toJSObject());
  1139         if (dbg->observesScript(script) && dbg->observesNewScript() &&
  1140             (wasEmpty || Find(dest->begin(), dest->end(), v) == dest->end()) &&
  1141             !dest->append(v))
  1143             return false;
  1146     return true;
  1149 void
  1150 Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal_)
  1152     Rooted<GlobalObject*> compileAndGoGlobal(cx, compileAndGoGlobal_);
  1154     JS_ASSERT(script->compileAndGo() == !!compileAndGoGlobal);
  1156     /*
  1157      * Build the list of recipients. For compile-and-go scripts, this is the
  1158      * same as the generic Debugger::dispatchHook code, but non-compile-and-go
  1159      * scripts are not tied to particular globals. We deliver them to every
  1160      * debugger observing any global in the script's compartment.
  1161      */
  1162     AutoValueVector triggered(cx);
  1163     if (script->compileAndGo()) {
  1164         if (GlobalObject::DebuggerVector *debuggers = compileAndGoGlobal->getDebuggers()) {
  1165             if (!AddNewScriptRecipients(debuggers, script, &triggered))
  1166                 return;
  1168     } else {
  1169         GlobalObjectSet &debuggees = script->compartment()->getDebuggees();
  1170         for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
  1171             if (!AddNewScriptRecipients(r.front()->getDebuggers(), script, &triggered))
  1172                 return;
  1176     /*
  1177      * Deliver the event to each debugger, checking again as in
  1178      * Debugger::dispatchHook.
  1179      */
  1180     for (Value *p = triggered.begin(); p != triggered.end(); p++) {
  1181         Debugger *dbg = Debugger::fromJSObject(&p->toObject());
  1182         if ((!compileAndGoGlobal || dbg->debuggees.has(compileAndGoGlobal)) &&
  1183             dbg->enabled && dbg->getHook(OnNewScript)) {
  1184             dbg->fireNewScript(cx, script);
  1189 JSTrapStatus
  1190 Debugger::onTrap(JSContext *cx, MutableHandleValue vp)
  1192     MOZ_ASSERT(cx->compartment()->debugMode());
  1194     ScriptFrameIter iter(cx);
  1195     RootedScript script(cx, iter.script());
  1196     Rooted<GlobalObject*> scriptGlobal(cx, &script->global());
  1197     jsbytecode *pc = iter.pc();
  1198     BreakpointSite *site = script->getBreakpointSite(pc);
  1199     JSOp op = JSOp(*pc);
  1201     /* Build list of breakpoint handlers. */
  1202     Vector<Breakpoint *> triggered(cx);
  1203     for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
  1204         if (!triggered.append(bp))
  1205             return JSTRAP_ERROR;
  1208     for (Breakpoint **p = triggered.begin(); p != triggered.end(); p++) {
  1209         Breakpoint *bp = *p;
  1211         /* Handlers can clear breakpoints. Check that bp still exists. */
  1212         if (!site || !site->hasBreakpoint(bp))
  1213             continue;
  1216         /*
  1217          * There are two reasons we have to check whether dbg is enabled and
  1218          * debugging scriptGlobal.
  1220          * One is just that one breakpoint handler can disable other Debuggers
  1221          * or remove debuggees.
  1223          * The other has to do with non-compile-and-go scripts, which have no
  1224          * specific global--until they are executed. Only now do we know which
  1225          * global the script is running against.
  1226          */
  1227         Debugger *dbg = bp->debugger;
  1228         if (dbg->enabled && dbg->debuggees.lookup(scriptGlobal)) {
  1229             Maybe<AutoCompartment> ac;
  1230             ac.construct(cx, dbg->object);
  1232             RootedValue scriptFrame(cx);
  1233             if (!dbg->getScriptFrame(cx, iter, &scriptFrame))
  1234                 return dbg->handleUncaughtException(ac, false);
  1235             RootedValue rv(cx);
  1236             Rooted<JSObject*> handler(cx, bp->handler);
  1237             bool ok = CallMethodIfPresent(cx, handler, "hit", 1, scriptFrame.address(), &rv);
  1238             JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true);
  1239             if (st != JSTRAP_CONTINUE)
  1240                 return st;
  1242             /* Calling JS code invalidates site. Reload it. */
  1243             site = script->getBreakpointSite(pc);
  1247     if (site && site->trapHandler) {
  1248         JSTrapStatus st = site->trapHandler(cx, script, pc, vp.address(), site->trapClosure);
  1249         if (st != JSTRAP_CONTINUE)
  1250             return st;
  1253     /* By convention, return the true op to the interpreter in vp. */
  1254     vp.setInt32(op);
  1255     return JSTRAP_CONTINUE;
  1258 JSTrapStatus
  1259 Debugger::onSingleStep(JSContext *cx, MutableHandleValue vp)
  1261     ScriptFrameIter iter(cx);
  1263     /*
  1264      * We may be stepping over a JSOP_EXCEPTION, that pushes the context's
  1265      * pending exception for a 'catch' clause to handle. Don't let the
  1266      * onStep handlers mess with that (other than by returning a resumption
  1267      * value).
  1268      */
  1269     RootedValue exception(cx, UndefinedValue());
  1270     bool exceptionPending = cx->isExceptionPending();
  1271     if (exceptionPending) {
  1272         if (!cx->getPendingException(&exception))
  1273             return JSTRAP_ERROR;
  1274         cx->clearPendingException();
  1277     /*
  1278      * Build list of Debugger.Frame instances referring to this frame with
  1279      * onStep handlers.
  1280      */
  1281     AutoObjectVector frames(cx);
  1282     for (FrameRange r(iter.abstractFramePtr()); !r.empty(); r.popFront()) {
  1283         JSObject *frame = r.frontFrame();
  1284         if (!frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() &&
  1285             !frames.append(frame))
  1287             return JSTRAP_ERROR;
  1291 #ifdef DEBUG
  1292     /*
  1293      * Validate the single-step count on this frame's script, to ensure that
  1294      * we're not receiving traps we didn't ask for. Even when frames is
  1295      * non-empty (and thus we know this trap was requested), do the check
  1296      * anyway, to make sure the count has the correct non-zero value.
  1298      * The converse --- ensuring that we do receive traps when we should --- can
  1299      * be done with unit tests.
  1300      */
  1302         uint32_t stepperCount = 0;
  1303         JSScript *trappingScript = iter.script();
  1304         GlobalObject *global = cx->global();
  1305         if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
  1306             for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
  1307                 Debugger *dbg = *p;
  1308                 for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
  1309                     AbstractFramePtr frame = r.front().key();
  1310                     JSObject *frameobj = r.front().value();
  1311                     if (frame.script() == trappingScript &&
  1312                         !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
  1314                         stepperCount++;
  1319         if (trappingScript->compileAndGo())
  1320             JS_ASSERT(stepperCount == trappingScript->stepModeCount());
  1321         else
  1322             JS_ASSERT(stepperCount <= trappingScript->stepModeCount());
  1324 #endif
  1326     /* Preserve the debuggee's iterValue while handlers run. */
  1327     class PreserveIterValue {
  1328         JSContext *cx;
  1329         RootedValue savedIterValue;
  1331       public:
  1332         PreserveIterValue(JSContext *cx) : cx(cx), savedIterValue(cx, cx->iterValue) {
  1333             cx->iterValue.setMagic(JS_NO_ITER_VALUE);
  1335         ~PreserveIterValue() {
  1336             cx->iterValue = savedIterValue;
  1338     };
  1339     PreserveIterValue piv(cx);
  1341     /* Call all the onStep handlers we found. */
  1342     for (JSObject **p = frames.begin(); p != frames.end(); p++) {
  1343         RootedObject frame(cx, *p);
  1344         Debugger *dbg = Debugger::fromChildJSObject(frame);
  1346         Maybe<AutoCompartment> ac;
  1347         ac.construct(cx, dbg->object);
  1349         const Value &handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
  1350         RootedValue rval(cx);
  1351         bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, nullptr, &rval);
  1352         JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp);
  1353         if (st != JSTRAP_CONTINUE)
  1354             return st;
  1357     vp.setUndefined();
  1358     if (exceptionPending)
  1359         cx->setPendingException(exception);
  1360     return JSTRAP_CONTINUE;
  1363 JSTrapStatus
  1364 Debugger::fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, MutableHandleValue vp)
  1366     RootedObject hook(cx, getHook(OnNewGlobalObject));
  1367     JS_ASSERT(hook);
  1368     JS_ASSERT(hook->isCallable());
  1370     Maybe<AutoCompartment> ac;
  1371     ac.construct(cx, object);
  1373     RootedValue wrappedGlobal(cx, ObjectValue(*global));
  1374     if (!wrapDebuggeeValue(cx, &wrappedGlobal))
  1375         return handleUncaughtException(ac, false);
  1377     RootedValue rv(cx);
  1379     // onNewGlobalObject is infallible, and thus is only allowed to return
  1380     // undefined as a resumption value. If it returns anything else, we throw.
  1381     // And if that happens, or if the hook itself throws, we invoke the
  1382     // uncaughtExceptionHook so that we never leave an exception pending on the
  1383     // cx. This allows JS_NewGlobalObject to avoid handling failures from debugger
  1384     // hooks.
  1385     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, wrappedGlobal.address(), &rv);
  1386     if (ok && !rv.isUndefined()) {
  1387         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  1388                              JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
  1389         ok = false;
  1391     // NB: Even though we don't care about what goes into it, we have to pass vp
  1392     // to handleUncaughtException so that it parses resumption values from the
  1393     // uncaughtExceptionHook and tells the caller whether we should execute the
  1394     // rest of the onNewGlobalObject hooks or not.
  1395     JSTrapStatus status = ok ? JSTRAP_CONTINUE
  1396                              : handleUncaughtException(ac, vp, true);
  1397     JS_ASSERT(!cx->isExceptionPending());
  1398     return status;
  1401 void
  1402 Debugger::slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global)
  1404     JS_ASSERT(!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers));
  1405     if (global->compartment()->options().invisibleToDebugger())
  1406         return;
  1408     /*
  1409      * Make a copy of the runtime's onNewGlobalObjectWatchers before running the
  1410      * handlers. Since one Debugger's handler can disable another's, the list
  1411      * can be mutated while we're walking it.
  1412      */
  1413     AutoObjectVector watchers(cx);
  1414     for (JSCList *link = JS_LIST_HEAD(&cx->runtime()->onNewGlobalObjectWatchers);
  1415          link != &cx->runtime()->onNewGlobalObjectWatchers;
  1416          link = JS_NEXT_LINK(link)) {
  1417         Debugger *dbg = fromOnNewGlobalObjectWatchersLink(link);
  1418         JS_ASSERT(dbg->observesNewGlobalObject());
  1419         if (!watchers.append(dbg->object))
  1420             return;
  1423     JSTrapStatus status = JSTRAP_CONTINUE;
  1424     RootedValue value(cx);
  1426     for (size_t i = 0; i < watchers.length(); i++) {
  1427         Debugger *dbg = fromJSObject(watchers[i]);
  1429         // We disallow resumption values from onNewGlobalObject hooks, because we
  1430         // want the debugger hooks for global object creation to be infallible.
  1431         // But if an onNewGlobalObject hook throws, and the uncaughtExceptionHook
  1432         // decides to raise an error, we want to at least avoid invoking the rest
  1433         // of the onNewGlobalObject handlers in the list (not for any super
  1434         // compelling reason, just because it seems like the right thing to do).
  1435         // So we ignore whatever comes out in |value|, but break out of the loop
  1436         // if a non-success trap status is returned.
  1437         if (dbg->observesNewGlobalObject()) {
  1438             status = dbg->fireNewGlobalObject(cx, global, &value);
  1439             if (status != JSTRAP_CONTINUE && status != JSTRAP_RETURN)
  1440                 break;
  1443     JS_ASSERT(!cx->isExceptionPending());
  1447 /*** Debugger JSObjects **************************************************************************/
  1449 void
  1450 Debugger::markKeysInCompartment(JSTracer *trc)
  1452     /*
  1453      * WeakMap::Range is deliberately private, to discourage C++ code from
  1454      * enumerating WeakMap keys. However in this case we need access, so we
  1455      * make a base-class reference. Range is public in HashMap.
  1456      */
  1457     objects.markKeys(trc);
  1458     environments.markKeys(trc);
  1459     scripts.markKeys(trc);
  1460     sources.markKeys(trc);
  1463 /*
  1464  * Ordinarily, WeakMap keys and values are marked because at some point it was
  1465  * discovered that the WeakMap was live; that is, some object containing the
  1466  * WeakMap was marked during mark phase.
  1468  * However, during compartment GC, we have to do something about
  1469  * cross-compartment WeakMaps in non-GC'd compartments. If their keys and values
  1470  * might need to be marked, we have to do it manually.
  1472  * Each Debugger object keeps found cross-compartment WeakMaps: objects, scripts,
  1473  * script source objects, and environments. They have the nice property that all
  1474  * their values are in the same compartment as the Debugger object, so we only
  1475  * need to mark the keys. We must simply mark all keys that are in a compartment
  1476  * being GC'd.
  1478  * We must scan all Debugger objects regardless of whether they *currently*
  1479  * have any debuggees in a compartment being GC'd, because the WeakMap
  1480  * entries persist even when debuggees are removed.
  1482  * This happens during the initial mark phase, not iterative marking, because
  1483  * all the edges being reported here are strong references.
  1484  */
  1485 void
  1486 Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *trc)
  1488     JSRuntime *rt = trc->runtime();
  1490     /*
  1491      * Mark all objects in comp that are referents of Debugger.Objects in other
  1492      * compartments.
  1493      */
  1494     for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
  1495         if (!dbg->object->zone()->isCollecting())
  1496             dbg->markKeysInCompartment(trc);
  1500 /*
  1501  * This method has two tasks:
  1502  *   1. Mark Debugger objects that are unreachable except for debugger hooks that
  1503  *      may yet be called.
  1504  *   2. Mark breakpoint handlers.
  1506  * This happens during the iterative part of the GC mark phase. This method
  1507  * returns true if it has to mark anything; GC calls it repeatedly until it
  1508  * returns false.
  1509  */
  1510 bool
  1511 Debugger::markAllIteratively(GCMarker *trc)
  1513     bool markedAny = false;
  1515     /*
  1516      * Find all Debugger objects in danger of GC. This code is a little
  1517      * convoluted since the easiest way to find them is via their debuggees.
  1518      */
  1519     JSRuntime *rt = trc->runtime();
  1520     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
  1521         GlobalObjectSet &debuggees = c->getDebuggees();
  1522         for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
  1523             GlobalObject *global = e.front();
  1524             if (!IsObjectMarked(&global))
  1525                 continue;
  1526             else if (global != e.front())
  1527                 e.rekeyFront(global);
  1529             /*
  1530              * Every debuggee has at least one debugger, so in this case
  1531              * getDebuggers can't return nullptr.
  1532              */
  1533             const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
  1534             JS_ASSERT(debuggers);
  1535             for (Debugger * const *p = debuggers->begin(); p != debuggers->end(); p++) {
  1536                 Debugger *dbg = *p;
  1538                 /*
  1539                  * dbg is a Debugger with at least one debuggee. Check three things:
  1540                  *   - dbg is actually in a compartment that is being marked
  1541                  *   - it isn't already marked
  1542                  *   - it actually has hooks that might be called
  1543                  */
  1544                 HeapPtrObject &dbgobj = dbg->toJSObjectRef();
  1545                 if (!dbgobj->zone()->isGCMarking())
  1546                     continue;
  1548                 bool dbgMarked = IsObjectMarked(&dbgobj);
  1549                 if (!dbgMarked && dbg->hasAnyLiveHooks()) {
  1550                     /*
  1551                      * obj could be reachable only via its live, enabled
  1552                      * debugger hooks, which may yet be called.
  1553                      */
  1554                     MarkObject(trc, &dbgobj, "enabled Debugger");
  1555                     markedAny = true;
  1556                     dbgMarked = true;
  1559                 if (dbgMarked) {
  1560                     /* Search for breakpoints to mark. */
  1561                     for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
  1562                         if (IsScriptMarked(&bp->site->script)) {
  1563                             /*
  1564                              * The debugger and the script are both live.
  1565                              * Therefore the breakpoint handler is live.
  1566                              */
  1567                             if (!IsObjectMarked(&bp->getHandlerRef())) {
  1568                                 MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler");
  1569                                 markedAny = true;
  1577     return markedAny;
  1580 /*
  1581  * Mark all debugger-owned GC things unconditionally. This is used by the minor
  1582  * GC: the minor GC cannot apply the weak constraints of the full GC because it
  1583  * visits only part of the heap.
  1584  */
  1585 void
  1586 Debugger::markAll(JSTracer *trc)
  1588     JSRuntime *rt = trc->runtime();
  1589     for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
  1590         GlobalObjectSet &debuggees = dbg->debuggees;
  1591         for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
  1592             GlobalObject *global = e.front();
  1594             MarkObjectUnbarriered(trc, &global, "Global Object");
  1595             if (global != e.front())
  1596                 e.rekeyFront(global);
  1599         HeapPtrObject &dbgobj = dbg->toJSObjectRef();
  1600         MarkObject(trc, &dbgobj, "Debugger Object");
  1602         dbg->scripts.trace(trc);
  1603         dbg->sources.trace(trc);
  1604         dbg->objects.trace(trc);
  1605         dbg->environments.trace(trc);
  1607         for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
  1608             MarkScriptUnbarriered(trc, &bp->site->script, "breakpoint script");
  1609             MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler");
  1614 void
  1615 Debugger::traceObject(JSTracer *trc, JSObject *obj)
  1617     if (Debugger *dbg = Debugger::fromJSObject(obj))
  1618         dbg->trace(trc);
  1621 void
  1622 Debugger::trace(JSTracer *trc)
  1624     if (uncaughtExceptionHook)
  1625         MarkObject(trc, &uncaughtExceptionHook, "hooks");
  1627     /*
  1628      * Mark Debugger.Frame objects. These are all reachable from JS, because the
  1629      * corresponding JS frames are still on the stack.
  1631      * (Once we support generator frames properly, we will need
  1632      * weakly-referenced Debugger.Frame objects as well, for suspended generator
  1633      * frames.)
  1634      */
  1635     for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
  1636         RelocatablePtrObject &frameobj = r.front().value();
  1637         JS_ASSERT(frameobj->getPrivate());
  1638         MarkObject(trc, &frameobj, "live Debugger.Frame");
  1641     /* Trace the weak map from JSScript instances to Debugger.Script objects. */
  1642     scripts.trace(trc);
  1644     /* Trace the referent ->Debugger.Source weak map */
  1645     sources.trace(trc);
  1647     /* Trace the referent -> Debugger.Object weak map. */
  1648     objects.trace(trc);
  1650     /* Trace the referent -> Debugger.Environment weak map. */
  1651     environments.trace(trc);
  1654 void
  1655 Debugger::sweepAll(FreeOp *fop)
  1657     JSRuntime *rt = fop->runtime();
  1659     for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
  1660         if (IsObjectAboutToBeFinalized(&dbg->object)) {
  1661             /*
  1662              * dbg is being GC'd. Detach it from its debuggees. The debuggee
  1663              * might be GC'd too. Since detaching requires access to both
  1664              * objects, this must be done before finalize time.
  1665              */
  1666             for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
  1667                 // We can't recompile on-stack scripts here, and we
  1668                 // can only toggle debug mode to off, so we use an
  1669                 // infallible variant of removeDebuggeeGlobal.
  1670                 dbg->removeDebuggeeGlobalUnderGC(fop, e.front(), nullptr, &e);
  1675     for (gc::GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) {
  1676         /* For each debuggee being GC'd, detach it from all its debuggers. */
  1677         GlobalObjectSet &debuggees = comp->getDebuggees();
  1678         for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
  1679             GlobalObject *global = e.front();
  1680             if (IsObjectAboutToBeFinalized(&global)) {
  1681                 // See infallibility note above.
  1682                 detachAllDebuggersFromGlobal(fop, global, &e);
  1684             else if (global != e.front())
  1685                 e.rekeyFront(global);
  1690 void
  1691 Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global,
  1692                                        GlobalObjectSet::Enum *compartmentEnum)
  1694     const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
  1695     JS_ASSERT(!debuggers->empty());
  1696     while (!debuggers->empty())
  1697         debuggers->back()->removeDebuggeeGlobalUnderGC(fop, global, compartmentEnum, nullptr);
  1700 /* static */ void
  1701 Debugger::findCompartmentEdges(Zone *zone, js::gc::ComponentFinder<Zone> &finder)
  1703     /*
  1704      * For debugger cross compartment wrappers, add edges in the opposite
  1705      * direction to those already added by JSCompartment::findOutgoingEdges.
  1706      * This ensure that debuggers and their debuggees are finalized in the same
  1707      * group.
  1708      */
  1709     for (Debugger *dbg = zone->runtimeFromMainThread()->debuggerList.getFirst();
  1710          dbg;
  1711          dbg = dbg->getNext())
  1713         Zone *w = dbg->object->zone();
  1714         if (w == zone || !w->isGCMarking())
  1715             continue;
  1716         if (dbg->scripts.hasKeyInZone(zone) ||
  1717             dbg->sources.hasKeyInZone(zone) ||
  1718             dbg->objects.hasKeyInZone(zone) ||
  1719             dbg->environments.hasKeyInZone(zone))
  1721             finder.addEdgeTo(w);
  1726 void
  1727 Debugger::finalize(FreeOp *fop, JSObject *obj)
  1729     Debugger *dbg = fromJSObject(obj);
  1730     if (!dbg)
  1731         return;
  1732     fop->delete_(dbg);
  1735 const Class Debugger::jsclass = {
  1736     "Debugger",
  1737     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
  1738     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
  1739     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  1740     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Debugger::finalize,
  1741     nullptr,              /* call        */
  1742     nullptr,              /* hasInstance */
  1743     nullptr,              /* construct   */
  1744     Debugger::traceObject
  1745 };
  1747 Debugger *
  1748 Debugger::fromThisValue(JSContext *cx, const CallArgs &args, const char *fnname)
  1750     if (!args.thisv().isObject()) {
  1751         ReportObjectRequired(cx);
  1752         return nullptr;
  1754     JSObject *thisobj = &args.thisv().toObject();
  1755     if (thisobj->getClass() != &Debugger::jsclass) {
  1756         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  1757                              "Debugger", fnname, thisobj->getClass()->name);
  1758         return nullptr;
  1761     /*
  1762      * Forbid Debugger.prototype, which is of the Debugger JSClass but isn't
  1763      * really a Debugger object. The prototype object is distinguished by
  1764      * having a nullptr private value.
  1765      */
  1766     Debugger *dbg = fromJSObject(thisobj);
  1767     if (!dbg) {
  1768         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  1769                              "Debugger", fnname, "prototype object");
  1771     return dbg;
  1774 #define THIS_DEBUGGER(cx, argc, vp, fnname, args, dbg)                       \
  1775     CallArgs args = CallArgsFromVp(argc, vp);                                \
  1776     Debugger *dbg = Debugger::fromThisValue(cx, args, fnname);               \
  1777     if (!dbg)                                                                \
  1778         return false
  1780 bool
  1781 Debugger::getEnabled(JSContext *cx, unsigned argc, Value *vp)
  1783     THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg);
  1784     args.rval().setBoolean(dbg->enabled);
  1785     return true;
  1788 bool
  1789 Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp)
  1791     REQUIRE_ARGC("Debugger.set enabled", 1);
  1792     THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg);
  1793     bool enabled = ToBoolean(args[0]);
  1795     if (enabled != dbg->enabled) {
  1796         for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
  1797             if (enabled)
  1798                 bp->site->inc(cx->runtime()->defaultFreeOp());
  1799             else
  1800                 bp->site->dec(cx->runtime()->defaultFreeOp());
  1803         /*
  1804          * Add or remove ourselves from the runtime's list of Debuggers
  1805          * that care about new globals.
  1806          */
  1807         if (dbg->getHook(OnNewGlobalObject)) {
  1808             if (enabled) {
  1809                 /* If we were not enabled, the link should be a singleton list. */
  1810                 JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
  1811                 JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
  1812                                &cx->runtime()->onNewGlobalObjectWatchers);
  1813             } else {
  1814                 /* If we were enabled, the link should be inserted in the list. */
  1815                 JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
  1816                 JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
  1821     dbg->enabled = enabled;
  1822     args.rval().setUndefined();
  1823     return true;
  1826 bool
  1827 Debugger::getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which)
  1829     JS_ASSERT(which >= 0 && which < HookCount);
  1830     THIS_DEBUGGER(cx, argc, vp, "getHook", args, dbg);
  1831     args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + which));
  1832     return true;
  1835 bool
  1836 Debugger::setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which)
  1838     JS_ASSERT(which >= 0 && which < HookCount);
  1839     REQUIRE_ARGC("Debugger.setHook", 1);
  1840     THIS_DEBUGGER(cx, argc, vp, "setHook", args, dbg);
  1841     if (args[0].isObject()) {
  1842         if (!args[0].toObject().isCallable())
  1843             return ReportIsNotFunction(cx, args[0], args.length() - 1);
  1844     } else if (!args[0].isUndefined()) {
  1845         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
  1846         return false;
  1848     dbg->object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]);
  1849     args.rval().setUndefined();
  1850     return true;
  1853 bool
  1854 Debugger::getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
  1856     return getHookImpl(cx, argc, vp, OnDebuggerStatement);
  1859 bool
  1860 Debugger::setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
  1862     return setHookImpl(cx, argc, vp, OnDebuggerStatement);
  1865 bool
  1866 Debugger::getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp)
  1868     return getHookImpl(cx, argc, vp, OnExceptionUnwind);
  1871 bool
  1872 Debugger::setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp)
  1874     return setHookImpl(cx, argc, vp, OnExceptionUnwind);
  1877 bool
  1878 Debugger::getOnNewScript(JSContext *cx, unsigned argc, Value *vp)
  1880     return getHookImpl(cx, argc, vp, OnNewScript);
  1883 bool
  1884 Debugger::setOnNewScript(JSContext *cx, unsigned argc, Value *vp)
  1886     return setHookImpl(cx, argc, vp, OnNewScript);
  1889 bool
  1890 Debugger::getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
  1892     return getHookImpl(cx, argc, vp, OnEnterFrame);
  1895 bool
  1896 Debugger::setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
  1898     return setHookImpl(cx, argc, vp, OnEnterFrame);
  1901 bool
  1902 Debugger::getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp)
  1904     return getHookImpl(cx, argc, vp, OnNewGlobalObject);
  1907 bool
  1908 Debugger::setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp)
  1910     THIS_DEBUGGER(cx, argc, vp, "setOnNewGlobalObject", args, dbg);
  1911     RootedObject oldHook(cx, dbg->getHook(OnNewGlobalObject));
  1913     if (!setHookImpl(cx, argc, vp, OnNewGlobalObject))
  1914         return false;
  1916     /*
  1917      * Add or remove ourselves from the runtime's list of Debuggers that
  1918      * care about new globals.
  1919      */
  1920     if (dbg->enabled) {
  1921         JSObject *newHook = dbg->getHook(OnNewGlobalObject);
  1922         if (!oldHook && newHook) {
  1923             /* If we didn't have a hook, the link should be a singleton list. */
  1924             JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
  1925             JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
  1926                            &cx->runtime()->onNewGlobalObjectWatchers);
  1927         } else if (oldHook && !newHook) {
  1928             /* If we did have a hook, the link should be inserted in the list. */
  1929             JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
  1930             JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
  1934     return true;
  1937 bool
  1938 Debugger::getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
  1940     THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg);
  1941     args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
  1942     return true;
  1945 bool
  1946 Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
  1948     REQUIRE_ARGC("Debugger.set uncaughtExceptionHook", 1);
  1949     THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg);
  1950     if (!args[0].isNull() && (!args[0].isObject() || !args[0].toObject().isCallable())) {
  1951         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ASSIGN_FUNCTION_OR_NULL,
  1952                              "uncaughtExceptionHook");
  1953         return false;
  1955     dbg->uncaughtExceptionHook = args[0].toObjectOrNull();
  1956     args.rval().setUndefined();
  1957     return true;
  1959 bool
  1960 Debugger::getMemory(JSContext *cx, unsigned argc, Value *vp)
  1962     THIS_DEBUGGER(cx, argc, vp, "get memory", args, dbg);
  1963     args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE));
  1964     return true;
  1967 GlobalObject *
  1968 Debugger::unwrapDebuggeeArgument(JSContext *cx, const Value &v)
  1970     if (!v.isObject()) {
  1971         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
  1972                              "argument", "not a global object");
  1973         return nullptr;
  1976     RootedObject obj(cx, &v.toObject());
  1978     /* If it's a Debugger.Object belonging to this debugger, dereference that. */
  1979     if (obj->getClass() == &DebuggerObject_class) {
  1980         RootedValue rv(cx, v);
  1981         if (!unwrapDebuggeeValue(cx, &rv))
  1982             return nullptr;
  1983         obj = &rv.toObject();
  1986     /* If we have a cross-compartment wrapper, dereference as far as is secure. */
  1987     obj = CheckedUnwrap(obj);
  1988     if (!obj) {
  1989         JS_ReportError(cx, "Permission denied to access object");
  1990         return nullptr;
  1993     /* If that produced an outer window, innerize it. */
  1994     obj = GetInnerObject(cx, obj);
  1995     if (!obj)
  1996         return nullptr;
  1998     /* If that didn't produce a global object, it's an error. */
  1999     if (!obj->is<GlobalObject>()) {
  2000         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
  2001                              "argument", "not a global object");
  2002         return nullptr;
  2005     return &obj->as<GlobalObject>();
  2008 bool
  2009 Debugger::addDebuggee(JSContext *cx, unsigned argc, Value *vp)
  2011     REQUIRE_ARGC("Debugger.addDebuggee", 1);
  2012     THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg);
  2013     Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  2014     if (!global)
  2015         return false;
  2017     if (!dbg->addDebuggeeGlobal(cx, global))
  2018         return false;
  2020     RootedValue v(cx, ObjectValue(*global));
  2021     if (!dbg->wrapDebuggeeValue(cx, &v))
  2022         return false;
  2023     args.rval().set(v);
  2024     return true;
  2027 bool
  2028 Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp)
  2030     THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg);
  2031     for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
  2032         // Invalidate a zone at a time to avoid doing a zone-wide CellIter
  2033         // per compartment.
  2034         AutoDebugModeInvalidation invalidate(zone);
  2036         for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
  2037             if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
  2038                 continue;
  2039             c->zone()->scheduledForDestruction = false;
  2040             GlobalObject *global = c->maybeGlobal();
  2041             if (global) {
  2042                 Rooted<GlobalObject*> rg(cx, global);
  2043                 if (!dbg->addDebuggeeGlobal(cx, rg, invalidate))
  2044                     return false;
  2049     args.rval().setUndefined();
  2050     return true;
  2053 bool
  2054 Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp)
  2056     REQUIRE_ARGC("Debugger.removeDebuggee", 1);
  2057     THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);
  2058     GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]);
  2059     if (!global)
  2060         return false;
  2061     if (dbg->debuggees.has(global)) {
  2062         if (!dbg->removeDebuggeeGlobal(cx, global, nullptr, nullptr))
  2063             return false;
  2065     args.rval().setUndefined();
  2066     return true;
  2069 bool
  2070 Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
  2072     THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
  2074     for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
  2075         if (!dbg->removeDebuggeeGlobal(cx, e.front(), nullptr, &e))
  2076             return false;
  2079     args.rval().setUndefined();
  2080     return true;
  2083 bool
  2084 Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp)
  2086     REQUIRE_ARGC("Debugger.hasDebuggee", 1);
  2087     THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg);
  2088     GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]);
  2089     if (!global)
  2090         return false;
  2091     args.rval().setBoolean(!!dbg->debuggees.lookup(global));
  2092     return true;
  2095 bool
  2096 Debugger::getDebuggees(JSContext *cx, unsigned argc, Value *vp)
  2098     THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg);
  2099     RootedObject arrobj(cx, NewDenseAllocatedArray(cx, dbg->debuggees.count()));
  2100     if (!arrobj)
  2101         return false;
  2102     arrobj->ensureDenseInitializedLength(cx, 0, dbg->debuggees.count());
  2103     unsigned i = 0;
  2104     for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
  2105         RootedValue v(cx, ObjectValue(*e.front()));
  2106         if (!dbg->wrapDebuggeeValue(cx, &v))
  2107             return false;
  2108         arrobj->setDenseElement(i++, v);
  2110     args.rval().setObject(*arrobj);
  2111     return true;
  2114 bool
  2115 Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp)
  2117     THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
  2119     /* Since there may be multiple contexts, use AllFramesIter. */
  2120     for (AllFramesIter i(cx); !i.done(); ++i) {
  2121         if (dbg->observesFrame(i)) {
  2122             // Ensure that Ion frames are rematerialized. Only rematerialized
  2123             // Ion frames may be used as AbstractFramePtrs.
  2124             if (i.isIon() && !i.ensureHasRematerializedFrame())
  2125                 return false;
  2126             AbstractFramePtr frame = i.abstractFramePtr();
  2127             ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED);
  2128             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
  2129                 ++iter;
  2130             return dbg->getScriptFrame(cx, iter, args.rval());
  2133     args.rval().setNull();
  2134     return true;
  2137 bool
  2138 Debugger::clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp)
  2140     THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg);
  2141     for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront())
  2142         r.front()->compartment()->clearBreakpointsIn(cx->runtime()->defaultFreeOp(),
  2143                                                      dbg, NullPtr());
  2144     return true;
  2147 bool
  2148 Debugger::construct(JSContext *cx, unsigned argc, Value *vp)
  2150     CallArgs args = CallArgsFromVp(argc, vp);
  2152     /* Check that the arguments, if any, are cross-compartment wrappers. */
  2153     for (unsigned i = 0; i < args.length(); i++) {
  2154         const Value &arg = args[i];
  2155         if (!arg.isObject())
  2156             return ReportObjectRequired(cx);
  2157         JSObject *argobj = &arg.toObject();
  2158         if (!argobj->is<CrossCompartmentWrapperObject>()) {
  2159             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CCW_REQUIRED,
  2160                                  "Debugger");
  2161             return false;
  2165     /* Get Debugger.prototype. */
  2166     RootedValue v(cx);
  2167     RootedObject callee(cx, &args.callee());
  2168     if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &v))
  2169         return false;
  2170     RootedObject proto(cx, &v.toObject());
  2171     JS_ASSERT(proto->getClass() == &Debugger::jsclass);
  2172     /*
  2173      * Make the new Debugger object. Each one has a reference to
  2174      * Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The
  2175      * rest of the reserved slots are for hooks; they default to undefined.
  2176      */
  2177     RootedObject obj(cx, NewObjectWithGivenProto(cx, &Debugger::jsclass, proto, nullptr));
  2178     if (!obj)
  2179         return false;
  2180     for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
  2181         obj->setReservedSlot(slot, proto->getReservedSlot(slot));
  2182     /* Create the Debugger.Memory instance accessible by the
  2183      * |Debugger.prototype.memory| getter. */
  2184     Value memoryProto = obj->getReservedSlot(JSSLOT_DEBUG_MEMORY_PROTO);
  2185     RootedObject memory(cx, NewObjectWithGivenProto(cx, &DebuggerMemory::class_,
  2186                                                     &memoryProto.toObject(), nullptr));
  2187     if (!memory)
  2188         return false;
  2189     obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, ObjectValue(*memory));
  2191     /* Construct the underlying C++ object. */
  2192     Debugger *dbg = cx->new_<Debugger>(cx, obj.get());
  2193     if (!dbg)
  2194         return false;
  2195     if (!dbg->init(cx)) {
  2196         js_delete(dbg);
  2197         return false;
  2199     obj->setPrivate(dbg);
  2200     /* Now the JSObject owns the js::Debugger instance, so we needn't delete it. */
  2202     /* Add the initial debuggees, if any. */
  2203     for (unsigned i = 0; i < args.length(); i++) {
  2204         Rooted<GlobalObject*>
  2205             debuggee(cx, &args[i].toObject().as<ProxyObject>().private_().toObject().global());
  2206         if (!dbg->addDebuggeeGlobal(cx, debuggee))
  2207             return false;
  2210     args.rval().setObject(*obj);
  2211     return true;
  2214 bool
  2215 Debugger::addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> global)
  2217     AutoDebugModeInvalidation invalidate(global->compartment());
  2218     return addDebuggeeGlobal(cx, global, invalidate);
  2221 bool
  2222 Debugger::addDebuggeeGlobal(JSContext *cx,
  2223                             Handle<GlobalObject*> global,
  2224                             AutoDebugModeInvalidation &invalidate)
  2226     if (debuggees.has(global))
  2227         return true;
  2229     // Callers should generally be unable to get a reference to a debugger-
  2230     // invisible global in order to pass it to addDebuggee. But this is possible
  2231     // with certain testing aides we expose in the shell, so just make addDebuggee
  2232     // throw in that case.
  2233     JSCompartment *debuggeeCompartment = global->compartment();
  2234     if (debuggeeCompartment->options().invisibleToDebugger()) {
  2235         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  2236                              JSMSG_DEBUG_CANT_DEBUG_GLOBAL);
  2237         return false;
  2240     /*
  2241      * Check for cycles. If global's compartment is reachable from this
  2242      * Debugger object's compartment by following debuggee-to-debugger links,
  2243      * then adding global would create a cycle. (Typically nobody is debugging
  2244      * the debugger, in which case we zip through this code without looping.)
  2245      */
  2246     Vector<JSCompartment *> visited(cx);
  2247     if (!visited.append(object->compartment()))
  2248         return false;
  2249     for (size_t i = 0; i < visited.length(); i++) {
  2250         JSCompartment *c = visited[i];
  2251         if (c == debuggeeCompartment) {
  2252             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP);
  2253             return false;
  2256         /*
  2257          * Find all compartments containing debuggers debugging global objects
  2258          * in c. Add those compartments to visited.
  2259          */
  2260         for (GlobalObjectSet::Range r = c->getDebuggees().all(); !r.empty(); r.popFront()) {
  2261             GlobalObject::DebuggerVector *v = r.front()->getDebuggers();
  2262             for (Debugger **p = v->begin(); p != v->end(); p++) {
  2263                 JSCompartment *next = (*p)->object->compartment();
  2264                 if (Find(visited, next) == visited.end() && !visited.append(next))
  2265                     return false;
  2270     /*
  2271      * Each debugger-debuggee relation must be stored in up to three places.
  2272      * JSCompartment::addDebuggee enables debug mode if needed.
  2273      */
  2274     AutoCompartment ac(cx, global);
  2275     GlobalObject::DebuggerVector *v = GlobalObject::getOrCreateDebuggers(cx, global);
  2276     if (!v || !v->append(this)) {
  2277         js_ReportOutOfMemory(cx);
  2278     } else {
  2279         if (!debuggees.put(global)) {
  2280             js_ReportOutOfMemory(cx);
  2281         } else {
  2282             if (global->getDebuggers()->length() > 1)
  2283                 return true;
  2284             if (debuggeeCompartment->addDebuggee(cx, global, invalidate))
  2285                 return true;
  2287             /* Maintain consistency on error. */
  2288             debuggees.remove(global);
  2290         JS_ASSERT(v->back() == this);
  2291         v->popBack();
  2293     return false;
  2296 void
  2297 Debugger::cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
  2298                                              AutoDebugModeInvalidation &invalidate,
  2299                                              GlobalObjectSet::Enum *compartmentEnum,
  2300                                              GlobalObjectSet::Enum *debugEnum)
  2302     /*
  2303      * Each debuggee is in two HashSets: one for its compartment and one for
  2304      * its debugger (this). The caller might be enumerating either set; if so,
  2305      * use HashSet::Enum::removeFront rather than HashSet::remove below, to
  2306      * avoid invalidating the live enumerator.
  2307      */
  2308     JS_ASSERT(global->compartment()->getDebuggees().has(global));
  2309     JS_ASSERT_IF(compartmentEnum, compartmentEnum->front() == global);
  2310     JS_ASSERT(debuggees.has(global));
  2311     JS_ASSERT_IF(debugEnum, debugEnum->front() == global);
  2313     /*
  2314      * FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame
  2315      * objects referring to a particular JS stack frame. This is hard if
  2316      * Debugger objects that are no longer debugging the relevant global might
  2317      * have live Frame objects. So we take the easy way out and kill them here.
  2318      * This is a bug, since it's observable and contrary to the spec. One
  2319      * possible fix would be to put such objects into a compartment-wide bag
  2320      * which slowPathOnLeaveFrame would have to examine.
  2321      */
  2322     for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
  2323         AbstractFramePtr frame = e.front().key();
  2324         JSObject *frameobj = e.front().value();
  2325         if (&frame.script()->global() == global) {
  2326             DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
  2327             DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
  2328             e.removeFront();
  2332     GlobalObject::DebuggerVector *v = global->getDebuggers();
  2333     Debugger **p;
  2334     for (p = v->begin(); p != v->end(); p++) {
  2335         if (*p == this)
  2336             break;
  2338     JS_ASSERT(p != v->end());
  2340     /*
  2341      * The relation must be removed from up to three places: *v and debuggees
  2342      * for sure, and possibly the compartment's debuggee set.
  2343      */
  2344     v->erase(p);
  2345     if (debugEnum)
  2346         debugEnum->removeFront();
  2347     else
  2348         debuggees.remove(global);
  2350     /* Remove all breakpoints for the debuggee. */
  2351     Breakpoint *nextbp;
  2352     for (Breakpoint *bp = firstBreakpoint(); bp; bp = nextbp) {
  2353         nextbp = bp->nextInDebugger();
  2354         if (bp->site->script->compartment() == global->compartment())
  2355             bp->destroy(fop);
  2357     JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
  2360 bool
  2361 Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
  2362                                GlobalObjectSet::Enum *compartmentEnum,
  2363                                GlobalObjectSet::Enum *debugEnum)
  2365     AutoDebugModeInvalidation invalidate(global->compartment());
  2366     return removeDebuggeeGlobal(cx, global, invalidate, compartmentEnum, debugEnum);
  2369 bool
  2370 Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
  2371                                AutoDebugModeInvalidation &invalidate,
  2372                                GlobalObjectSet::Enum *compartmentEnum,
  2373                                GlobalObjectSet::Enum *debugEnum)
  2375     cleanupDebuggeeGlobalBeforeRemoval(cx->runtime()->defaultFreeOp(), global,
  2376                                        invalidate, compartmentEnum, debugEnum);
  2378     // The debuggee needs to be removed from the compartment last to save a root.
  2379     if (global->getDebuggers()->empty())
  2380         return global->compartment()->removeDebuggee(cx, global, invalidate, compartmentEnum);
  2382     return true;
  2385 void
  2386 Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
  2387                                       GlobalObjectSet::Enum *compartmentEnum,
  2388                                       GlobalObjectSet::Enum *debugEnum)
  2390     AutoDebugModeInvalidation invalidate(global->compartment());
  2391     removeDebuggeeGlobalUnderGC(fop, global, invalidate, compartmentEnum, debugEnum);
  2394 void
  2395 Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
  2396                                       AutoDebugModeInvalidation &invalidate,
  2397                                       GlobalObjectSet::Enum *compartmentEnum,
  2398                                       GlobalObjectSet::Enum *debugEnum)
  2400     cleanupDebuggeeGlobalBeforeRemoval(fop, global, invalidate, compartmentEnum, debugEnum);
  2402     /*
  2403      * The debuggee needs to be removed from the compartment last, as this can
  2404      * trigger GCs if the compartment's debug mode is being changed, and the
  2405      * global cannot be rooted on the stack without a cx.
  2406      */
  2407     if (global->getDebuggers()->empty())
  2408         global->compartment()->removeDebuggeeUnderGC(fop, global, invalidate, compartmentEnum);
  2411 /*
  2412  * A class for parsing 'findScripts' query arguments and searching for
  2413  * scripts that match the criteria they represent.
  2414  */
  2415 class Debugger::ScriptQuery {
  2416   public:
  2417     /* Construct a ScriptQuery to use matching scripts for |dbg|. */
  2418     ScriptQuery(JSContext *cx, Debugger *dbg):
  2419         cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx), displayURL(cx),
  2420         displayURLChars(nullptr), innermostForCompartment(cx->runtime())
  2421     {}
  2423     /*
  2424      * Initialize this ScriptQuery. Raise an error and return false if we
  2425      * haven't enough memory.
  2426      */
  2427     bool init() {
  2428         if (!compartments.init() ||
  2429             !innermostForCompartment.init())
  2431             js_ReportOutOfMemory(cx);
  2432             return false;
  2435         return true;
  2438     /*
  2439      * Parse the query object |query|, and prepare to match only the scripts
  2440      * it specifies.
  2441      */
  2442     bool parseQuery(HandleObject query) {
  2443         /*
  2444          * Check for a 'global' property, which limits the results to those
  2445          * scripts scoped to a particular global object.
  2446          */
  2447         RootedValue global(cx);
  2448         if (!JSObject::getProperty(cx, query, query, cx->names().global, &global))
  2449             return false;
  2450         if (global.isUndefined()) {
  2451             matchAllDebuggeeGlobals();
  2452         } else {
  2453             GlobalObject *globalObject = debugger->unwrapDebuggeeArgument(cx, global);
  2454             if (!globalObject)
  2455                 return false;
  2457             /*
  2458              * If the given global isn't a debuggee, just leave the set of
  2459              * acceptable globals empty; we'll return no scripts.
  2460              */
  2461             if (debugger->debuggees.has(globalObject)) {
  2462                 if (!matchSingleGlobal(globalObject))
  2463                     return false;
  2467         /* Check for a 'url' property. */
  2468         if (!JSObject::getProperty(cx, query, query, cx->names().url, &url))
  2469             return false;
  2470         if (!url.isUndefined() && !url.isString()) {
  2471             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
  2472                                  "query object's 'url' property", "neither undefined nor a string");
  2473             return false;
  2476         /* Check for a 'line' property. */
  2477         RootedValue lineProperty(cx);
  2478         if (!JSObject::getProperty(cx, query, query, cx->names().line, &lineProperty))
  2479             return false;
  2480         if (lineProperty.isUndefined()) {
  2481             hasLine = false;
  2482         } else if (lineProperty.isNumber()) {
  2483             if (url.isUndefined()) {
  2484                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  2485                                      JSMSG_QUERY_LINE_WITHOUT_URL);
  2486                 return false;
  2488             double doubleLine = lineProperty.toNumber();
  2489             if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) {
  2490                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE);
  2491                 return false;
  2493             hasLine = true;
  2494             line = doubleLine;
  2495         } else {
  2496             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
  2497                                  "query object's 'line' property",
  2498                                  "neither undefined nor an integer");
  2499             return false;
  2502         /* Check for an 'innermost' property. */
  2503         PropertyName *innermostName = cx->names().innermost;
  2504         RootedValue innermostProperty(cx);
  2505         if (!JSObject::getProperty(cx, query, query, innermostName, &innermostProperty))
  2506             return false;
  2507         innermost = ToBoolean(innermostProperty);
  2508         if (innermost) {
  2509             /* Technically, we need only check hasLine, but this is clearer. */
  2510             if (url.isUndefined() || !hasLine) {
  2511                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  2512                                      JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL);
  2513                 return false;
  2517         /* Check for a 'displayURL' property. */
  2518         if (!JSObject::getProperty(cx, query, query, cx->names().displayURL, &displayURL))
  2519             return false;
  2520         if (!displayURL.isUndefined() && !displayURL.isString()) {
  2521             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
  2522                                  "query object's 'displayURL' property",
  2523                                  "neither undefined nor a string");
  2524             return false;
  2527         return true;
  2530     /* Set up this ScriptQuery appropriately for a missing query argument. */
  2531     bool omittedQuery() {
  2532         url.setUndefined();
  2533         hasLine = false;
  2534         innermost = false;
  2535         displayURLChars = nullptr;
  2536         return matchAllDebuggeeGlobals();
  2539     /*
  2540      * Search all relevant compartments and the stack for scripts matching
  2541      * this query, and append the matching scripts to |vector|.
  2542      */
  2543     bool findScripts(AutoScriptVector *v) {
  2544         if (!prepareQuery())
  2545             return false;
  2547         JSCompartment *singletonComp = nullptr;
  2548         if (compartments.count() == 1)
  2549             singletonComp = compartments.all().front();
  2551         /* Search each compartment for debuggee scripts. */
  2552         vector = v;
  2553         oom = false;
  2554         IterateScripts(cx->runtime(), singletonComp, this, considerScript);
  2555         if (oom) {
  2556             js_ReportOutOfMemory(cx);
  2557             return false;
  2560         /*
  2561          * For most queries, we just accumulate results in 'vector' as we find
  2562          * them. But if this is an 'innermost' query, then we've accumulated the
  2563          * results in the 'innermostForCompartment' map. In that case, we now need to
  2564          * walk that map and populate 'vector'.
  2565          */
  2566         if (innermost) {
  2567             for (CompartmentToScriptMap::Range r = innermostForCompartment.all();
  2568                  !r.empty();
  2569                  r.popFront()) {
  2570                 if (!v->append(r.front().value())) {
  2571                     js_ReportOutOfMemory(cx);
  2572                     return false;
  2577         return true;
  2580   private:
  2581     /* The context in which we should do our work. */
  2582     JSContext *cx;
  2584     /* The debugger for which we conduct queries. */
  2585     Debugger *debugger;
  2587     typedef HashSet<JSCompartment *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy>
  2588         CompartmentSet;
  2590     /* A script must be in one of these compartments to match the query. */
  2591     CompartmentSet compartments;
  2593     /* If this is a string, matching scripts have urls equal to it. */
  2594     RootedValue url;
  2596     /* url as a C string. */
  2597     JSAutoByteString urlCString;
  2599     /* If this is a string, matching scripts' sources have displayURLs equal to
  2600      * it. */
  2601     RootedValue displayURL;
  2603     /* displayURL as a jschar* */
  2604     const jschar *displayURLChars;
  2605     size_t displayURLLength;
  2607     /* True if the query contained a 'line' property. */
  2608     bool hasLine;
  2610     /* The line matching scripts must cover. */
  2611     unsigned int line;
  2613     /* True if the query has an 'innermost' property whose value is true. */
  2614     bool innermost;
  2616     typedef HashMap<JSCompartment *, JSScript *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy>
  2617         CompartmentToScriptMap;
  2619     /*
  2620      * For 'innermost' queries, a map from compartments to the innermost script
  2621      * we've seen so far in that compartment. (Template instantiation code size
  2622      * explosion ho!)
  2623      */
  2624     CompartmentToScriptMap innermostForCompartment;
  2626     /* The vector to which to append the scripts found. */
  2627     AutoScriptVector *vector;
  2629     /* Indicates whether OOM has occurred while matching. */
  2630     bool oom;
  2632     bool addCompartment(JSCompartment *comp) {
  2634             // All scripts in the debuggee compartment must be visible, so
  2635             // delazify everything.
  2636             AutoCompartment ac(cx, comp);
  2637             if (!comp->ensureDelazifyScriptsForDebugMode(cx))
  2638                 return false;
  2640         return compartments.put(comp);
  2643     /* Arrange for this ScriptQuery to match only scripts that run in |global|. */
  2644     bool matchSingleGlobal(GlobalObject *global) {
  2645         JS_ASSERT(compartments.count() == 0);
  2646         if (!addCompartment(global->compartment())) {
  2647             js_ReportOutOfMemory(cx);
  2648             return false;
  2650         return true;
  2653     /*
  2654      * Arrange for this ScriptQuery to match all scripts running in debuggee
  2655      * globals.
  2656      */
  2657     bool matchAllDebuggeeGlobals() {
  2658         JS_ASSERT(compartments.count() == 0);
  2659         /* Build our compartment set from the debugger's set of debuggee globals. */
  2660         for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
  2661             if (!addCompartment(r.front()->compartment())) {
  2662                 js_ReportOutOfMemory(cx);
  2663                 return false;
  2666         return true;
  2669     /*
  2670      * Given that parseQuery or omittedQuery has been called, prepare to match
  2671      * scripts. Set urlCString and displayURLChars as appropriate.
  2672      */
  2673     bool prepareQuery() {
  2674         /* Compute urlCString and displayURLChars, if a url or displayURL was
  2675          * given respectively. */
  2676         if (url.isString()) {
  2677             if (!urlCString.encodeLatin1(cx, url.toString()))
  2678                 return false;
  2680         if (displayURL.isString()) {
  2681             JSString *s = displayURL.toString();
  2682             displayURLChars = s->getChars(cx);
  2683             displayURLLength = s->length();
  2684             if (!displayURLChars)
  2685                 return false;
  2688         return true;
  2691     static void considerScript(JSRuntime *rt, void *data, JSScript *script) {
  2692         ScriptQuery *self = static_cast<ScriptQuery *>(data);
  2693         self->consider(script);
  2696     /*
  2697      * If |script| matches this query, append it to |vector| or place it in
  2698      * |innermostForCompartment|, as appropriate. Set |oom| if an out of memory
  2699      * condition occurred.
  2700      */
  2701     void consider(JSScript *script) {
  2702         // We check for presence of script->code() because it is possible that
  2703         // the script was created and thus exposed to GC, but *not* fully
  2704         // initialized from fullyInit{FromEmitter,Trivial} due to errors.
  2705         if (oom || script->selfHosted() || !script->code())
  2706             return;
  2707         JSCompartment *compartment = script->compartment();
  2708         if (!compartments.has(compartment))
  2709             return;
  2710         if (urlCString.ptr()) {
  2711             bool gotFilename = false;
  2712             if (script->filename() && strcmp(script->filename(), urlCString.ptr()) == 0)
  2713                 gotFilename = true;
  2715             bool gotSourceURL = false;
  2716             if (!gotFilename && script->scriptSource()->introducerFilename() &&
  2717                 strcmp(script->scriptSource()->introducerFilename(), urlCString.ptr()) == 0)
  2719                 gotSourceURL = true;
  2721             if (!gotFilename && !gotSourceURL)
  2722                 return;
  2724         if (hasLine) {
  2725             if (line < script->lineno() || script->lineno() + js_GetScriptLineExtent(script) < line)
  2726                 return;
  2728         if (displayURLChars) {
  2729             if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL())
  2730                 return;
  2731             const jschar *s = script->scriptSource()->displayURL();
  2732             if (CompareChars(s, js_strlen(s), displayURLChars, displayURLLength) != 0) {
  2733                 return;
  2737         if (innermost) {
  2738             /*
  2739              * For 'innermost' queries, we don't place scripts in |vector| right
  2740              * away; we may later find another script that is nested inside this
  2741              * one. Instead, we record the innermost script we've found so far
  2742              * for each compartment in innermostForCompartment, and only
  2743              * populate |vector| at the bottom of findScripts, when we've
  2744              * traversed all the scripts.
  2746              * So: check this script against the innermost one we've found so
  2747              * far (if any), as recorded in innermostForCompartment, and replace
  2748              * that if it's better.
  2749              */
  2750             CompartmentToScriptMap::AddPtr p = innermostForCompartment.lookupForAdd(compartment);
  2751             if (p) {
  2752                 /* Is our newly found script deeper than the last one we found? */
  2753                 JSScript *incumbent = p->value();
  2754                 if (script->staticLevel() > incumbent->staticLevel())
  2755                     p->value() = script;
  2756             } else {
  2757                 /*
  2758                  * This is the first matching script we've encountered for this
  2759                  * compartment, so it is thus the innermost such script.
  2760                  */
  2761                 if (!innermostForCompartment.add(p, compartment, script)) {
  2762                     oom = true;
  2763                     return;
  2766         } else {
  2767             /* Record this matching script in the results vector. */
  2768             if (!vector->append(script)) {
  2769                 oom = true;
  2770                 return;
  2774         return;
  2776 };
  2778 bool
  2779 Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp)
  2781     THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg);
  2783     ScriptQuery query(cx, dbg);
  2784     if (!query.init())
  2785         return false;
  2787     if (args.length() >= 1) {
  2788         RootedObject queryObject(cx, NonNullObject(cx, args[0]));
  2789         if (!queryObject || !query.parseQuery(queryObject))
  2790             return false;
  2791     } else {
  2792         if (!query.omittedQuery())
  2793             return false;
  2796     /*
  2797      * Accumulate the scripts in an AutoScriptVector, instead of creating
  2798      * the JS array as we go, because we mustn't allocate JS objects or GC
  2799      * while we use the CellIter.
  2800      */
  2801     AutoScriptVector scripts(cx);
  2803     if (!query.findScripts(&scripts))
  2804         return false;
  2806     RootedObject result(cx, NewDenseAllocatedArray(cx, scripts.length()));
  2807     if (!result)
  2808         return false;
  2810     result->ensureDenseInitializedLength(cx, 0, scripts.length());
  2812     for (size_t i = 0; i < scripts.length(); i++) {
  2813         JSObject *scriptObject = dbg->wrapScript(cx, scripts.handleAt(i));
  2814         if (!scriptObject)
  2815             return false;
  2816         result->setDenseElement(i, ObjectValue(*scriptObject));
  2819     args.rval().setObject(*result);
  2820     return true;
  2823 bool
  2824 Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp)
  2826     THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg);
  2828     RootedObject result(cx, NewDenseEmptyArray(cx));
  2829     if (!result)
  2830         return false;
  2832     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
  2833         if (c->options().invisibleToDebugger())
  2834             continue;
  2836         c->zone()->scheduledForDestruction = false;
  2838         GlobalObject *global = c->maybeGlobal();
  2840         if (cx->runtime()->isSelfHostingGlobal(global))
  2841             continue;
  2843         if (global) {
  2844             /*
  2845              * We pulled |global| out of nowhere, so it's possible that it was
  2846              * marked gray by XPConnect. Since we're now exposing it to JS code,
  2847              * we need to mark it black.
  2848              */
  2849             JS::ExposeGCThingToActiveJS(global, JSTRACE_OBJECT);
  2851             RootedValue globalValue(cx, ObjectValue(*global));
  2852             if (!dbg->wrapDebuggeeValue(cx, &globalValue))
  2853                 return false;
  2854             if (!NewbornArrayPush(cx, result, globalValue))
  2855                 return false;
  2859     args.rval().setObject(*result);
  2860     return true;
  2863 bool
  2864 Debugger::makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp)
  2866     REQUIRE_ARGC("Debugger.makeGlobalObjectReference", 1);
  2867     THIS_DEBUGGER(cx, argc, vp, "makeGlobalObjectReference", args, dbg);
  2869     Rooted<GlobalObject *> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  2870     if (!global)
  2871         return false;
  2873     args.rval().setObject(*global);
  2874     return dbg->wrapDebuggeeValue(cx, args.rval());
  2877 const JSPropertySpec Debugger::properties[] = {
  2878     JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0),
  2879     JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement,
  2880             Debugger::setOnDebuggerStatement, 0),
  2881     JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind,
  2882             Debugger::setOnExceptionUnwind, 0),
  2883     JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript, 0),
  2884     JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0),
  2885     JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0),
  2886     JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
  2887             Debugger::setUncaughtExceptionHook, 0),
  2888     JS_PSG("memory", Debugger::getMemory, 0),
  2889     JS_PS_END
  2890 };
  2891 const JSFunctionSpec Debugger::methods[] = {
  2892     JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0),
  2893     JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0),
  2894     JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
  2895     JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0),
  2896     JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
  2897     JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0),
  2898     JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
  2899     JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 1, 0),
  2900     JS_FN("findScripts", Debugger::findScripts, 1, 0),
  2901     JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
  2902     JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0),
  2903     JS_FS_END
  2904 };
  2907 /*** Debugger.Script *****************************************************************************/
  2909 static inline JSScript *
  2910 GetScriptReferent(JSObject *obj)
  2912     JS_ASSERT(obj->getClass() == &DebuggerScript_class);
  2913     return static_cast<JSScript *>(obj->getPrivate());
  2916 static void
  2917 DebuggerScript_trace(JSTracer *trc, JSObject *obj)
  2919     /* This comes from a private pointer, so no barrier needed. */
  2920     if (JSScript *script = GetScriptReferent(obj)) {
  2921         MarkCrossCompartmentScriptUnbarriered(trc, obj, &script, "Debugger.Script referent");
  2922         obj->setPrivateUnbarriered(script);
  2926 const Class DebuggerScript_class = {
  2927     "Script",
  2928     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
  2929     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
  2930     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  2931     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr,
  2932     nullptr,              /* call        */
  2933     nullptr,              /* hasInstance */
  2934     nullptr,              /* construct   */
  2935     DebuggerScript_trace
  2936 };
  2938 JSObject *
  2939 Debugger::newDebuggerScript(JSContext *cx, HandleScript script)
  2941     assertSameCompartment(cx, object.get());
  2943     JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject();
  2944     JS_ASSERT(proto);
  2945     JSObject *scriptobj = NewObjectWithGivenProto(cx, &DebuggerScript_class, proto, nullptr, TenuredObject);
  2946     if (!scriptobj)
  2947         return nullptr;
  2948     scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object));
  2949     scriptobj->setPrivateGCThing(script);
  2951     return scriptobj;
  2954 JSObject *
  2955 Debugger::wrapScript(JSContext *cx, HandleScript script)
  2957     assertSameCompartment(cx, object.get());
  2958     JS_ASSERT(cx->compartment() != script->compartment());
  2959     DependentAddPtr<ScriptWeakMap> p(cx, scripts, script);
  2960     if (!p) {
  2961         JSObject *scriptobj = newDebuggerScript(cx, script);
  2962         if (!scriptobj)
  2963             return nullptr;
  2965         if (!p.add(cx, scripts, script, scriptobj)) {
  2966             js_ReportOutOfMemory(cx);
  2967             return nullptr;
  2970         CrossCompartmentKey key(CrossCompartmentKey::DebuggerScript, object, script);
  2971         if (!object->compartment()->putWrapper(cx, key, ObjectValue(*scriptobj))) {
  2972             scripts.remove(script);
  2973             js_ReportOutOfMemory(cx);
  2974             return nullptr;
  2978     JS_ASSERT(GetScriptReferent(p->value()) == script);
  2979     return p->value();
  2982 static JSObject *
  2983 DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const char *fnname)
  2985     if (!v.isObject()) {
  2986         ReportObjectRequired(cx);
  2987         return nullptr;
  2989     JSObject *thisobj = &v.toObject();
  2990     if (thisobj->getClass() != &DebuggerScript_class) {
  2991         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  2992                              clsname, fnname, thisobj->getClass()->name);
  2993         return nullptr;
  2996     /*
  2997      * Check for Debugger.Script.prototype, which is of class DebuggerScript_class
  2998      * but whose script is null.
  2999      */
  3000     if (!GetScriptReferent(thisobj)) {
  3001         JS_ASSERT(!GetScriptReferent(thisobj));
  3002         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  3003                              clsname, fnname, "prototype object");
  3004         return nullptr;
  3007     return thisobj;
  3010 static JSObject *
  3011 DebuggerScript_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
  3013     return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname);
  3016 #define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script)            \
  3017     CallArgs args = CallArgsFromVp(argc, vp);                                       \
  3018     RootedObject obj(cx, DebuggerScript_checkThis(cx, args, fnname));               \
  3019     if (!obj)                                                                       \
  3020         return false;                                                               \
  3021     Rooted<JSScript*> script(cx, GetScriptReferent(obj))
  3023 static bool
  3024 DebuggerScript_getUrl(JSContext *cx, unsigned argc, Value *vp)
  3026     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get url)", args, obj, script);
  3028     if (script->filename()) {
  3029         JSString *str;
  3030         if (script->scriptSource()->introducerFilename())
  3031             str = js_NewStringCopyZ<CanGC>(cx, script->scriptSource()->introducerFilename());
  3032         else
  3033             str = js_NewStringCopyZ<CanGC>(cx, script->filename());
  3034         if (!str)
  3035             return false;
  3036         args.rval().setString(str);
  3037     } else {
  3038         args.rval().setNull();
  3040     return true;
  3043 static bool
  3044 DebuggerScript_getStartLine(JSContext *cx, unsigned argc, Value *vp)
  3046     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get startLine)", args, obj, script);
  3047     args.rval().setNumber(uint32_t(script->lineno()));
  3048     return true;
  3051 static bool
  3052 DebuggerScript_getLineCount(JSContext *cx, unsigned argc, Value *vp)
  3054     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get lineCount)", args, obj, script);
  3056     unsigned maxLine = js_GetScriptLineExtent(script);
  3057     args.rval().setNumber(double(maxLine));
  3058     return true;
  3061 static bool
  3062 DebuggerScript_getSource(JSContext *cx, unsigned argc, Value *vp)
  3064     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get source)", args, obj, script);
  3065     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3067     RootedScriptSource source(cx, &UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>());
  3068     RootedObject sourceObject(cx, dbg->wrapSource(cx, source));
  3069     if (!sourceObject)
  3070         return false;
  3072     args.rval().setObject(*sourceObject);
  3073     return true;
  3076 static bool
  3077 DebuggerScript_getSourceStart(JSContext *cx, unsigned argc, Value *vp)
  3079     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceStart)", args, obj, script);
  3080     args.rval().setNumber(uint32_t(script->sourceStart()));
  3081     return true;
  3084 static bool
  3085 DebuggerScript_getSourceLength(JSContext *cx, unsigned argc, Value *vp)
  3087     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceEnd)", args, obj, script);
  3088     args.rval().setNumber(uint32_t(script->sourceEnd() - script->sourceStart()));
  3089     return true;
  3092 static bool
  3093 DebuggerScript_getStaticLevel(JSContext *cx, unsigned argc, Value *vp)
  3095     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get staticLevel)", args, obj, script);
  3096     args.rval().setNumber(uint32_t(script->staticLevel()));
  3097     return true;
  3100 static bool
  3101 DebuggerScript_getSourceMapUrl(JSContext *cx, unsigned argc, Value *vp)
  3103     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceMapURL)", args, obj, script);
  3105     ScriptSource *source = script->scriptSource();
  3106     JS_ASSERT(source);
  3108     if (source->hasSourceMapURL()) {
  3109         JSString *str = JS_NewUCStringCopyZ(cx, source->sourceMapURL());
  3110         if (!str)
  3111             return false;
  3112         args.rval().setString(str);
  3113     } else {
  3114         args.rval().setNull();
  3117     return true;
  3120 static bool
  3121 DebuggerScript_getGlobal(JSContext *cx, unsigned argc, Value *vp)
  3123     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get global)", args, obj, script);
  3124     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3126     RootedValue v(cx, ObjectValue(script->global()));
  3127     if (!dbg->wrapDebuggeeValue(cx, &v))
  3128         return false;
  3129     args.rval().set(v);
  3130     return true;
  3133 static bool
  3134 DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp)
  3136     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script);
  3137     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3139     RootedObject result(cx, NewDenseEmptyArray(cx));
  3140     if (!result)
  3141         return false;
  3142     if (script->hasObjects()) {
  3143         /*
  3144          * script->savedCallerFun indicates that this is a direct eval script
  3145          * and the calling function is stored as script->objects()->vector[0].
  3146          * It is not really a child script of this script, so skip it using
  3147          * innerObjectsStart().
  3148          */
  3149         ObjectArray *objects = script->objects();
  3150         RootedFunction fun(cx);
  3151         RootedScript funScript(cx);
  3152         RootedObject obj(cx), s(cx);
  3153         for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) {
  3154             obj = objects->vector[i];
  3155             if (obj->is<JSFunction>()) {
  3156                 fun = &obj->as<JSFunction>();
  3157                 funScript = GetOrCreateFunctionScript(cx, fun);
  3158                 if (!funScript)
  3159                     return false;
  3160                 s = dbg->wrapScript(cx, funScript);
  3161                 if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s)))
  3162                     return false;
  3166     args.rval().setObject(*result);
  3167     return true;
  3170 static bool
  3171 ScriptOffset(JSContext *cx, JSScript *script, const Value &v, size_t *offsetp)
  3173     double d;
  3174     size_t off;
  3176     bool ok = v.isNumber();
  3177     if (ok) {
  3178         d = v.toNumber();
  3179         off = size_t(d);
  3181     if (!ok || off != d || !IsValidBytecodeOffset(cx, script, off)) {
  3182         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET);
  3183         return false;
  3185     *offsetp = off;
  3186     return true;
  3189 static bool
  3190 DebuggerScript_getOffsetLine(JSContext *cx, unsigned argc, Value *vp)
  3192     REQUIRE_ARGC("Debugger.Script.getOffsetLine", 1);
  3193     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script);
  3194     size_t offset;
  3195     if (!ScriptOffset(cx, script, args[0], &offset))
  3196         return false;
  3197     unsigned lineno = JS_PCToLineNumber(cx, script, script->offsetToPC(offset));
  3198     args.rval().setNumber(lineno);
  3199     return true;
  3202 namespace {
  3204 class BytecodeRangeWithPosition : private BytecodeRange
  3206   public:
  3207     using BytecodeRange::empty;
  3208     using BytecodeRange::frontPC;
  3209     using BytecodeRange::frontOpcode;
  3210     using BytecodeRange::frontOffset;
  3212     BytecodeRangeWithPosition(JSContext *cx, JSScript *script)
  3213       : BytecodeRange(cx, script), lineno(script->lineno()), column(0),
  3214         sn(script->notes()), snpc(script->code())
  3216         if (!SN_IS_TERMINATOR(sn))
  3217             snpc += SN_DELTA(sn);
  3218         updatePosition();
  3219         while (frontPC() != script->main())
  3220             popFront();
  3223     void popFront() {
  3224         BytecodeRange::popFront();
  3225         if (!empty())
  3226             updatePosition();
  3229     size_t frontLineNumber() const { return lineno; }
  3230     size_t frontColumnNumber() const { return column; }
  3232   private:
  3233     void updatePosition() {
  3234         /*
  3235          * Determine the current line number by reading all source notes up to
  3236          * and including the current offset.
  3237          */
  3238         while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) {
  3239             SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
  3240             if (type == SRC_COLSPAN) {
  3241                 ptrdiff_t colspan = js_GetSrcNoteOffset(sn, 0);
  3243                 if (colspan >= SN_COLSPAN_DOMAIN / 2)
  3244                     colspan -= SN_COLSPAN_DOMAIN;
  3245                 JS_ASSERT(ptrdiff_t(column) + colspan >= 0);
  3246                 column += colspan;
  3247             } if (type == SRC_SETLINE) {
  3248                 lineno = size_t(js_GetSrcNoteOffset(sn, 0));
  3249                 column = 0;
  3250             } else if (type == SRC_NEWLINE) {
  3251                 lineno++;
  3252                 column = 0;
  3255             sn = SN_NEXT(sn);
  3256             snpc += SN_DELTA(sn);
  3260     size_t lineno;
  3261     size_t column;
  3262     jssrcnote *sn;
  3263     jsbytecode *snpc;
  3264 };
  3266 /*
  3267  * FlowGraphSummary::populate(cx, script) computes a summary of script's
  3268  * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}.
  3270  * An instruction on a given line is an entry point for that line if it can be
  3271  * reached from (an instruction on) a different line. We distinguish between the
  3272  * following cases:
  3273  *   - hasNoEdges:
  3274  *       The instruction cannot be reached, so the instruction is not an entry
  3275  *       point for the line it is on.
  3276  *   - hasSingleEdge:
  3277  *   - hasMultipleEdgesFromSingleLine:
  3278  *       The instruction can be reached from a single line. If this line is
  3279  *       different from the line the instruction is on, the instruction is an
  3280  *       entry point for that line.
  3281  *   - hasMultipleEdgesFromMultipleLines:
  3282  *       The instruction can be reached from multiple lines. At least one of
  3283  *       these lines is guaranteed to be different from the line the instruction
  3284  *       is on, so the instruction is an entry point for that line.
  3286  * Similarly, an instruction on a given position (line/column pair) is an
  3287  * entry point for that position if it can be reached from (an instruction on) a
  3288  * different position. Again, we distinguish between the following cases:
  3289  *   - hasNoEdges:
  3290  *       The instruction cannot be reached, so the instruction is not an entry
  3291  *       point for the position it is on.
  3292  *   - hasSingleEdge:
  3293  *       The instruction can be reached from a single position. If this line is
  3294  *       different from the position the instruction is on, the instruction is
  3295  *       an entry point for that position.
  3296  *   - hasMultipleEdgesFromSingleLine:
  3297  *   - hasMultipleEdgesFromMultipleLines:
  3298  *       The instruction can be reached from multiple positions. At least one
  3299  *       of these positions is guaranteed to be different from the position the
  3300  *       instruction is on, so the instruction is an entry point for that
  3301  *       position.
  3302  */
  3303 class FlowGraphSummary {
  3304   public:
  3305     class Entry {
  3306       public:
  3307         static Entry createWithNoEdges() {
  3308             return Entry(SIZE_MAX, 0);
  3311         static Entry createWithSingleEdge(size_t lineno, size_t column) {
  3312             return Entry(lineno, column);
  3315         static Entry createWithMultipleEdgesFromSingleLine(size_t lineno) {
  3316             return Entry(lineno, SIZE_MAX);
  3319         static Entry createWithMultipleEdgesFromMultipleLines() {
  3320             return Entry(SIZE_MAX, SIZE_MAX);
  3323         Entry() {}
  3325         bool hasNoEdges() const {
  3326             return lineno_ == SIZE_MAX && column_ != SIZE_MAX;
  3329         bool hasSingleEdge() const {
  3330             return lineno_ != SIZE_MAX && column_ != SIZE_MAX;
  3333         bool hasMultipleEdgesFromSingleLine() const {
  3334             return lineno_ != SIZE_MAX && column_ == SIZE_MAX;
  3337         bool hasMultipleEdgesFromMultipleLines() const {
  3338             return lineno_ == SIZE_MAX && column_ == SIZE_MAX;
  3341         bool operator==(const Entry &other) const {
  3342             return lineno_ == other.lineno_ && column_ == other.column_;
  3345         bool operator!=(const Entry &other) const {
  3346             return lineno_ != other.lineno_ || column_ != other.column_;
  3349         size_t lineno() const {
  3350             return lineno_;
  3353         size_t column() const {
  3354             return column_;
  3357       private:
  3358         Entry(size_t lineno, size_t column) : lineno_(lineno), column_(column) {}
  3360         size_t lineno_;
  3361         size_t column_;
  3362     };
  3364     FlowGraphSummary(JSContext *cx) : entries_(cx) {}
  3366     Entry &operator[](size_t index) {
  3367         return entries_[index];
  3370     bool populate(JSContext *cx, JSScript *script) {
  3371         if (!entries_.growBy(script->length()))
  3372             return false;
  3373         unsigned mainOffset = script->pcToOffset(script->main());
  3374         entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines();
  3375         for (size_t i = mainOffset + 1; i < script->length(); i++)
  3376             entries_[i] = Entry::createWithNoEdges();
  3378         size_t prevLineno = script->lineno();
  3379         size_t prevColumn = 0;
  3380         JSOp prevOp = JSOP_NOP;
  3381         for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
  3382             size_t lineno = r.frontLineNumber();
  3383             size_t column = r.frontColumnNumber();
  3384             JSOp op = r.frontOpcode();
  3386             if (FlowsIntoNext(prevOp))
  3387                 addEdge(prevLineno, prevColumn, r.frontOffset());
  3389             if (js_CodeSpec[op].type() == JOF_JUMP) {
  3390                 addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
  3391             } else if (op == JSOP_TABLESWITCH) {
  3392                 jsbytecode *pc = r.frontPC();
  3393                 size_t offset = r.frontOffset();
  3394                 ptrdiff_t step = JUMP_OFFSET_LEN;
  3395                 size_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
  3396                 pc += step;
  3397                 addEdge(lineno, column, defaultOffset);
  3399                 int32_t low = GET_JUMP_OFFSET(pc);
  3400                 pc += JUMP_OFFSET_LEN;
  3401                 int ncases = GET_JUMP_OFFSET(pc) - low + 1;
  3402                 pc += JUMP_OFFSET_LEN;
  3404                 for (int i = 0; i < ncases; i++) {
  3405                     size_t target = offset + GET_JUMP_OFFSET(pc);
  3406                     addEdge(lineno, column, target);
  3407                     pc += step;
  3411             prevLineno = lineno;
  3412             prevColumn = column;
  3413             prevOp = op;
  3416         return true;
  3419   private:
  3420     void addEdge(size_t sourceLineno, size_t sourceColumn, size_t targetOffset) {
  3421         if (entries_[targetOffset].hasNoEdges())
  3422             entries_[targetOffset] = Entry::createWithSingleEdge(sourceLineno, sourceColumn);
  3423         else if (entries_[targetOffset].lineno() != sourceLineno)
  3424             entries_[targetOffset] = Entry::createWithMultipleEdgesFromMultipleLines();
  3425         else if (entries_[targetOffset].column() != sourceColumn)
  3426             entries_[targetOffset] = Entry::createWithMultipleEdgesFromSingleLine(sourceLineno);
  3429     Vector<Entry> entries_;
  3430 };
  3432 } /* anonymous namespace */
  3434 static bool
  3435 DebuggerScript_getAllOffsets(JSContext *cx, unsigned argc, Value *vp)
  3437     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
  3439     /*
  3440      * First pass: determine which offsets in this script are jump targets and
  3441      * which line numbers jump to them.
  3442      */
  3443     FlowGraphSummary flowData(cx);
  3444     if (!flowData.populate(cx, script))
  3445         return false;
  3447     /* Second pass: build the result array. */
  3448     RootedObject result(cx, NewDenseEmptyArray(cx));
  3449     if (!result)
  3450         return false;
  3451     for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
  3452         size_t offset = r.frontOffset();
  3453         size_t lineno = r.frontLineNumber();
  3455         /* Make a note, if the current instruction is an entry point for the current line. */
  3456         if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) {
  3457             /* Get the offsets array for this line. */
  3458             RootedObject offsets(cx);
  3459             RootedValue offsetsv(cx);
  3461             RootedId id(cx, INT_TO_JSID(lineno));
  3463             bool found;
  3464             if (!js::HasOwnProperty(cx, result, id, &found))
  3465                 return false;
  3466             if (found && !JSObject::getGeneric(cx, result, result, id, &offsetsv))
  3467                 return false;
  3469             if (offsetsv.isObject()) {
  3470                 offsets = &offsetsv.toObject();
  3471             } else {
  3472                 JS_ASSERT(offsetsv.isUndefined());
  3474                 /*
  3475                  * Create an empty offsets array for this line.
  3476                  * Store it in the result array.
  3477                  */
  3478                 RootedId id(cx);
  3479                 RootedValue v(cx, NumberValue(lineno));
  3480                 offsets = NewDenseEmptyArray(cx);
  3481                 if (!offsets ||
  3482                     !ValueToId<CanGC>(cx, v, &id))
  3484                     return false;
  3487                 RootedValue value(cx, ObjectValue(*offsets));
  3488                 if (!JSObject::defineGeneric(cx, result, id, value))
  3489                     return false;
  3492             /* Append the current offset to the offsets array. */
  3493             if (!NewbornArrayPush(cx, offsets, NumberValue(offset)))
  3494                 return false;
  3498     args.rval().setObject(*result);
  3499     return true;
  3502 static bool
  3503 DebuggerScript_getAllColumnOffsets(JSContext *cx, unsigned argc, Value *vp)
  3505     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllColumnOffsets", args, obj, script);
  3507     /*
  3508      * First pass: determine which offsets in this script are jump targets and
  3509      * which positions jump to them.
  3510      */
  3511     FlowGraphSummary flowData(cx);
  3512     if (!flowData.populate(cx, script))
  3513         return false;
  3515     /* Second pass: build the result array. */
  3516     RootedObject result(cx, NewDenseEmptyArray(cx));
  3517     if (!result)
  3518         return false;
  3519     for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
  3520         size_t lineno = r.frontLineNumber();
  3521         size_t column = r.frontColumnNumber();
  3522         size_t offset = r.frontOffset();
  3524         /* Make a note, if the current instruction is an entry point for the current position. */
  3525         if (!flowData[offset].hasNoEdges() &&
  3526             (flowData[offset].lineno() != lineno ||
  3527              flowData[offset].column() != column)) {
  3528             RootedObject entry(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
  3529             if (!entry)
  3530                 return false;
  3532             RootedId id(cx, NameToId(cx->names().lineNumber));
  3533             RootedValue value(cx, NumberValue(lineno));
  3534             if (!JSObject::defineGeneric(cx, entry, id, value))
  3535                 return false;
  3537             value = NumberValue(column);
  3538             if (!JSObject::defineProperty(cx, entry, cx->names().columnNumber, value))
  3539                 return false;
  3541             id = NameToId(cx->names().offset);
  3542             value = NumberValue(offset);
  3543             if (!JSObject::defineGeneric(cx, entry, id, value))
  3544                 return false;
  3546             if (!NewbornArrayPush(cx, result, ObjectValue(*entry)))
  3547                 return false;
  3551     args.rval().setObject(*result);
  3552     return true;
  3555 static bool
  3556 DebuggerScript_getLineOffsets(JSContext *cx, unsigned argc, Value *vp)
  3558     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script);
  3559     REQUIRE_ARGC("Debugger.Script.getLineOffsets", 1);
  3561     /* Parse lineno argument. */
  3562     RootedValue linenoValue(cx, args[0]);
  3563     size_t lineno;
  3564     if (!ToNumber(cx, &linenoValue))
  3565         return false;
  3567         double d = linenoValue.toNumber();
  3568         lineno = size_t(d);
  3569         if (lineno != d) {
  3570             JS_ReportErrorNumber(cx,  js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE);
  3571             return false;
  3575     /*
  3576      * First pass: determine which offsets in this script are jump targets and
  3577      * which line numbers jump to them.
  3578      */
  3579     FlowGraphSummary flowData(cx);
  3580     if (!flowData.populate(cx, script))
  3581         return false;
  3583     /* Second pass: build the result array. */
  3584     RootedObject result(cx, NewDenseEmptyArray(cx));
  3585     if (!result)
  3586         return false;
  3587     for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
  3588         size_t offset = r.frontOffset();
  3590         /* If the op at offset is an entry point, append offset to result. */
  3591         if (r.frontLineNumber() == lineno &&
  3592             !flowData[offset].hasNoEdges() &&
  3593             flowData[offset].lineno() != lineno)
  3595             if (!NewbornArrayPush(cx, result, NumberValue(offset)))
  3596                 return false;
  3600     args.rval().setObject(*result);
  3601     return true;
  3604 bool
  3605 Debugger::observesFrame(AbstractFramePtr frame) const
  3607     return observesScript(frame.script());
  3610 bool
  3611 Debugger::observesFrame(const ScriptFrameIter &iter) const
  3613     return observesScript(iter.script());
  3616 bool
  3617 Debugger::observesScript(JSScript *script) const
  3619     if (!enabled)
  3620         return false;
  3621     return observesGlobal(&script->global()) && (!script->selfHosted() ||
  3622                                                  SelfHostedFramesVisible());
  3625 /* static */ bool
  3626 Debugger::replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to,
  3627                            ScriptFrameIter &iter)
  3629     for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) {
  3630         RootedObject frameobj(cx, r.frontFrame());
  3631         Debugger *dbg = r.frontDebugger();
  3632         JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
  3634         // Update frame object's ScriptFrameIter::data pointer.
  3635         DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj);
  3636         ScriptFrameIter::Data *data = iter.copyData();
  3637         if (!data)
  3638             return false;
  3639         frameobj->setPrivate(data);
  3641         // Remove the old entry before mutating the HashMap.
  3642         r.removeFrontFrame();
  3644         // Add the frame object with |to| as key.
  3645         if (!dbg->frames.putNew(to, frameobj)) {
  3646             js_ReportOutOfMemory(cx);
  3647             return false;
  3651     return true;
  3654 /* static */ bool
  3655 Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to)
  3657     ScriptFrameIter iter(cx);
  3658     JS_ASSERT(iter.abstractFramePtr() == to);
  3659     return replaceFrameGuts(cx, from, to, iter);
  3662 /* static */ bool
  3663 Debugger::handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to)
  3665     // When we return to a bailed-out Ion real frame, we must update all
  3666     // Debugger.Frames that refer to its inline frames. However, since we
  3667     // can't pop individual inline frames off the stack (we can only pop the
  3668     // real frame that contains them all, as a unit), we cannot assume that
  3669     // the frame we're dealing with is the top frame. Advance the iterator
  3670     // across any inlined frames younger than |to|, the baseline frame
  3671     // reconstructed during bailout from the Ion frame corresponding to
  3672     // |from|.
  3673     ScriptFrameIter iter(cx);
  3674     while (iter.abstractFramePtr() != to)
  3675         ++iter;
  3676     return replaceFrameGuts(cx, from, to, iter);
  3679 static bool
  3680 DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp)
  3682     REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2);
  3683     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
  3684     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3686     if (!dbg->observesScript(script)) {
  3687         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING);
  3688         return false;
  3691     size_t offset;
  3692     if (!ScriptOffset(cx, script, args[0], &offset))
  3693         return false;
  3695     JSObject *handler = NonNullObject(cx, args[1]);
  3696     if (!handler)
  3697         return false;
  3699     jsbytecode *pc = script->offsetToPC(offset);
  3700     BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc);
  3701     if (!site)
  3702         return false;
  3703     site->inc(cx->runtime()->defaultFreeOp());
  3704     if (cx->runtime()->new_<Breakpoint>(dbg, site, handler)) {
  3705         args.rval().setUndefined();
  3706         return true;
  3708     site->dec(cx->runtime()->defaultFreeOp());
  3709     site->destroyIfEmpty(cx->runtime()->defaultFreeOp());
  3710     return false;
  3713 static bool
  3714 DebuggerScript_getBreakpoints(JSContext *cx, unsigned argc, Value *vp)
  3716     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
  3717     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3719     jsbytecode *pc;
  3720     if (args.length() > 0) {
  3721         size_t offset;
  3722         if (!ScriptOffset(cx, script, args[0], &offset))
  3723             return false;
  3724         pc = script->offsetToPC(offset);
  3725     } else {
  3726         pc = nullptr;
  3729     RootedObject arr(cx, NewDenseEmptyArray(cx));
  3730     if (!arr)
  3731         return false;
  3733     for (unsigned i = 0; i < script->length(); i++) {
  3734         BreakpointSite *site = script->getBreakpointSite(script->offsetToPC(i));
  3735         if (site && (!pc || site->pc == pc)) {
  3736             for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
  3737                 if (bp->debugger == dbg &&
  3738                     !NewbornArrayPush(cx, arr, ObjectValue(*bp->getHandler())))
  3740                     return false;
  3745     args.rval().setObject(*arr);
  3746     return true;
  3749 static bool
  3750 DebuggerScript_clearBreakpoint(JSContext *cx, unsigned argc, Value *vp)
  3752     REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1);
  3753     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
  3754     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3756     JSObject *handler = NonNullObject(cx, args[0]);
  3757     if (!handler)
  3758         return false;
  3760     script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, handler);
  3761     args.rval().setUndefined();
  3762     return true;
  3765 static bool
  3766 DebuggerScript_clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp)
  3768     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script);
  3769     Debugger *dbg = Debugger::fromChildJSObject(obj);
  3770     script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, nullptr);
  3771     args.rval().setUndefined();
  3772     return true;
  3775 static bool
  3776 DebuggerScript_isInCatchScope(JSContext *cx, unsigned argc, Value* vp)
  3778     REQUIRE_ARGC("Debugger.Script.isInCatchScope", 1);
  3779     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "isInCatchScope", args, obj, script);
  3781     size_t offset;
  3782     if (!ScriptOffset(cx, script, args[0], &offset))
  3783         return false;
  3785     /*
  3786      * Try note ranges are relative to the mainOffset of the script, so adjust
  3787      * offset accordingly.
  3788      */
  3789     offset -= script->mainOffset();
  3791     args.rval().setBoolean(false);
  3792     if (script->hasTrynotes()) {
  3793         JSTryNote* tnBegin = script->trynotes()->vector;
  3794         JSTryNote* tnEnd = tnBegin + script->trynotes()->length;
  3795         while (tnBegin != tnEnd) {
  3796             if (tnBegin->start <= offset &&
  3797                 offset <= tnBegin->start + tnBegin->length &&
  3798                 tnBegin->kind == JSTRY_CATCH)
  3800                 args.rval().setBoolean(true);
  3801                 break;
  3803             ++tnBegin;
  3806     return true;
  3809 static bool
  3810 DebuggerScript_construct(JSContext *cx, unsigned argc, Value *vp)
  3812     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
  3813                          "Debugger.Script");
  3814     return false;
  3817 static const JSPropertySpec DebuggerScript_properties[] = {
  3818     JS_PSG("url", DebuggerScript_getUrl, 0),
  3819     JS_PSG("startLine", DebuggerScript_getStartLine, 0),
  3820     JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
  3821     JS_PSG("source", DebuggerScript_getSource, 0),
  3822     JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0),
  3823     JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0),
  3824     JS_PSG("staticLevel", DebuggerScript_getStaticLevel, 0),
  3825     JS_PSG("sourceMapURL", DebuggerScript_getSourceMapUrl, 0),
  3826     JS_PSG("global", DebuggerScript_getGlobal, 0),
  3827     JS_PS_END
  3828 };
  3830 static const JSFunctionSpec DebuggerScript_methods[] = {
  3831     JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0),
  3832     JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0),
  3833     JS_FN("getAllColumnOffsets", DebuggerScript_getAllColumnOffsets, 0, 0),
  3834     JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0),
  3835     JS_FN("getOffsetLine", DebuggerScript_getOffsetLine, 0, 0),
  3836     JS_FN("setBreakpoint", DebuggerScript_setBreakpoint, 2, 0),
  3837     JS_FN("getBreakpoints", DebuggerScript_getBreakpoints, 1, 0),
  3838     JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0),
  3839     JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0),
  3840     JS_FN("isInCatchScope", DebuggerScript_isInCatchScope, 1, 0),
  3841     JS_FS_END
  3842 };
  3845 /*** Debugger.Source *****************************************************************************/
  3847 static inline ScriptSourceObject *
  3848 GetSourceReferent(JSObject *obj)
  3850     JS_ASSERT(obj->getClass() == &DebuggerSource_class);
  3851     return static_cast<ScriptSourceObject *>(obj->getPrivate());
  3854 static void
  3855 DebuggerSource_trace(JSTracer *trc, JSObject *obj)
  3857     /*
  3858      * There is a barrier on private pointers, so the Unbarriered marking
  3859      * is okay.
  3860      */
  3861     if (JSObject *referent = GetSourceReferent(obj)) {
  3862         MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Source referent");
  3863         obj->setPrivateUnbarriered(referent);
  3867 const Class DebuggerSource_class = {
  3868     "Source",
  3869     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
  3870     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT),
  3871     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  3872     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr,
  3873     nullptr,              /* call        */
  3874     nullptr,              /* hasInstance */
  3875     nullptr,              /* construct   */
  3876     DebuggerSource_trace
  3877 };
  3879 JSObject *
  3880 Debugger::newDebuggerSource(JSContext *cx, HandleScriptSource source)
  3882     assertSameCompartment(cx, object.get());
  3884     JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject();
  3885     JS_ASSERT(proto);
  3886     JSObject *sourceobj = NewObjectWithGivenProto(cx, &DebuggerSource_class, proto, nullptr, TenuredObject);
  3887     if (!sourceobj)
  3888         return nullptr;
  3889     sourceobj->setReservedSlot(JSSLOT_DEBUGSOURCE_OWNER, ObjectValue(*object));
  3890     sourceobj->setPrivateGCThing(source);
  3892     return sourceobj;
  3895 JSObject *
  3896 Debugger::wrapSource(JSContext *cx, HandleScriptSource source)
  3898     assertSameCompartment(cx, object.get());
  3899     JS_ASSERT(cx->compartment() != source->compartment());
  3900     DependentAddPtr<SourceWeakMap> p(cx, sources, source);
  3901     if (!p) {
  3902         JSObject *sourceobj = newDebuggerSource(cx, source);
  3903         if (!sourceobj)
  3904             return nullptr;
  3906         if (!p.add(cx, sources, source, sourceobj)) {
  3907             js_ReportOutOfMemory(cx);
  3908             return nullptr;
  3911         CrossCompartmentKey key(CrossCompartmentKey::DebuggerSource, object, source);
  3912         if (!object->compartment()->putWrapper(cx, key, ObjectValue(*sourceobj))) {
  3913             sources.remove(source);
  3914             js_ReportOutOfMemory(cx);
  3915             return nullptr;
  3919     JS_ASSERT(GetSourceReferent(p->value()) == source);
  3920     return p->value();
  3923 static bool
  3924 DebuggerSource_construct(JSContext *cx, unsigned argc, Value *vp)
  3926     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
  3927                          "Debugger.Source");
  3928     return false;
  3931 static JSObject *
  3932 DebuggerSource_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
  3934     if (!args.thisv().isObject()) {
  3935         ReportObjectRequired(cx);
  3936         return nullptr;
  3939     JSObject *thisobj = &args.thisv().toObject();
  3940     if (thisobj->getClass() != &DebuggerSource_class) {
  3941         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  3942                              "Debugger.Source", fnname, thisobj->getClass()->name);
  3943         return nullptr;
  3946     if (!GetSourceReferent(thisobj)) {
  3947         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  3948                              "Debugger.Frame", fnname, "prototype object");
  3949         return nullptr;
  3952     return thisobj;
  3955 #define THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, fnname, args, obj, sourceObject)    \
  3956     CallArgs args = CallArgsFromVp(argc, vp);                                       \
  3957     RootedObject obj(cx, DebuggerSource_checkThis(cx, args, fnname));               \
  3958     if (!obj)                                                                       \
  3959         return false;                                                               \
  3960     RootedScriptSource sourceObject(cx, GetSourceReferent(obj));                    \
  3961     if (!sourceObject)                                                              \
  3962         return false;
  3964 static bool
  3965 DebuggerSource_getText(JSContext *cx, unsigned argc, Value *vp)
  3967     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get text)", args, obj, sourceObject);
  3969     ScriptSource *ss = sourceObject->source();
  3970     bool hasSourceData = ss->hasSourceData();
  3971     if (!ss->hasSourceData() && !JSScript::loadSource(cx, ss, &hasSourceData))
  3972         return false;
  3974     JSString *str = hasSourceData ? ss->substring(cx, 0, ss->length())
  3975                                   : js_NewStringCopyZ<CanGC>(cx, "[no source]");
  3976     if (!str)
  3977         return false;
  3979     args.rval().setString(str);
  3980     return true;
  3983 static bool
  3984 DebuggerSource_getUrl(JSContext *cx, unsigned argc, Value *vp)
  3986     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject);
  3988     ScriptSource *ss = sourceObject->source();
  3989     if (ss->filename()) {
  3990         JSString *str = js_NewStringCopyZ<CanGC>(cx, ss->filename());
  3991         if (!str)
  3992             return false;
  3993         args.rval().setString(str);
  3994     } else {
  3995         args.rval().setNull();
  3997     return true;
  4000 static bool
  4001 DebuggerSource_getDisplayURL(JSContext *cx, unsigned argc, Value *vp)
  4003     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject);
  4005     ScriptSource *ss = sourceObject->source();
  4006     JS_ASSERT(ss);
  4008     if (ss->hasDisplayURL()) {
  4009         JSString *str = JS_NewUCStringCopyZ(cx, ss->displayURL());
  4010         if (!str)
  4011             return false;
  4012         args.rval().setString(str);
  4013     } else {
  4014         args.rval().setNull();
  4017     return true;
  4020 static bool
  4021 DebuggerSource_getElement(JSContext *cx, unsigned argc, Value *vp)
  4023     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get element)", args, obj, sourceObject);
  4025     if (sourceObject->element()) {
  4026         args.rval().setObjectOrNull(sourceObject->element());
  4027         if (!Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval()))
  4028             return false;
  4029     } else {
  4030         args.rval().setUndefined();
  4032     return true;
  4035 static bool
  4036 DebuggerSource_getElementProperty(JSContext *cx, unsigned argc, Value *vp)
  4038     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get elementAttributeName)", args, obj, sourceObject);
  4039     args.rval().set(sourceObject->elementAttributeName());
  4040     return Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval());
  4043 static bool
  4044 DebuggerSource_getIntroductionScript(JSContext *cx, unsigned argc, Value *vp)
  4046     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionScript)", args, obj, sourceObject);
  4048     RootedScript script(cx, sourceObject->introductionScript());
  4049     if (script) {
  4050         RootedObject scriptDO(cx, Debugger::fromChildJSObject(obj)->wrapScript(cx, script));
  4051         if (!scriptDO)
  4052             return false;
  4053         args.rval().setObject(*scriptDO);
  4054     } else {
  4055         args.rval().setUndefined();
  4057     return true;
  4060 static bool
  4061 DebuggerSource_getIntroductionOffset(JSContext *cx, unsigned argc, Value *vp)
  4063     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionOffset)", args, obj, sourceObject);
  4065     // Regardless of what's recorded in the ScriptSourceObject and
  4066     // ScriptSource, only hand out the introduction offset if we also have
  4067     // the script within which it applies.
  4068     ScriptSource *ss = sourceObject->source();
  4069     if (ss->hasIntroductionOffset() && sourceObject->introductionScript())
  4070         args.rval().setInt32(ss->introductionOffset());
  4071     else
  4072         args.rval().setUndefined();
  4073     return true;
  4076 static bool
  4077 DebuggerSource_getIntroductionType(JSContext *cx, unsigned argc, Value *vp)
  4079     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionType)", args, obj, sourceObject);
  4081     ScriptSource *ss = sourceObject->source();
  4082     if (ss->hasIntroductionType()) {
  4083         JSString *str = js_NewStringCopyZ<CanGC>(cx, ss->introductionType());
  4084         if (!str)
  4085             return false;
  4086         args.rval().setString(str);
  4087     } else {
  4088         args.rval().setUndefined();
  4090     return true;
  4093 static const JSPropertySpec DebuggerSource_properties[] = {
  4094     JS_PSG("text", DebuggerSource_getText, 0),
  4095     JS_PSG("url", DebuggerSource_getUrl, 0),
  4096     JS_PSG("element", DebuggerSource_getElement, 0),
  4097     JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0),
  4098     JS_PSG("introductionScript", DebuggerSource_getIntroductionScript, 0),
  4099     JS_PSG("introductionOffset", DebuggerSource_getIntroductionOffset, 0),
  4100     JS_PSG("introductionType", DebuggerSource_getIntroductionType, 0),
  4101     JS_PSG("elementAttributeName", DebuggerSource_getElementProperty, 0),
  4102     JS_PS_END
  4103 };
  4105 static const JSFunctionSpec DebuggerSource_methods[] = {
  4106     JS_FS_END
  4107 };
  4110 /*** Debugger.Frame ******************************************************************************/
  4112 static void
  4113 UpdateFrameIterPc(FrameIter &iter)
  4115     if (iter.abstractFramePtr().isRematerializedFrame()) {
  4116 #ifdef DEBUG
  4117         // Rematerialized frames don't need their pc updated. The reason we
  4118         // need to update pc is because we might get the same Debugger.Frame
  4119         // object for multiple re-entries into debugger code from debuggee
  4120         // code. This reentrancy is not possible with rematerialized frames,
  4121         // because when returning to debuggee code, we would have bailed out
  4122         // to baseline.
  4123         //
  4124         // We walk the stack to assert that it doesn't need updating.
  4125         jit::RematerializedFrame *frame = iter.abstractFramePtr().asRematerializedFrame();
  4126         jit::IonJSFrameLayout *jsFrame = (jit::IonJSFrameLayout *)frame->top();
  4127         jit::JitActivation *activation = iter.activation()->asJit();
  4129         ActivationIterator activationIter(activation->cx()->runtime());
  4130         while (activationIter.activation() != activation)
  4131             ++activationIter;
  4133         jit::JitFrameIterator jitIter(activationIter);
  4134         while (!jitIter.isIonJS() || jitIter.jsFrame() != jsFrame)
  4135             ++jitIter;
  4137         jit::InlineFrameIterator ionInlineIter(activation->cx(), &jitIter);
  4138         while (ionInlineIter.frameNo() != frame->frameNo())
  4139             ++ionInlineIter;
  4141         MOZ_ASSERT(ionInlineIter.pc() == iter.pc());
  4142 #endif
  4143         return;
  4146     iter.updatePcQuadratic();
  4149 static void
  4150 DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj)
  4152     AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->getPrivate());
  4153     if (frame.isScriptFrameIterData())
  4154         fop->delete_((ScriptFrameIter::Data *) frame.raw());
  4155     obj->setPrivate(nullptr);
  4158 static void
  4159 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame,
  4160                                                      JSObject *frameobj)
  4162     /* If this frame has an onStep handler, decrement the script's count. */
  4163     if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
  4164         frame.script()->decrementStepModeCount(fop);
  4167 static void
  4168 DebuggerFrame_finalize(FreeOp *fop, JSObject *obj)
  4170     DebuggerFrame_freeScriptFrameIterData(fop, obj);
  4173 const Class DebuggerFrame_class = {
  4174     "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
  4175     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  4176     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, DebuggerFrame_finalize
  4177 };
  4179 static JSObject *
  4180 CheckThisFrame(JSContext *cx, const CallArgs &args, const char *fnname, bool checkLive)
  4182     if (!args.thisv().isObject()) {
  4183         ReportObjectRequired(cx);
  4184         return nullptr;
  4186     JSObject *thisobj = &args.thisv().toObject();
  4187     if (thisobj->getClass() != &DebuggerFrame_class) {
  4188         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  4189                              "Debugger.Frame", fnname, thisobj->getClass()->name);
  4190         return nullptr;
  4193     /*
  4194      * Forbid Debugger.Frame.prototype, which is of class DebuggerFrame_class
  4195      * but isn't really a working Debugger.Frame object. The prototype object
  4196      * is distinguished by having a nullptr private value. Also, forbid popped
  4197      * frames.
  4198      */
  4199     if (!thisobj->getPrivate()) {
  4200         if (thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).isUndefined()) {
  4201             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  4202                                  "Debugger.Frame", fnname, "prototype object");
  4203             return nullptr;
  4205         if (checkLive) {
  4206             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
  4207                                  "Debugger.Frame");
  4208             return nullptr;
  4211     return thisobj;
  4214 /*
  4215  * To make frequently fired hooks like onEnterFrame more performant,
  4216  * Debugger.Frame methods should not create a ScriptFrameIter unless it
  4217  * absolutely needs to. That is, unless the method has to call a method on
  4218  * ScriptFrameIter that's otherwise not available on AbstractFramePtr.
  4220  * When a Debugger.Frame is first created, its private slot is set to the
  4221  * AbstractFramePtr itself. The first time the users asks for a
  4222  * ScriptFrameIter, we construct one, have it settle on the frame pointed to
  4223  * by the AbstractFramePtr and cache its internal Data in the Debugger.Frame
  4224  * object's private slot. Subsequent uses of the Debugger.Frame object will
  4225  * always create a ScriptFrameIter from the cached Data.
  4227  * Methods that only need the AbstractFramePtr should use THIS_FRAME.
  4228  * Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER.
  4229  */
  4231 #define THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj)                \
  4232     CallArgs args = CallArgsFromVp(argc, vp);                                  \
  4233     RootedObject thisobj(cx, CheckThisFrame(cx, args, fnname, true));          \
  4234     if (!thisobj)                                                              \
  4235         return false
  4237 #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame)                 \
  4238     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
  4239     AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
  4240     if (frame.isScriptFrameIterData()) {                                       \
  4241         ScriptFrameIter iter(*(ScriptFrameIter::Data *)(frame.raw()));         \
  4242         frame = iter.abstractFramePtr();                                       \
  4245 #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter)  \
  4246     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
  4247     Maybe<ScriptFrameIter> maybeIter;                                          \
  4248     {                                                                          \
  4249         AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
  4250         if (f.isScriptFrameIterData()) {                                       \
  4251             maybeIter.construct(*(ScriptFrameIter::Data *)(f.raw()));          \
  4252         } else {                                                               \
  4253             maybeIter.construct(cx, ScriptFrameIter::ALL_CONTEXTS,             \
  4254                                 ScriptFrameIter::GO_THROUGH_SAVED);            \
  4255             ScriptFrameIter &iter = maybeIter.ref();                           \
  4256             while (iter.isIon() || iter.abstractFramePtr() != f)               \
  4257                 ++iter;                                                        \
  4258             AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();         \
  4259             if (!data)                                                         \
  4260                 return false;                                                  \
  4261             thisobj->setPrivate(data.raw());                                   \
  4262         }                                                                      \
  4263     }                                                                          \
  4264     ScriptFrameIter &iter = maybeIter.ref()
  4266 #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg)      \
  4267     THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame);                    \
  4268     Debugger *dbg = Debugger::fromChildJSObject(thisobj)
  4270 #define THIS_FRAME_OWNER_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter, dbg) \
  4271     THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter);               \
  4272     Debugger *dbg = Debugger::fromChildJSObject(thisobj)
  4274 static bool
  4275 DebuggerFrame_getType(JSContext *cx, unsigned argc, Value *vp)
  4277     THIS_FRAME(cx, argc, vp, "get type", args, thisobj, frame);
  4279     /*
  4280      * Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the
  4281      * order of checks here is significant.
  4282      */
  4283     args.rval().setString(frame.isEvalFrame()
  4284                           ? cx->names().eval
  4285                           : frame.isGlobalFrame()
  4286                           ? cx->names().global
  4287                           : cx->names().call);
  4288     return true;
  4291 static bool
  4292 DebuggerFrame_getImplementation(JSContext *cx, unsigned argc, Value *vp)
  4294     THIS_FRAME(cx, argc, vp, "get implementation", args, thisobj, frame);
  4296     const char *s;
  4297     if (frame.isBaselineFrame())
  4298         s = "baseline";
  4299     else if (frame.isRematerializedFrame())
  4300         s = "ion";
  4301     else
  4302         s = "interpreter";
  4304     JSAtom *str = Atomize(cx, s, strlen(s));
  4305     if (!str)
  4306         return false;
  4308     args.rval().setString(str);
  4309     return true;
  4312 static bool
  4313 DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
  4315     THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg);
  4317     Rooted<Env*> env(cx);
  4319         AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain());
  4320         UpdateFrameIterPc(iter);
  4321         env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc());
  4322         if (!env)
  4323             return false;
  4326     return dbg->wrapEnvironment(cx, env, args.rval());
  4329 static bool
  4330 DebuggerFrame_getCallee(JSContext *cx, unsigned argc, Value *vp)
  4332     THIS_FRAME(cx, argc, vp, "get callee", args, thisobj, frame);
  4333     RootedValue calleev(cx, frame.isNonEvalFunctionFrame() ? frame.calleev() : NullValue());
  4334     if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &calleev))
  4335         return false;
  4336     args.rval().set(calleev);
  4337     return true;
  4340 static bool
  4341 DebuggerFrame_getGenerator(JSContext *cx, unsigned argc, Value *vp)
  4343     THIS_FRAME(cx, argc, vp, "get generator", args, thisobj, frame);
  4344     args.rval().setBoolean(frame.isGeneratorFrame());
  4345     return true;
  4348 static bool
  4349 DebuggerFrame_getConstructing(JSContext *cx, unsigned argc, Value *vp)
  4351     THIS_FRAME_ITER(cx, argc, vp, "get constructing", args, thisobj, _, iter);
  4352     args.rval().setBoolean(iter.isFunctionFrame() && iter.isConstructing());
  4353     return true;
  4356 static bool
  4357 DebuggerFrame_getThis(JSContext *cx, unsigned argc, Value *vp)
  4359     THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter);
  4360     RootedValue thisv(cx);
  4362         AutoCompartment ac(cx, iter.scopeChain());
  4363         if (!iter.computeThis(cx))
  4364             return false;
  4365         thisv = iter.computedThisValue();
  4367     if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &thisv))
  4368         return false;
  4369     args.rval().set(thisv);
  4370     return true;
  4373 static bool
  4374 DebuggerFrame_getOlder(JSContext *cx, unsigned argc, Value *vp)
  4376     THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter);
  4377     Debugger *dbg = Debugger::fromChildJSObject(thisobj);
  4379     for (++iter; !iter.done(); ++iter) {
  4380         if (dbg->observesFrame(iter)) {
  4381             if (iter.isIon() && !iter.ensureHasRematerializedFrame())
  4382                 return false;
  4383             return dbg->getScriptFrame(cx, iter, args.rval());
  4386     args.rval().setNull();
  4387     return true;
  4390 const Class DebuggerArguments_class = {
  4391     "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT),
  4392     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  4393     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
  4394 };
  4396 /* The getter used for each element of frame.arguments. See DebuggerFrame_getArguments. */
  4397 static bool
  4398 DebuggerArguments_getArg(JSContext *cx, unsigned argc, Value *vp)
  4400     CallArgs args = CallArgsFromVp(argc, vp);
  4401     int32_t i = args.callee().as<JSFunction>().getExtendedSlot(0).toInt32();
  4403     /* Check that the this value is an Arguments object. */
  4404     if (!args.thisv().isObject()) {
  4405         ReportObjectRequired(cx);
  4406         return false;
  4408     RootedObject argsobj(cx, &args.thisv().toObject());
  4409     if (argsobj->getClass() != &DebuggerArguments_class) {
  4410         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  4411                              "Arguments", "getArgument", argsobj->getClass()->name);
  4412         return false;
  4415     /*
  4416      * Put the Debugger.Frame into the this-value slot, then use THIS_FRAME
  4417      * to check that it is still live and get the fp.
  4418      */
  4419     args.setThis(argsobj->getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME));
  4420     THIS_FRAME(cx, argc, vp, "get argument", ca2, thisobj, frame);
  4422     /*
  4423      * Since getters can be extracted and applied to other objects,
  4424      * there is no guarantee this object has an ith argument.
  4425      */
  4426     JS_ASSERT(i >= 0);
  4427     RootedValue arg(cx);
  4428     RootedScript script(cx);
  4429     if (unsigned(i) < frame.numActualArgs()) {
  4430         script = frame.script();
  4431         if (unsigned(i) < frame.numFormalArgs() && script->formalIsAliased(i)) {
  4432             for (AliasedFormalIter fi(script); ; fi++) {
  4433                 if (fi.frameIndex() == unsigned(i)) {
  4434                     arg = frame.callObj().aliasedVar(fi);
  4435                     break;
  4438         } else if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
  4439             arg = frame.argsObj().arg(i);
  4440         } else {
  4441             arg = frame.unaliasedActual(i, DONT_CHECK_ALIASING);
  4443     } else {
  4444         arg.setUndefined();
  4447     if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg))
  4448         return false;
  4449     args.rval().set(arg);
  4450     return true;
  4453 static bool
  4454 DebuggerFrame_getArguments(JSContext *cx, unsigned argc, Value *vp)
  4456     THIS_FRAME(cx, argc, vp, "get arguments", args, thisobj, frame);
  4457     Value argumentsv = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS);
  4458     if (!argumentsv.isUndefined()) {
  4459         JS_ASSERT(argumentsv.isObjectOrNull());
  4460         args.rval().set(argumentsv);
  4461         return true;
  4464     RootedObject argsobj(cx);
  4465     if (frame.hasArgs()) {
  4466         /* Create an arguments object. */
  4467         Rooted<GlobalObject*> global(cx, &args.callee().global());
  4468         JSObject *proto = GlobalObject::getOrCreateArrayPrototype(cx, global);
  4469         if (!proto)
  4470             return false;
  4471         argsobj = NewObjectWithGivenProto(cx, &DebuggerArguments_class, proto, global);
  4472         if (!argsobj)
  4473             return false;
  4474         SetReservedSlot(argsobj, JSSLOT_DEBUGARGUMENTS_FRAME, ObjectValue(*thisobj));
  4476         JS_ASSERT(frame.numActualArgs() <= 0x7fffffff);
  4477         unsigned fargc = frame.numActualArgs();
  4478         RootedValue fargcVal(cx, Int32Value(fargc));
  4479         if (!DefineNativeProperty(cx, argsobj, cx->names().length,
  4480                                   fargcVal, nullptr, nullptr,
  4481                                   JSPROP_PERMANENT | JSPROP_READONLY))
  4483             return false;
  4486         Rooted<jsid> id(cx);
  4487         for (unsigned i = 0; i < fargc; i++) {
  4488             RootedFunction getobj(cx);
  4489             getobj = NewFunction(cx, js::NullPtr(), DebuggerArguments_getArg, 0,
  4490                                  JSFunction::NATIVE_FUN, global, js::NullPtr(),
  4491                                  JSFunction::ExtendedFinalizeKind);
  4492             if (!getobj)
  4493                 return false;
  4494             id = INT_TO_JSID(i);
  4495             if (!getobj ||
  4496                 !DefineNativeProperty(cx, argsobj, id, UndefinedHandleValue,
  4497                                       JS_DATA_TO_FUNC_PTR(PropertyOp, getobj.get()), nullptr,
  4498                                       JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER))
  4500                 return false;
  4502             getobj->setExtendedSlot(0, Int32Value(i));
  4504     } else {
  4505         argsobj = nullptr;
  4507     args.rval().setObjectOrNull(argsobj);
  4508     thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, args.rval());
  4509     return true;
  4512 static bool
  4513 DebuggerFrame_getScript(JSContext *cx, unsigned argc, Value *vp)
  4515     THIS_FRAME(cx, argc, vp, "get script", args, thisobj, frame);
  4516     Debugger *debug = Debugger::fromChildJSObject(thisobj);
  4518     RootedObject scriptObject(cx);
  4519     if (frame.isFunctionFrame() && !frame.isEvalFrame()) {
  4520         RootedFunction callee(cx, frame.callee());
  4521         if (callee->isInterpreted()) {
  4522             RootedScript script(cx, callee->nonLazyScript());
  4523             scriptObject = debug->wrapScript(cx, script);
  4524             if (!scriptObject)
  4525                 return false;
  4527     } else {
  4528         /*
  4529          * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script
  4530          * frames.
  4531          */
  4532         RootedScript script(cx, frame.script());
  4533         scriptObject = debug->wrapScript(cx, script);
  4534         if (!scriptObject)
  4535             return false;
  4537     args.rval().setObjectOrNull(scriptObject);
  4538     return true;
  4541 static bool
  4542 DebuggerFrame_getOffset(JSContext *cx, unsigned argc, Value *vp)
  4544     THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter);
  4545     JSScript *script = iter.script();
  4546     UpdateFrameIterPc(iter);
  4547     jsbytecode *pc = iter.pc();
  4548     size_t offset = script->pcToOffset(pc);
  4549     args.rval().setNumber(double(offset));
  4550     return true;
  4553 static bool
  4554 DebuggerFrame_getLive(JSContext *cx, unsigned argc, Value *vp)
  4556     CallArgs args = CallArgsFromVp(argc, vp);
  4557     JSObject *thisobj = CheckThisFrame(cx, args, "get live", false);
  4558     if (!thisobj)
  4559         return false;
  4560     bool hasFrame = !!thisobj->getPrivate();
  4561     args.rval().setBoolean(hasFrame);
  4562     return true;
  4565 static bool
  4566 IsValidHook(const Value &v)
  4568     return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
  4571 static bool
  4572 DebuggerFrame_getOnStep(JSContext *cx, unsigned argc, Value *vp)
  4574     THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, frame);
  4575     (void) frame;  // Silence GCC warning
  4576     Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
  4577     JS_ASSERT(IsValidHook(handler));
  4578     args.rval().set(handler);
  4579     return true;
  4582 static bool
  4583 DebuggerFrame_setOnStep(JSContext *cx, unsigned argc, Value *vp)
  4585     REQUIRE_ARGC("Debugger.Frame.set onStep", 1);
  4586     THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame);
  4587     if (!IsValidHook(args[0])) {
  4588         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
  4589         return false;
  4592     Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
  4593     if (!args[0].isUndefined() && prior.isUndefined()) {
  4594         // Single stepping toggled off->on.
  4595         AutoCompartment ac(cx, frame.scopeChain());
  4596         if (!frame.script()->incrementStepModeCount(cx))
  4597             return false;
  4598     } else if (args[0].isUndefined() && !prior.isUndefined()) {
  4599         // Single stepping toggled on->off.
  4600         frame.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp());
  4603     /* Now that the step mode switch has succeeded, we can install the handler. */
  4604     thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]);
  4605     args.rval().setUndefined();
  4606     return true;
  4609 static bool
  4610 DebuggerFrame_getOnPop(JSContext *cx, unsigned argc, Value *vp)
  4612     THIS_FRAME(cx, argc, vp, "get onPop", args, thisobj, frame);
  4613     (void) frame;  // Silence GCC warning
  4614     Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER);
  4615     JS_ASSERT(IsValidHook(handler));
  4616     args.rval().set(handler);
  4617     return true;
  4620 static bool
  4621 DebuggerFrame_setOnPop(JSContext *cx, unsigned argc, Value *vp)
  4623     REQUIRE_ARGC("Debugger.Frame.set onPop", 1);
  4624     THIS_FRAME(cx, argc, vp, "set onPop", args, thisobj, frame);
  4625     (void) frame;  // Silence GCC warning
  4626     if (!IsValidHook(args[0])) {
  4627         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
  4628         return false;
  4631     thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]);
  4632     args.rval().setUndefined();
  4633     return true;
  4636 /*
  4637  * Evaluate |chars[0..length-1]| in the environment |env|, treating that
  4638  * source as appearing starting at |lineno| in |filename|. Store the return
  4639  * value in |*rval|. Use |thisv| as the 'this' value.
  4641  * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
  4642  * must be either |frame|'s DebugScopeObject, or some extension of that
  4643  * environment; either way, |frame|'s scope is where newly declared variables
  4644  * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|.
  4645  */
  4646 bool
  4647 js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
  4648                   ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno,
  4649                   MutableHandleValue rval)
  4651     assertSameCompartment(cx, env, frame);
  4652     JS_ASSERT_IF(frame, thisv.get() == frame.thisValue());
  4654     JS_ASSERT(!IsPoisonedPtr(chars.get()));
  4656     /*
  4657      * NB: This function breaks the assumption that the compiler can see all
  4658      * calls and properly compute a static level. In practice, any non-zero
  4659      * static level will suffice.
  4660      */
  4661     CompileOptions options(cx);
  4662     options.setCompileAndGo(true)
  4663            .setForEval(true)
  4664            .setNoScriptRval(false)
  4665            .setFileAndLine(filename, lineno)
  4666            .setCanLazilyParse(false)
  4667            .setIntroductionType("debugger eval");
  4668     RootedScript callerScript(cx, frame ? frame.script() : nullptr);
  4669     SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership);
  4670     RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript,
  4671                                                     options, srcBuf,
  4672                                                     /* source = */ nullptr,
  4673                                                     /* staticLevel = */ frame ? 1 : 0));
  4674     if (!script)
  4675         return false;
  4677     script->setActiveEval();
  4678     ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG;
  4679     return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address());
  4682 enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false };
  4684 static bool
  4685 DebuggerGenericEval(JSContext *cx, const char *fullMethodName, const Value &code,
  4686                     EvalBindings evalWithBindings, HandleValue bindings, HandleValue options,
  4687                     MutableHandleValue vp, Debugger *dbg, HandleObject scope,
  4688                     ScriptFrameIter *iter)
  4690     /* Either we're specifying the frame, or a global. */
  4691     JS_ASSERT_IF(iter, !scope);
  4692     JS_ASSERT_IF(!iter, scope && scope->is<GlobalObject>());
  4694     /* Check the first argument, the eval code string. */
  4695     if (!code.isString()) {
  4696         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
  4697                              fullMethodName, "string", InformalValueTypeName(code));
  4698         return false;
  4700     Rooted<JSFlatString *> flat(cx, code.toString()->ensureFlat(cx));
  4701     if (!flat)
  4702         return false;
  4704     /*
  4705      * Gather keys and values of bindings, if any. This must be done in the
  4706      * debugger compartment, since that is where any exceptions must be
  4707      * thrown.
  4708      */
  4709     AutoIdVector keys(cx);
  4710     AutoValueVector values(cx);
  4711     if (evalWithBindings) {
  4712         RootedObject bindingsobj(cx, NonNullObject(cx, bindings));
  4713         if (!bindingsobj ||
  4714             !GetPropertyNames(cx, bindingsobj, JSITER_OWNONLY, &keys) ||
  4715             !values.growBy(keys.length()))
  4717             return false;
  4719         for (size_t i = 0; i < keys.length(); i++) {
  4720             MutableHandleValue valp = values.handleAt(i);
  4721             if (!JSObject::getGeneric(cx, bindingsobj, bindingsobj, keys.handleAt(i), valp) ||
  4722                 !dbg->unwrapDebuggeeValue(cx, valp))
  4724                 return false;
  4729     /* Set options from object if provided. */
  4730     JSAutoByteString url_bytes;
  4731     char *url = nullptr;
  4732     unsigned lineNumber = 1;
  4734     if (options.isObject()) {
  4735         RootedObject opts(cx, &options.toObject());
  4736         RootedValue v(cx);
  4738         if (!JS_GetProperty(cx, opts, "url", &v))
  4739             return false;
  4740         if (!v.isUndefined()) {
  4741             RootedString url_str(cx, ToString<CanGC>(cx, v));
  4742             if (!url_str)
  4743                 return false;
  4744             url = url_bytes.encodeLatin1(cx, url_str);
  4745             if (!url)
  4746                 return false;
  4749         if (!JS_GetProperty(cx, opts, "lineNumber", &v))
  4750             return false;
  4751         if (!v.isUndefined()) {
  4752             uint32_t lineno;
  4753             if (!ToUint32(cx, v, &lineno))
  4754                 return false;
  4755             lineNumber = lineno;
  4759     Maybe<AutoCompartment> ac;
  4760     if (iter)
  4761         ac.construct(cx, iter->scopeChain());
  4762     else
  4763         ac.construct(cx, scope);
  4765     RootedValue thisv(cx);
  4766     Rooted<Env *> env(cx);
  4767     if (iter) {
  4768         /* ExecuteInEnv requires 'fp' to have a computed 'this" value. */
  4769         if (!iter->computeThis(cx))
  4770             return false;
  4771         thisv = iter->computedThisValue();
  4772         env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc());
  4773         if (!env)
  4774             return false;
  4775     } else {
  4776         /*
  4777          * Use the global as 'this'. If the global is an inner object, it
  4778          * should have a thisObject hook that returns the appropriate outer
  4779          * object.
  4780          */
  4781         JSObject *thisObj = JSObject::thisObject(cx, scope);
  4782         if (!thisObj)
  4783             return false;
  4784         thisv = ObjectValue(*thisObj);
  4785         env = scope;
  4788     /* If evalWithBindings, create the inner environment. */
  4789     if (evalWithBindings) {
  4790         /* TODO - This should probably be a Call object, like ES5 strict eval. */
  4791         env = NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, env);
  4792         if (!env)
  4793             return false;
  4794         RootedId id(cx);
  4795         for (size_t i = 0; i < keys.length(); i++) {
  4796             id = keys[i];
  4797             MutableHandleValue val = values.handleAt(i);
  4798             if (!cx->compartment()->wrap(cx, val) ||
  4799                 !DefineNativeProperty(cx, env, id, val, nullptr, nullptr, 0))
  4801                 return false;
  4806     /* Run the code and produce the completion value. */
  4807     RootedValue rval(cx);
  4808     JS::Anchor<JSString *> anchor(flat);
  4809     AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
  4810     bool ok = EvaluateInEnv(cx, env, thisv, frame,
  4811                             ConstTwoByteChars(flat->chars(), flat->length()),
  4812                             flat->length(), url ? url : "debugger eval code", lineNumber, &rval);
  4813     return dbg->receiveCompletionValue(ac, ok, rval, vp);
  4816 static bool
  4817 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
  4819     THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
  4820     REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1);
  4821     Debugger *dbg = Debugger::fromChildJSObject(thisobj);
  4822     UpdateFrameIterPc(iter);
  4823     return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval",
  4824                                args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
  4825                                args.get(1), args.rval(), dbg, js::NullPtr(), &iter);
  4828 static bool
  4829 DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp)
  4831     THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter);
  4832     REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2);
  4833     Debugger *dbg = Debugger::fromChildJSObject(thisobj);
  4834     UpdateFrameIterPc(iter);
  4835     return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings",
  4836                                args[0], EvalHasExtraBindings, args[1], args.get(2),
  4837                                args.rval(), dbg, js::NullPtr(), &iter);
  4840 static bool
  4841 DebuggerFrame_construct(JSContext *cx, unsigned argc, Value *vp)
  4843     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
  4844                          "Debugger.Frame");
  4845     return false;
  4848 static const JSPropertySpec DebuggerFrame_properties[] = {
  4849     JS_PSG("arguments", DebuggerFrame_getArguments, 0),
  4850     JS_PSG("callee", DebuggerFrame_getCallee, 0),
  4851     JS_PSG("constructing", DebuggerFrame_getConstructing, 0),
  4852     JS_PSG("environment", DebuggerFrame_getEnvironment, 0),
  4853     JS_PSG("generator", DebuggerFrame_getGenerator, 0),
  4854     JS_PSG("live", DebuggerFrame_getLive, 0),
  4855     JS_PSG("offset", DebuggerFrame_getOffset, 0),
  4856     JS_PSG("older", DebuggerFrame_getOlder, 0),
  4857     JS_PSG("script", DebuggerFrame_getScript, 0),
  4858     JS_PSG("this", DebuggerFrame_getThis, 0),
  4859     JS_PSG("type", DebuggerFrame_getType, 0),
  4860     JS_PSG("implementation", DebuggerFrame_getImplementation, 0),
  4861     JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
  4862     JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0),
  4863     JS_PS_END
  4864 };
  4866 static const JSFunctionSpec DebuggerFrame_methods[] = {
  4867     JS_FN("eval", DebuggerFrame_eval, 1, 0),
  4868     JS_FN("evalWithBindings", DebuggerFrame_evalWithBindings, 1, 0),
  4869     JS_FS_END
  4870 };
  4873 /*** Debugger.Object *****************************************************************************/
  4875 static void
  4876 DebuggerObject_trace(JSTracer *trc, JSObject *obj)
  4878     /*
  4879      * There is a barrier on private pointers, so the Unbarriered marking
  4880      * is okay.
  4881      */
  4882     if (JSObject *referent = (JSObject *) obj->getPrivate()) {
  4883         MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Object referent");
  4884         obj->setPrivateUnbarriered(referent);
  4888 const Class DebuggerObject_class = {
  4889     "Object",
  4890     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
  4891     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
  4892     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  4893     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr,
  4894     nullptr,              /* call        */
  4895     nullptr,              /* hasInstance */
  4896     nullptr,              /* construct   */
  4897     DebuggerObject_trace
  4898 };
  4900 static JSObject *
  4901 DebuggerObject_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
  4903     if (!args.thisv().isObject()) {
  4904         ReportObjectRequired(cx);
  4905         return nullptr;
  4907     JSObject *thisobj = &args.thisv().toObject();
  4908     if (thisobj->getClass() != &DebuggerObject_class) {
  4909         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  4910                              "Debugger.Object", fnname, thisobj->getClass()->name);
  4911         return nullptr;
  4914     /*
  4915      * Forbid Debugger.Object.prototype, which is of class DebuggerObject_class
  4916      * but isn't a real working Debugger.Object. The prototype object is
  4917      * distinguished by having no referent.
  4918      */
  4919     if (!thisobj->getPrivate()) {
  4920         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  4921                              "Debugger.Object", fnname, "prototype object");
  4922         return nullptr;
  4924     return thisobj;
  4927 #define THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj)            \
  4928     CallArgs args = CallArgsFromVp(argc, vp);                                 \
  4929     RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname));         \
  4930     if (!obj)                                                                 \
  4931         return false;                                                         \
  4932     obj = (JSObject *) obj->getPrivate();                                     \
  4933     JS_ASSERT(obj)
  4935 #define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj) \
  4936     CallArgs args = CallArgsFromVp(argc, vp);                                 \
  4937     RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname));         \
  4938     if (!obj)                                                                 \
  4939         return false;                                                         \
  4940     Debugger *dbg = Debugger::fromChildJSObject(obj);                         \
  4941     obj = (JSObject *) obj->getPrivate();                                     \
  4942     JS_ASSERT(obj)
  4944 static bool
  4945 DebuggerObject_construct(JSContext *cx, unsigned argc, Value *vp)
  4947     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
  4948                          "Debugger.Object");
  4949     return false;
  4952 static bool
  4953 DebuggerObject_getProto(JSContext *cx, unsigned argc, Value *vp)
  4955     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get proto", args, dbg, refobj);
  4956     RootedObject proto(cx);
  4958         AutoCompartment ac(cx, refobj);
  4959         if (!JSObject::getProto(cx, refobj, &proto))
  4960             return false;
  4962     RootedValue protov(cx, ObjectOrNullValue(proto));
  4963     if (!dbg->wrapDebuggeeValue(cx, &protov))
  4964         return false;
  4965     args.rval().set(protov);
  4966     return true;
  4969 static bool
  4970 DebuggerObject_getClass(JSContext *cx, unsigned argc, Value *vp)
  4972     THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get class", args, refobj);
  4973     const char *className;
  4975         AutoCompartment ac(cx, refobj);
  4976         className = JSObject::className(cx, refobj);
  4978     JSAtom *str = Atomize(cx, className, strlen(className));
  4979     if (!str)
  4980         return false;
  4981     args.rval().setString(str);
  4982     return true;
  4985 static bool
  4986 DebuggerObject_getCallable(JSContext *cx, unsigned argc, Value *vp)
  4988     THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get callable", args, refobj);
  4989     args.rval().setBoolean(refobj->isCallable());
  4990     return true;
  4993 static bool
  4994 DebuggerObject_getName(JSContext *cx, unsigned argc, Value *vp)
  4996     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get name", args, dbg, obj);
  4997     if (!obj->is<JSFunction>()) {
  4998         args.rval().setUndefined();
  4999         return true;
  5002     JSString *name = obj->as<JSFunction>().atom();
  5003     if (!name) {
  5004         args.rval().setUndefined();
  5005         return true;
  5008     RootedValue namev(cx, StringValue(name));
  5009     if (!dbg->wrapDebuggeeValue(cx, &namev))
  5010         return false;
  5011     args.rval().set(namev);
  5012     return true;
  5015 static bool
  5016 DebuggerObject_getDisplayName(JSContext *cx, unsigned argc, Value *vp)
  5018     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get display name", args, dbg, obj);
  5019     if (!obj->is<JSFunction>()) {
  5020         args.rval().setUndefined();
  5021         return true;
  5024     JSString *name = obj->as<JSFunction>().displayAtom();
  5025     if (!name) {
  5026         args.rval().setUndefined();
  5027         return true;
  5030     RootedValue namev(cx, StringValue(name));
  5031     if (!dbg->wrapDebuggeeValue(cx, &namev))
  5032         return false;
  5033     args.rval().set(namev);
  5034     return true;
  5037 static bool
  5038 DebuggerObject_getParameterNames(JSContext *cx, unsigned argc, Value *vp)
  5040     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get parameterNames", args, dbg, obj);
  5041     if (!obj->is<JSFunction>()) {
  5042         args.rval().setUndefined();
  5043         return true;
  5046     RootedFunction fun(cx, &obj->as<JSFunction>());
  5048     /* Only hand out parameter info for debuggee functions. */
  5049     if (!dbg->observesGlobal(&fun->global())) {
  5050         args.rval().setUndefined();
  5051         return true;
  5054     RootedObject result(cx, NewDenseAllocatedArray(cx, fun->nargs()));
  5055     if (!result)
  5056         return false;
  5057     result->ensureDenseInitializedLength(cx, 0, fun->nargs());
  5059     if (fun->isInterpreted()) {
  5060         RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
  5061         if (!script)
  5062             return false;
  5064         JS_ASSERT(fun->nargs() == script->bindings.numArgs());
  5066         if (fun->nargs() > 0) {
  5067             BindingVector bindings(cx);
  5068             if (!FillBindingVector(script, &bindings))
  5069                 return false;
  5070             for (size_t i = 0; i < fun->nargs(); i++) {
  5071                 Value v;
  5072                 if (bindings[i].name()->length() == 0)
  5073                     v = UndefinedValue();
  5074                 else
  5075                     v = StringValue(bindings[i].name());
  5076                 result->setDenseElement(i, v);
  5079     } else {
  5080         for (size_t i = 0; i < fun->nargs(); i++)
  5081             result->setDenseElement(i, UndefinedValue());
  5084     args.rval().setObject(*result);
  5085     return true;
  5088 static bool
  5089 DebuggerObject_getScript(JSContext *cx, unsigned argc, Value *vp)
  5091     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get script", args, dbg, obj);
  5093     if (!obj->is<JSFunction>()) {
  5094         args.rval().setUndefined();
  5095         return true;
  5098     RootedFunction fun(cx, &obj->as<JSFunction>());
  5099     if (fun->isBuiltin()) {
  5100         args.rval().setUndefined();
  5101         return true;
  5104     RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
  5105     if (!script)
  5106         return false;
  5108     /* Only hand out debuggee scripts. */
  5109     if (!dbg->observesScript(script)) {
  5110         args.rval().setNull();
  5111         return true;
  5114     RootedObject scriptObject(cx, dbg->wrapScript(cx, script));
  5115     if (!scriptObject)
  5116         return false;
  5118     args.rval().setObject(*scriptObject);
  5119     return true;
  5122 static bool
  5123 DebuggerObject_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
  5125     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj);
  5127     /* Don't bother switching compartments just to check obj's type and get its env. */
  5128     if (!obj->is<JSFunction>() || !obj->as<JSFunction>().isInterpreted()) {
  5129         args.rval().setUndefined();
  5130         return true;
  5133     /* Only hand out environments of debuggee functions. */
  5134     if (!dbg->observesGlobal(&obj->global())) {
  5135         args.rval().setNull();
  5136         return true;
  5139     Rooted<Env*> env(cx);
  5141         AutoCompartment ac(cx, obj);
  5142         RootedFunction fun(cx, &obj->as<JSFunction>());
  5143         env = GetDebugScopeForFunction(cx, fun);
  5144         if (!env)
  5145             return false;
  5148     return dbg->wrapEnvironment(cx, env, args.rval());
  5151 static bool
  5152 DebuggerObject_getGlobal(JSContext *cx, unsigned argc, Value *vp)
  5154     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get global", args, dbg, obj);
  5156     RootedValue v(cx, ObjectValue(obj->global()));
  5157     if (!dbg->wrapDebuggeeValue(cx, &v))
  5158         return false;
  5159     args.rval().set(v);
  5160     return true;
  5163 static bool
  5164 DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
  5166     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyDescriptor", args, dbg, obj);
  5168     RootedId id(cx);
  5169     if (!ValueToId<CanGC>(cx, args.get(0), &id))
  5170         return false;
  5172     /* Bug: This can cause the debuggee to run! */
  5173     Rooted<PropertyDescriptor> desc(cx);
  5175         Maybe<AutoCompartment> ac;
  5176         ac.construct(cx, obj);
  5177         if (!cx->compartment()->wrapId(cx, id.address()))
  5178             return false;
  5180         ErrorCopier ec(ac, dbg->toJSObject());
  5181         if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
  5182             return false;
  5185     if (desc.object()) {
  5186         /* Rewrap the debuggee values in desc for the debugger. */
  5187         if (!dbg->wrapDebuggeeValue(cx, desc.value()))
  5188             return false;
  5190         if (desc.hasGetterObject()) {
  5191             RootedValue get(cx, ObjectOrNullValue(desc.getterObject()));
  5192             if (!dbg->wrapDebuggeeValue(cx, &get))
  5193                 return false;
  5194             desc.setGetterObject(get.toObjectOrNull());
  5196         if (desc.hasSetterObject()) {
  5197             RootedValue set(cx, ObjectOrNullValue(desc.setterObject()));
  5198             if (!dbg->wrapDebuggeeValue(cx, &set))
  5199                 return false;
  5200             desc.setSetterObject(set.toObjectOrNull());
  5204     return NewPropertyDescriptorObject(cx, desc, args.rval());
  5207 static bool
  5208 DebuggerObject_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp)
  5210     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyNames", args, dbg, obj);
  5212     AutoIdVector keys(cx);
  5214         Maybe<AutoCompartment> ac;
  5215         ac.construct(cx, obj);
  5216         ErrorCopier ec(ac, dbg->toJSObject());
  5217         if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys))
  5218             return false;
  5221     AutoValueVector vals(cx);
  5222     if (!vals.resize(keys.length()))
  5223         return false;
  5225     for (size_t i = 0, len = keys.length(); i < len; i++) {
  5226          jsid id = keys[i];
  5227          if (JSID_IS_INT(id)) {
  5228              JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
  5229              if (!str)
  5230                  return false;
  5231              vals[i].setString(str);
  5232          } else if (JSID_IS_ATOM(id)) {
  5233              vals[i].setString(JSID_TO_STRING(id));
  5234              if (!cx->compartment()->wrap(cx, vals.handleAt(i)))
  5235                  return false;
  5236          } else {
  5237              vals[i].setObject(*JSID_TO_OBJECT(id));
  5238              if (!dbg->wrapDebuggeeValue(cx, vals.handleAt(i)))
  5239                  return false;
  5243     JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin());
  5244     if (!aobj)
  5245         return false;
  5246     args.rval().setObject(*aobj);
  5247     return true;
  5250 static bool
  5251 DebuggerObject_defineProperty(JSContext *cx, unsigned argc, Value *vp)
  5253     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperty", args, dbg, obj);
  5254     REQUIRE_ARGC("Debugger.Object.defineProperty", 2);
  5256     RootedId id(cx);
  5257     if (!ValueToId<CanGC>(cx, args[0], &id))
  5258         return false;
  5260     AutoPropDescArrayRooter descs(cx);
  5261     if (!descs.reserve(3)) // desc, unwrappedDesc, rewrappedDesc
  5262         return false;
  5263     PropDesc *desc = descs.append();
  5264     if (!desc || !desc->initialize(cx, args[1], false))
  5265         return false;
  5266     desc->clearPd();
  5268     PropDesc *unwrappedDesc = descs.append();
  5269     if (!unwrappedDesc || !desc->unwrapDebuggerObjectsInto(cx, dbg, obj, unwrappedDesc))
  5270         return false;
  5271     if (!unwrappedDesc->checkGetter(cx) || !unwrappedDesc->checkSetter(cx))
  5272         return false;
  5275         PropDesc *rewrappedDesc = descs.append();
  5276         if (!rewrappedDesc)
  5277             return false;
  5278         RootedId wrappedId(cx);
  5280         Maybe<AutoCompartment> ac;
  5281         ac.construct(cx, obj);
  5282         if (!unwrappedDesc->wrapInto(cx, obj, id, wrappedId.address(), rewrappedDesc))
  5283             return false;
  5285         ErrorCopier ec(ac, dbg->toJSObject());
  5286         bool dummy;
  5287         if (!DefineProperty(cx, obj, wrappedId, *rewrappedDesc, true, &dummy))
  5288             return false;
  5291     args.rval().setUndefined();
  5292     return true;
  5295 static bool
  5296 DebuggerObject_defineProperties(JSContext *cx, unsigned argc, Value *vp)
  5298     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperties", args, dbg, obj);
  5299     REQUIRE_ARGC("Debugger.Object.defineProperties", 1);
  5301     RootedValue arg(cx, args[0]);
  5302     RootedObject props(cx, ToObject(cx, arg));
  5303     if (!props)
  5304         return false;
  5306     AutoIdVector ids(cx);
  5307     AutoPropDescArrayRooter descs(cx);
  5308     if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs))
  5309         return false;
  5310     size_t n = ids.length();
  5312     AutoPropDescArrayRooter unwrappedDescs(cx);
  5313     for (size_t i = 0; i < n; i++) {
  5314         if (!unwrappedDescs.append())
  5315             return false;
  5316         if (!descs[i].unwrapDebuggerObjectsInto(cx, dbg, obj, &unwrappedDescs[i]))
  5317             return false;
  5318         if (!unwrappedDescs[i].checkGetter(cx) || !unwrappedDescs[i].checkSetter(cx))
  5319             return false;
  5323         AutoIdVector rewrappedIds(cx);
  5324         AutoPropDescArrayRooter rewrappedDescs(cx);
  5326         Maybe<AutoCompartment> ac;
  5327         ac.construct(cx, obj);
  5328         RootedId id(cx);
  5329         for (size_t i = 0; i < n; i++) {
  5330             if (!rewrappedIds.append(JSID_VOID) || !rewrappedDescs.append())
  5331                 return false;
  5332             id = ids[i];
  5333             if (!unwrappedDescs[i].wrapInto(cx, obj, id, &rewrappedIds[i], &rewrappedDescs[i]))
  5334                 return false;
  5337         ErrorCopier ec(ac, dbg->toJSObject());
  5338         for (size_t i = 0; i < n; i++) {
  5339             bool dummy;
  5340             if (!DefineProperty(cx, obj, rewrappedIds.handleAt(i),
  5341                                 rewrappedDescs[i], true, &dummy))
  5343                 return false;
  5348     args.rval().setUndefined();
  5349     return true;
  5352 /*
  5353  * This does a non-strict delete, as a matter of API design. The case where the
  5354  * property is non-configurable isn't necessarily exceptional here.
  5355  */
  5356 static bool
  5357 DebuggerObject_deleteProperty(JSContext *cx, unsigned argc, Value *vp)
  5359     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "deleteProperty", args, dbg, obj);
  5360     RootedValue nameArg(cx, args.get(0));
  5362     Maybe<AutoCompartment> ac;
  5363     ac.construct(cx, obj);
  5364     if (!cx->compartment()->wrap(cx, &nameArg))
  5365         return false;
  5367     bool succeeded;
  5368     ErrorCopier ec(ac, dbg->toJSObject());
  5369     if (!JSObject::deleteByValue(cx, obj, nameArg, &succeeded))
  5370         return false;
  5371     args.rval().setBoolean(succeeded);
  5372     return true;
  5375 enum SealHelperOp { Seal, Freeze, PreventExtensions };
  5377 static bool
  5378 DebuggerObject_sealHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op, const char *name)
  5380     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj);
  5382     Maybe<AutoCompartment> ac;
  5383     ac.construct(cx, obj);
  5384     ErrorCopier ec(ac, dbg->toJSObject());
  5385     bool ok;
  5386     if (op == Seal) {
  5387         ok = JSObject::seal(cx, obj);
  5388     } else if (op == Freeze) {
  5389         ok = JSObject::freeze(cx, obj);
  5390     } else {
  5391         JS_ASSERT(op == PreventExtensions);
  5392         bool extensible;
  5393         if (!JSObject::isExtensible(cx, obj, &extensible))
  5394             return false;
  5395         if (!extensible) {
  5396             args.rval().setUndefined();
  5397             return true;
  5399         ok = JSObject::preventExtensions(cx, obj);
  5401     if (!ok)
  5402         return false;
  5403     args.rval().setUndefined();
  5404     return true;
  5407 static bool
  5408 DebuggerObject_seal(JSContext *cx, unsigned argc, Value *vp)
  5410     return DebuggerObject_sealHelper(cx, argc, vp, Seal, "seal");
  5413 static bool
  5414 DebuggerObject_freeze(JSContext *cx, unsigned argc, Value *vp)
  5416     return DebuggerObject_sealHelper(cx, argc, vp, Freeze, "freeze");
  5419 static bool
  5420 DebuggerObject_preventExtensions(JSContext *cx, unsigned argc, Value *vp)
  5422     return DebuggerObject_sealHelper(cx, argc, vp, PreventExtensions, "preventExtensions");
  5425 static bool
  5426 DebuggerObject_isSealedHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op,
  5427                               const char *name)
  5429     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj);
  5431     Maybe<AutoCompartment> ac;
  5432     ac.construct(cx, obj);
  5433     ErrorCopier ec(ac, dbg->toJSObject());
  5434     bool r;
  5435     if (op == Seal) {
  5436         if (!JSObject::isSealed(cx, obj, &r))
  5437             return false;
  5438     } else if (op == Freeze) {
  5439         if (!JSObject::isFrozen(cx, obj, &r))
  5440             return false;
  5441     } else {
  5442         if (!JSObject::isExtensible(cx, obj, &r))
  5443             return false;
  5445     args.rval().setBoolean(r);
  5446     return true;
  5449 static bool
  5450 DebuggerObject_isSealed(JSContext *cx, unsigned argc, Value *vp)
  5452     return DebuggerObject_isSealedHelper(cx, argc, vp, Seal, "isSealed");
  5455 static bool
  5456 DebuggerObject_isFrozen(JSContext *cx, unsigned argc, Value *vp)
  5458     return DebuggerObject_isSealedHelper(cx, argc, vp, Freeze, "isFrozen");
  5461 static bool
  5462 DebuggerObject_isExtensible(JSContext *cx, unsigned argc, Value *vp)
  5464     return DebuggerObject_isSealedHelper(cx, argc, vp, PreventExtensions, "isExtensible");
  5467 enum ApplyOrCallMode { ApplyMode, CallMode };
  5469 static bool
  5470 ApplyOrCall(JSContext *cx, unsigned argc, Value *vp, ApplyOrCallMode mode)
  5472     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "apply", args, dbg, obj);
  5474     /*
  5475      * Any JS exceptions thrown must be in the debugger compartment, so do
  5476      * sanity checks and fallible conversions before entering the debuggee.
  5477      */
  5478     RootedValue calleev(cx, ObjectValue(*obj));
  5479     if (!obj->isCallable()) {
  5480         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  5481                              "Debugger.Object", "apply", obj->getClass()->name);
  5482         return false;
  5485     /*
  5486      * Unwrap Debugger.Objects. This happens in the debugger's compartment since
  5487      * that is where any exceptions must be reported.
  5488      */
  5489     RootedValue thisv(cx, args.get(0));
  5490     if (!dbg->unwrapDebuggeeValue(cx, &thisv))
  5491         return false;
  5492     unsigned callArgc = 0;
  5493     Value *callArgv = nullptr;
  5494     AutoValueVector argv(cx);
  5495     if (mode == ApplyMode) {
  5496         if (args.length() >= 2 && !args[1].isNullOrUndefined()) {
  5497             if (!args[1].isObject()) {
  5498                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_APPLY_ARGS,
  5499                                      js_apply_str);
  5500                 return false;
  5502             RootedObject argsobj(cx, &args[1].toObject());
  5503             if (!GetLengthProperty(cx, argsobj, &callArgc))
  5504                 return false;
  5505             callArgc = unsigned(Min(callArgc, ARGS_LENGTH_MAX));
  5506             if (!argv.growBy(callArgc) || !GetElements(cx, argsobj, callArgc, argv.begin()))
  5507                 return false;
  5508             callArgv = argv.begin();
  5510     } else {
  5511         callArgc = args.length() > 0 ? unsigned(Min(args.length() - 1, ARGS_LENGTH_MAX)) : 0;
  5512         callArgv = args.array() + 1;
  5515     AutoArrayRooter callArgvRooter(cx, callArgc, callArgv);
  5516     for (unsigned i = 0; i < callArgc; i++) {
  5517         if (!dbg->unwrapDebuggeeValue(cx, callArgvRooter.handleAt(i)))
  5518             return false;
  5521     /*
  5522      * Enter the debuggee compartment and rewrap all input value for that compartment.
  5523      * (Rewrapping always takes place in the destination compartment.)
  5524      */
  5525     Maybe<AutoCompartment> ac;
  5526     ac.construct(cx, obj);
  5527     if (!cx->compartment()->wrap(cx, &calleev) || !cx->compartment()->wrap(cx, &thisv))
  5528         return false;
  5530     RootedValue arg(cx);
  5531     for (unsigned i = 0; i < callArgc; i++) {
  5532         if (!cx->compartment()->wrap(cx, callArgvRooter.handleAt(i)))
  5533              return false;
  5536     /*
  5537      * Call the function. Use receiveCompletionValue to return to the debugger
  5538      * compartment and populate args.rval().
  5539      */
  5540     RootedValue rval(cx);
  5541     bool ok = Invoke(cx, thisv, calleev, callArgc, callArgv, &rval);
  5542     return dbg->receiveCompletionValue(ac, ok, rval, args.rval());
  5545 static bool
  5546 DebuggerObject_apply(JSContext *cx, unsigned argc, Value *vp)
  5548     return ApplyOrCall(cx, argc, vp, ApplyMode);
  5551 static bool
  5552 DebuggerObject_call(JSContext *cx, unsigned argc, Value *vp)
  5554     return ApplyOrCall(cx, argc, vp, CallMode);
  5557 static bool
  5558 DebuggerObject_makeDebuggeeValue(JSContext *cx, unsigned argc, Value *vp)
  5560     REQUIRE_ARGC("Debugger.Object.prototype.makeDebuggeeValue", 1);
  5561     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "makeDebuggeeValue", args, dbg, referent);
  5563     RootedValue arg0(cx, args[0]);
  5565     /* Non-objects are already debuggee values. */
  5566     if (arg0.isObject()) {
  5567         // Enter this Debugger.Object's referent's compartment, and wrap the
  5568         // argument as appropriate for references from there.
  5570             AutoCompartment ac(cx, referent);
  5571             if (!cx->compartment()->wrap(cx, &arg0))
  5572                 return false;
  5575         // Back in the debugger's compartment, produce a new Debugger.Object
  5576         // instance referring to the wrapped argument.
  5577         if (!dbg->wrapDebuggeeValue(cx, &arg0))
  5578             return false;
  5581     args.rval().set(arg0);
  5582     return true;
  5585 static bool
  5586 RequireGlobalObject(JSContext *cx, HandleValue dbgobj, HandleObject referent)
  5588     RootedObject obj(cx, referent);
  5590     if (!obj->is<GlobalObject>()) {
  5591         const char *isWrapper = "";
  5592         const char *isWindowProxy = "";
  5594         /* Help the poor programmer by pointing out wrappers around globals... */
  5595         if (obj->is<WrapperObject>()) {
  5596             obj = js::UncheckedUnwrap(obj);
  5597             isWrapper = "a wrapper around ";
  5600         /* ... and WindowProxies around Windows. */
  5601         if (IsOuterObject(obj)) {
  5602             obj = JS_ObjectToInnerObject(cx, obj);
  5603             isWindowProxy = "a WindowProxy referring to ";
  5606         if (obj->is<GlobalObject>()) {
  5607             js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_WRAPPER_IN_WAY,
  5608                                      JSDVG_SEARCH_STACK, dbgobj, js::NullPtr(),
  5609                                      isWrapper, isWindowProxy);
  5610         } else {
  5611             js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
  5612                                      JSDVG_SEARCH_STACK, dbgobj, js::NullPtr(),
  5613                                      "a global object", nullptr);
  5615         return false;
  5618     return true;
  5621 static bool
  5622 DebuggerObject_evalInGlobal(JSContext *cx, unsigned argc, Value *vp)
  5624     REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobal", 1);
  5625     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobal", args, dbg, referent);
  5626     if (!RequireGlobalObject(cx, args.thisv(), referent))
  5627         return false;
  5629     return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobal",
  5630                                args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
  5631                                args.get(1), args.rval(), dbg, referent, nullptr);
  5634 static bool
  5635 DebuggerObject_evalInGlobalWithBindings(JSContext *cx, unsigned argc, Value *vp)
  5637     REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobalWithBindings", 2);
  5638     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobalWithBindings", args, dbg, referent);
  5639     if (!RequireGlobalObject(cx, args.thisv(), referent))
  5640         return false;
  5642     return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobalWithBindings",
  5643                                args[0], EvalHasExtraBindings, args[1], args.get(2),
  5644                                args.rval(), dbg, referent, nullptr);
  5647 static bool
  5648 DebuggerObject_unwrap(JSContext *cx, unsigned argc, Value *vp)
  5650     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "unwrap", args, dbg, referent);
  5651     JSObject *unwrapped = UnwrapOneChecked(referent);
  5652     if (!unwrapped) {
  5653         args.rval().setNull();
  5654         return true;
  5657     args.rval().setObject(*unwrapped);
  5658     if (!dbg->wrapDebuggeeValue(cx, args.rval()))
  5659         return false;
  5660     return true;
  5663 static bool
  5664 DebuggerObject_unsafeDereference(JSContext *cx, unsigned argc, Value *vp)
  5666     THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "unsafeDereference", args, referent);
  5667     args.rval().setObject(*referent);
  5668     if (!cx->compartment()->wrap(cx, args.rval()))
  5669         return false;
  5671     // Wrapping should outerize inner objects.
  5672     JS_ASSERT(!IsInnerObject(&args.rval().toObject()));
  5674     return true;
  5677 static const JSPropertySpec DebuggerObject_properties[] = {
  5678     JS_PSG("proto", DebuggerObject_getProto, 0),
  5679     JS_PSG("class", DebuggerObject_getClass, 0),
  5680     JS_PSG("callable", DebuggerObject_getCallable, 0),
  5681     JS_PSG("name", DebuggerObject_getName, 0),
  5682     JS_PSG("displayName", DebuggerObject_getDisplayName, 0),
  5683     JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0),
  5684     JS_PSG("script", DebuggerObject_getScript, 0),
  5685     JS_PSG("environment", DebuggerObject_getEnvironment, 0),
  5686     JS_PSG("global", DebuggerObject_getGlobal, 0),
  5687     JS_PS_END
  5688 };
  5690 static const JSFunctionSpec DebuggerObject_methods[] = {
  5691     JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0),
  5692     JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0),
  5693     JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0),
  5694     JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0),
  5695     JS_FN("deleteProperty", DebuggerObject_deleteProperty, 1, 0),
  5696     JS_FN("seal", DebuggerObject_seal, 0, 0),
  5697     JS_FN("freeze", DebuggerObject_freeze, 0, 0),
  5698     JS_FN("preventExtensions", DebuggerObject_preventExtensions, 0, 0),
  5699     JS_FN("isSealed", DebuggerObject_isSealed, 0, 0),
  5700     JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0),
  5701     JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0),
  5702     JS_FN("apply", DebuggerObject_apply, 0, 0),
  5703     JS_FN("call", DebuggerObject_call, 0, 0),
  5704     JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0),
  5705     JS_FN("evalInGlobal", DebuggerObject_evalInGlobal, 1, 0),
  5706     JS_FN("evalInGlobalWithBindings", DebuggerObject_evalInGlobalWithBindings, 2, 0),
  5707     JS_FN("unwrap", DebuggerObject_unwrap, 0, 0),
  5708     JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0),
  5709     JS_FS_END
  5710 };
  5713 /*** Debugger.Environment ************************************************************************/
  5715 static void
  5716 DebuggerEnv_trace(JSTracer *trc, JSObject *obj)
  5718     /*
  5719      * There is a barrier on private pointers, so the Unbarriered marking
  5720      * is okay.
  5721      */
  5722     if (Env *referent = (JSObject *) obj->getPrivate()) {
  5723         MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Environment referent");
  5724         obj->setPrivateUnbarriered(referent);
  5728 const Class DebuggerEnv_class = {
  5729     "Environment",
  5730     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
  5731     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT),
  5732     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
  5733     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr,
  5734     nullptr,              /* call        */
  5735     nullptr,              /* hasInstance */
  5736     nullptr,              /* construct   */
  5737     DebuggerEnv_trace
  5738 };
  5740 static JSObject *
  5741 DebuggerEnv_checkThis(JSContext *cx, const CallArgs &args, const char *fnname,
  5742                       bool requireDebuggee = true)
  5744     if (!args.thisv().isObject()) {
  5745         ReportObjectRequired(cx);
  5746         return nullptr;
  5748     JSObject *thisobj = &args.thisv().toObject();
  5749     if (thisobj->getClass() != &DebuggerEnv_class) {
  5750         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  5751                              "Debugger.Environment", fnname, thisobj->getClass()->name);
  5752         return nullptr;
  5755     /*
  5756      * Forbid Debugger.Environment.prototype, which is of class DebuggerEnv_class
  5757      * but isn't a real working Debugger.Environment. The prototype object is
  5758      * distinguished by having no referent.
  5759      */
  5760     if (!thisobj->getPrivate()) {
  5761         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
  5762                              "Debugger.Environment", fnname, "prototype object");
  5763         return nullptr;
  5766     /*
  5767      * Forbid access to Debugger.Environment objects that are not debuggee
  5768      * environments.
  5769      */
  5770     if (requireDebuggee) {
  5771         Rooted<Env*> env(cx, static_cast<Env *>(thisobj->getPrivate()));
  5772         if (!Debugger::fromChildJSObject(thisobj)->observesGlobal(&env->global())) {
  5773             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGEE,
  5774                                  "Debugger.Environment", "environment");
  5775             return nullptr;
  5779     return thisobj;
  5782 #define THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env)                \
  5783     CallArgs args = CallArgsFromVp(argc, vp);                                 \
  5784     JSObject *envobj = DebuggerEnv_checkThis(cx, args, fnname);               \
  5785     if (!envobj)                                                              \
  5786         return false;                                                         \
  5787     Rooted<Env*> env(cx, static_cast<Env *>(envobj->getPrivate()));           \
  5788     JS_ASSERT(env);                                                           \
  5789     JS_ASSERT(!env->is<ScopeObject>())
  5791 #define THIS_DEBUGENV_OWNER(cx, argc, vp, fnname, args, envobj, env, dbg)     \
  5792     THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env);                   \
  5793     Debugger *dbg = Debugger::fromChildJSObject(envobj)
  5795 static bool
  5796 DebuggerEnv_construct(JSContext *cx, unsigned argc, Value *vp)
  5798     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
  5799                          "Debugger.Environment");
  5800     return false;
  5803 static bool
  5804 IsDeclarative(Env *env)
  5806     return env->is<DebugScopeObject>() && env->as<DebugScopeObject>().isForDeclarative();
  5809 static bool
  5810 IsWith(Env *env)
  5812     return env->is<DebugScopeObject>() &&
  5813            env->as<DebugScopeObject>().scope().is<DynamicWithObject>();
  5816 static bool
  5817 DebuggerEnv_getType(JSContext *cx, unsigned argc, Value *vp)
  5819     THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env);
  5821     /* Don't bother switching compartments just to check env's class. */
  5822     const char *s;
  5823     if (IsDeclarative(env))
  5824         s = "declarative";
  5825     else if (IsWith(env))
  5826         s = "with";
  5827     else
  5828         s = "object";
  5830     JSAtom *str = Atomize(cx, s, strlen(s), InternAtom);
  5831     if (!str)
  5832         return false;
  5833     args.rval().setString(str);
  5834     return true;
  5837 static bool
  5838 DebuggerEnv_getParent(JSContext *cx, unsigned argc, Value *vp)
  5840     THIS_DEBUGENV_OWNER(cx, argc, vp, "get parent", args, envobj, env, dbg);
  5842     /* Don't bother switching compartments just to get env's parent. */
  5843     Rooted<Env*> parent(cx, env->enclosingScope());
  5844     return dbg->wrapEnvironment(cx, parent, args.rval());
  5847 static bool
  5848 DebuggerEnv_getObject(JSContext *cx, unsigned argc, Value *vp)
  5850     THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
  5852     /*
  5853      * Don't bother switching compartments just to check env's class and
  5854      * possibly get its proto.
  5855      */
  5856     if (IsDeclarative(env)) {
  5857         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NO_SCOPE_OBJECT);
  5858         return false;
  5861     JSObject *obj;
  5862     if (IsWith(env)) {
  5863         obj = &env->as<DebugScopeObject>().scope().as<DynamicWithObject>().object();
  5864     } else {
  5865         obj = env;
  5866         JS_ASSERT(!obj->is<DebugScopeObject>());
  5869     args.rval().setObject(*obj);
  5870     if (!dbg->wrapDebuggeeValue(cx, args.rval()))
  5871         return false;
  5872     return true;
  5875 static bool
  5876 DebuggerEnv_getCallee(JSContext *cx, unsigned argc, Value *vp)
  5878     THIS_DEBUGENV_OWNER(cx, argc, vp, "get callee", args, envobj, env, dbg);
  5880     args.rval().setNull();
  5882     if (!env->is<DebugScopeObject>())
  5883         return true;
  5885     JSObject &scope = env->as<DebugScopeObject>().scope();
  5886     if (!scope.is<CallObject>())
  5887         return true;
  5889     CallObject &callobj = scope.as<CallObject>();
  5890     if (callobj.isForEval())
  5891         return true;
  5893     args.rval().setObject(callobj.callee());
  5894     if (!dbg->wrapDebuggeeValue(cx, args.rval()))
  5895         return false;
  5896     return true;
  5899 static bool
  5900 DebuggerEnv_getInspectable(JSContext *cx, unsigned argc, Value *vp)
  5902     CallArgs args = CallArgsFromVp(argc, vp);
  5903     JSObject *envobj = DebuggerEnv_checkThis(cx, args, "get inspectable", false);
  5904     if (!envobj)
  5905         return false;
  5906     Rooted<Env*> env(cx, static_cast<Env *>(envobj->getPrivate()));
  5907     JS_ASSERT(env);
  5908     JS_ASSERT(!env->is<ScopeObject>());
  5910     Debugger *dbg = Debugger::fromChildJSObject(envobj);
  5912     args.rval().setBoolean(dbg->observesGlobal(&env->global()));
  5913     return true;
  5916 static bool
  5917 DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp)
  5919     THIS_DEBUGENV_OWNER(cx, argc, vp, "names", args, envobj, env, dbg);
  5921     AutoIdVector keys(cx);
  5923         Maybe<AutoCompartment> ac;
  5924         ac.construct(cx, env);
  5925         ErrorCopier ec(ac, dbg->toJSObject());
  5926         if (!GetPropertyNames(cx, env, JSITER_HIDDEN, &keys))
  5927             return false;
  5930     RootedObject arr(cx, NewDenseEmptyArray(cx));
  5931     if (!arr)
  5932         return false;
  5933     RootedId id(cx);
  5934     for (size_t i = 0, len = keys.length(); i < len; i++) {
  5935         id = keys[i];
  5936         if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) {
  5937             if (!cx->compartment()->wrapId(cx, id.address()))
  5938                 return false;
  5939             if (!NewbornArrayPush(cx, arr, StringValue(JSID_TO_STRING(id))))
  5940                 return false;
  5943     args.rval().setObject(*arr);
  5944     return true;
  5947 static bool
  5948 DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp)
  5950     REQUIRE_ARGC("Debugger.Environment.find", 1);
  5951     THIS_DEBUGENV_OWNER(cx, argc, vp, "find", args, envobj, env, dbg);
  5953     RootedId id(cx);
  5954     if (!ValueToIdentifier(cx, args[0], &id))
  5955         return false;
  5958         Maybe<AutoCompartment> ac;
  5959         ac.construct(cx, env);
  5960         if (!cx->compartment()->wrapId(cx, id.address()))
  5961             return false;
  5963         /* This can trigger resolve hooks. */
  5964         ErrorCopier ec(ac, dbg->toJSObject());
  5965         RootedShape prop(cx);
  5966         RootedObject pobj(cx);
  5967         for (; env && !prop; env = env->enclosingScope()) {
  5968             if (!JSObject::lookupGeneric(cx, env, id, &pobj, &prop))
  5969                 return false;
  5970             if (prop)
  5971                 break;
  5975     return dbg->wrapEnvironment(cx, env, args.rval());
  5978 static bool
  5979 DebuggerEnv_getVariable(JSContext *cx, unsigned argc, Value *vp)
  5981     REQUIRE_ARGC("Debugger.Environment.getVariable", 1);
  5982     THIS_DEBUGENV_OWNER(cx, argc, vp, "getVariable", args, envobj, env, dbg);
  5984     RootedId id(cx);
  5985     if (!ValueToIdentifier(cx, args[0], &id))
  5986         return false;
  5988     RootedValue v(cx);
  5990         Maybe<AutoCompartment> ac;
  5991         ac.construct(cx, env);
  5992         if (!cx->compartment()->wrapId(cx, id.address()))
  5993             return false;
  5995         /* This can trigger getters. */
  5996         ErrorCopier ec(ac, dbg->toJSObject());
  5998         // For DebugScopeObjects, we get sentinel values for optimized out
  5999         // slots and arguments instead of throwing (the default behavior).
  6000         //
  6001         // See wrapDebuggeeValue for how the sentinel values are wrapped.
  6002         if (env->is<DebugScopeObject>()) {
  6003             if (!env->as<DebugScopeObject>().getMaybeSentinelValue(cx, id, &v))
  6004                 return false;
  6005         } else {
  6006             if (!JSObject::getGeneric(cx, env, env, id, &v))
  6007                 return false;
  6011     if (!dbg->wrapDebuggeeValue(cx, &v))
  6012         return false;
  6013     args.rval().set(v);
  6014     return true;
  6017 static bool
  6018 DebuggerEnv_setVariable(JSContext *cx, unsigned argc, Value *vp)
  6020     REQUIRE_ARGC("Debugger.Environment.setVariable", 2);
  6021     THIS_DEBUGENV_OWNER(cx, argc, vp, "setVariable", args, envobj, env, dbg);
  6023     RootedId id(cx);
  6024     if (!ValueToIdentifier(cx, args[0], &id))
  6025         return false;
  6027     RootedValue v(cx, args[1]);
  6028     if (!dbg->unwrapDebuggeeValue(cx, &v))
  6029         return false;
  6032         Maybe<AutoCompartment> ac;
  6033         ac.construct(cx, env);
  6034         if (!cx->compartment()->wrapId(cx, id.address()) || !cx->compartment()->wrap(cx, &v))
  6035             return false;
  6037         /* This can trigger setters. */
  6038         ErrorCopier ec(ac, dbg->toJSObject());
  6040         /* Make sure the environment actually has the specified binding. */
  6041         bool has;
  6042         if (!JSObject::hasProperty(cx, env, id, &has))
  6043             return false;
  6044         if (!has) {
  6045             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_VARIABLE_NOT_FOUND);
  6046             return false;
  6049         /* Just set the property. */
  6050         if (!JSObject::setGeneric(cx, env, env, id, &v, true))
  6051             return false;
  6054     args.rval().setUndefined();
  6055     return true;
  6058 static const JSPropertySpec DebuggerEnv_properties[] = {
  6059     JS_PSG("type", DebuggerEnv_getType, 0),
  6060     JS_PSG("object", DebuggerEnv_getObject, 0),
  6061     JS_PSG("parent", DebuggerEnv_getParent, 0),
  6062     JS_PSG("callee", DebuggerEnv_getCallee, 0),
  6063     JS_PSG("inspectable", DebuggerEnv_getInspectable, 0),
  6064     JS_PS_END
  6065 };
  6067 static const JSFunctionSpec DebuggerEnv_methods[] = {
  6068     JS_FN("names", DebuggerEnv_names, 0, 0),
  6069     JS_FN("find", DebuggerEnv_find, 1, 0),
  6070     JS_FN("getVariable", DebuggerEnv_getVariable, 1, 0),
  6071     JS_FN("setVariable", DebuggerEnv_setVariable, 2, 0),
  6072     JS_FS_END
  6073 };
  6077 /*** Glue ****************************************************************************************/
  6079 extern JS_PUBLIC_API(bool)
  6080 JS_DefineDebuggerObject(JSContext *cx, HandleObject obj)
  6082     RootedObject
  6083         objProto(cx),
  6084         debugCtor(cx),
  6085         debugProto(cx),
  6086         frameProto(cx),
  6087         scriptProto(cx),
  6088         sourceProto(cx),
  6089         objectProto(cx),
  6090         envProto(cx),
  6091         memoryProto(cx);
  6092     objProto = obj->as<GlobalObject>().getOrCreateObjectPrototype(cx);
  6093     if (!objProto)
  6094         return false;
  6095     debugProto = js_InitClass(cx, obj,
  6096                               objProto, &Debugger::jsclass, Debugger::construct,
  6097                               1, Debugger::properties, Debugger::methods, nullptr, nullptr,
  6098                               debugCtor.address());
  6099     if (!debugProto)
  6100         return false;
  6102     frameProto = js_InitClass(cx, debugCtor, objProto, &DebuggerFrame_class,
  6103                               DebuggerFrame_construct, 0,
  6104                               DebuggerFrame_properties, DebuggerFrame_methods,
  6105                               nullptr, nullptr);
  6106     if (!frameProto)
  6107         return false;
  6109     scriptProto = js_InitClass(cx, debugCtor, objProto, &DebuggerScript_class,
  6110                                DebuggerScript_construct, 0,
  6111                                DebuggerScript_properties, DebuggerScript_methods,
  6112                                nullptr, nullptr);
  6113     if (!scriptProto)
  6114         return false;
  6116     sourceProto = js_InitClass(cx, debugCtor, sourceProto, &DebuggerSource_class,
  6117                                DebuggerSource_construct, 0,
  6118                                DebuggerSource_properties, DebuggerSource_methods,
  6119                                nullptr, nullptr);
  6120     if (!sourceProto)
  6121         return false;
  6123     objectProto = js_InitClass(cx, debugCtor, objProto, &DebuggerObject_class,
  6124                                DebuggerObject_construct, 0,
  6125                                DebuggerObject_properties, DebuggerObject_methods,
  6126                                nullptr, nullptr);
  6127     if (!objectProto)
  6128         return false;
  6129     envProto = js_InitClass(cx, debugCtor, objProto, &DebuggerEnv_class,
  6130                             DebuggerEnv_construct, 0,
  6131                             DebuggerEnv_properties, DebuggerEnv_methods,
  6132                             nullptr, nullptr);
  6133     if (!envProto)
  6134         return false;
  6135     memoryProto = js_InitClass(cx, debugCtor, objProto, &DebuggerMemory::class_,
  6136                                DebuggerMemory::construct, 0, DebuggerMemory::properties,
  6137                                DebuggerMemory::methods, nullptr, nullptr);
  6138     if (!memoryProto)
  6139         return false;
  6141     debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
  6142     debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
  6143     debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto));
  6144     debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO, ObjectValue(*sourceProto));
  6145     debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto));
  6146     debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO, ObjectValue(*memoryProto));
  6147     return true;

mercurial