Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | } |