js/src/vm/Debugger.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial