1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=99 ft=cpp:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is SpiderMonkey Debugger object.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Mozilla Foundation.
21 : * Portions created by the Initial Developer are Copyright (C) 1998-1999
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributors:
25 : * Jim Blandy <jimb@mozilla.com>
26 : * Jason Orendorff <jorendorff@mozilla.com>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either of the GNU General Public License Version 2 or later (the "GPL"),
30 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 : #include "vm/Debugger.h"
43 : #include "jsapi.h"
44 : #include "jscntxt.h"
45 : #include "jsgcmark.h"
46 : #include "jsnum.h"
47 : #include "jsobj.h"
48 : #include "jswrapper.h"
49 : #include "jsarrayinlines.h"
50 : #include "jsgcinlines.h"
51 : #include "jsinterpinlines.h"
52 : #include "jsobjinlines.h"
53 : #include "jsopcodeinlines.h"
54 :
55 : #include "frontend/BytecodeCompiler.h"
56 : #include "frontend/BytecodeEmitter.h"
57 : #include "methodjit/Retcon.h"
58 : #include "js/Vector.h"
59 :
60 : #include "vm/Stack-inl.h"
61 :
62 : using namespace js;
63 :
64 :
65 : /*** Forward declarations ************************************************************************/
66 :
67 : extern Class DebuggerFrame_class;
68 :
69 : enum {
70 : JSSLOT_DEBUGFRAME_OWNER,
71 : JSSLOT_DEBUGFRAME_ARGUMENTS,
72 : JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
73 : JSSLOT_DEBUGFRAME_ONPOP_HANDLER,
74 : JSSLOT_DEBUGFRAME_COUNT
75 : };
76 :
77 : extern Class DebuggerArguments_class;
78 :
79 : enum {
80 : JSSLOT_DEBUGARGUMENTS_FRAME,
81 : JSSLOT_DEBUGARGUMENTS_COUNT
82 : };
83 :
84 : extern Class DebuggerEnv_class;
85 :
86 : enum {
87 : JSSLOT_DEBUGENV_OWNER,
88 : JSSLOT_DEBUGENV_COUNT
89 : };
90 :
91 : extern Class DebuggerObject_class;
92 :
93 : enum {
94 : JSSLOT_DEBUGOBJECT_OWNER,
95 : JSSLOT_DEBUGOBJECT_COUNT
96 : };
97 :
98 : extern Class DebuggerScript_class;
99 :
100 : enum {
101 : JSSLOT_DEBUGSCRIPT_OWNER,
102 : JSSLOT_DEBUGSCRIPT_COUNT
103 : };
104 :
105 :
106 : /*** Utils ***************************************************************************************/
107 :
108 : bool
109 54 : ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required)
110 : {
111 54 : JS_ASSERT(required > 0);
112 54 : JS_ASSERT(required <= 10);
113 : char s[2];
114 54 : s[0] = '0' + (required - 1);
115 54 : s[1] = '\0';
116 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
117 54 : name, s, required == 1 ? "" : "s");
118 54 : return false;
119 : }
120 :
121 : #define REQUIRE_ARGC(name, n) \
122 : JS_BEGIN_MACRO \
123 : if (argc < (n)) \
124 : return ReportMoreArgsNeeded(cx, name, n); \
125 : JS_END_MACRO
126 :
127 : bool
128 81 : ReportObjectRequired(JSContext *cx)
129 : {
130 81 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT);
131 81 : return false;
132 : }
133 :
134 : bool
135 819 : ValueToIdentifier(JSContext *cx, const Value &v, jsid *idp)
136 : {
137 : jsid id;
138 819 : if (!ValueToId(cx, v, &id))
139 0 : return false;
140 819 : if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) {
141 : js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
142 108 : JSDVG_SEARCH_STACK, v, NULL, "not an identifier", NULL);
143 108 : return false;
144 : }
145 711 : *idp = id;
146 711 : return true;
147 : }
148 :
149 : /*
150 : * A range of all the Debugger.Frame objects for a particular StackFrame.
151 : *
152 : * FIXME This checks only current debuggers, so it relies on a hack in
153 : * Debugger::removeDebuggeeGlobal to make sure only current debuggers have Frame
154 : * objects with .live === true.
155 : */
156 : class Debugger::FrameRange {
157 : JSContext *cx;
158 : StackFrame *fp;
159 :
160 : /* The debuggers in |fp|'s compartment, or NULL if there are none. */
161 : GlobalObject::DebuggerVector *debuggers;
162 :
163 : /*
164 : * The index of the front Debugger.Frame's debugger in debuggers.
165 : * nextDebugger < debuggerCount if and only if the range is not empty.
166 : */
167 : size_t debuggerCount, nextDebugger;
168 :
169 : /*
170 : * If the range is not empty, this is front Debugger.Frame's entry in its
171 : * debugger's frame table.
172 : */
173 : FrameMap::Ptr entry;
174 :
175 : public:
176 : /*
177 : * Return a range containing all Debugger.Frame instances referring to |fp|.
178 : * |global| is |fp|'s global object; if NULL or omitted, we compute it
179 : * ourselves from |fp|.
180 : *
181 : * We keep an index into the compartment's debugger list, and a
182 : * FrameMap::Ptr into the current debugger's frame map. Thus, if the set of
183 : * debuggers in |fp|'s compartment changes, this range becomes invalid.
184 : * Similarly, if stack frames are added to or removed from frontDebugger(),
185 : * then the range's front is invalid until popFront is called.
186 : */
187 62549 : FrameRange(JSContext *cx, StackFrame *fp, GlobalObject *global = NULL)
188 62549 : : cx(cx), fp(fp) {
189 62549 : nextDebugger = 0;
190 :
191 : /* Find our global, if we were not given one. */
192 62549 : if (!global)
193 5813 : global = &fp->scopeChain().global();
194 :
195 : /* The frame and global must match. */
196 62549 : JS_ASSERT(&fp->scopeChain().global() == global);
197 :
198 : /* Find the list of debuggers we'll iterate over. There may be none. */
199 62549 : debuggers = global->getDebuggers();
200 62549 : if (debuggers) {
201 61916 : debuggerCount = debuggers->length();
202 61916 : findNext();
203 : } else {
204 633 : debuggerCount = 0;
205 : }
206 62549 : }
207 :
208 307502 : bool empty() const {
209 307502 : return nextDebugger >= debuggerCount;
210 : }
211 :
212 34299 : JSObject *frontFrame() const {
213 34299 : JS_ASSERT(!empty());
214 34299 : return entry->value;
215 : }
216 :
217 14256 : Debugger *frontDebugger() const {
218 14256 : JS_ASSERT(!empty());
219 14256 : return (*debuggers)[nextDebugger];
220 : }
221 :
222 : /*
223 : * Delete the front frame from its Debugger's frame map. After this call,
224 : * the range's front is invalid until popFront is called.
225 : */
226 : void removeFrontFrame() const {
227 : JS_ASSERT(!empty());
228 : frontDebugger()->frames.remove(entry);
229 : }
230 :
231 34299 : void popFront() {
232 34299 : JS_ASSERT(!empty());
233 34299 : nextDebugger++;
234 34299 : findNext();
235 34299 : }
236 :
237 : private:
238 : /*
239 : * Either make this range refer to the first appropriate Debugger.Frame at
240 : * or after nextDebugger, or make it empty.
241 : */
242 96215 : void findNext() {
243 224015 : while (!empty()) {
244 65884 : Debugger *dbg = (*debuggers)[nextDebugger];
245 65884 : entry = dbg->frames.lookup(fp);
246 65884 : if (entry)
247 34299 : break;
248 31585 : nextDebugger++;
249 : }
250 96215 : }
251 : };
252 :
253 :
254 : /*** Breakpoints *********************************************************************************/
255 :
256 1172 : BreakpointSite::BreakpointSite(JSScript *script, jsbytecode *pc)
257 : : script(script), pc(pc), scriptGlobal(NULL), enabledCount(0),
258 1172 : trapHandler(NULL), trapClosure(UndefinedValue())
259 : {
260 1172 : JS_ASSERT(!script->hasBreakpointsAt(pc));
261 1172 : JS_INIT_CLIST(&breakpoints);
262 1172 : }
263 :
264 : /*
265 : * Precondition: script is live, meaning either it is a non-held script that is
266 : * on the stack or a held script that hasn't been GC'd.
267 : */
268 : static GlobalObject *
269 1296 : ScriptGlobal(JSContext *cx, JSScript *script, GlobalObject *scriptGlobal)
270 : {
271 1296 : if (scriptGlobal)
272 603 : return scriptGlobal;
273 :
274 : /*
275 : * The referent is a non-held script. There is no direct reference from
276 : * script to the scope, so find it on the stack.
277 : */
278 2115 : for (AllFramesIter i(cx->stack.space()); ; ++i) {
279 2115 : JS_ASSERT(!i.done());
280 2115 : if (i.fp()->maybeScript() == script)
281 693 : return &i.fp()->scopeChain().global();
282 : }
283 : JS_NOT_REACHED("ScriptGlobal: live non-held script not on stack");
284 : }
285 :
286 : void
287 2344 : BreakpointSite::recompile(FreeOp *fop)
288 : {
289 : #ifdef JS_METHODJIT
290 2344 : if (script->hasJITCode()) {
291 331 : mjit::Recompiler::clearStackReferences(fop, script);
292 331 : mjit::ReleaseScriptCode(fop, script);
293 : }
294 : #endif
295 2344 : }
296 :
297 : void
298 1278 : BreakpointSite::inc(FreeOp *fop)
299 : {
300 1278 : if (enabledCount == 0 && !trapHandler)
301 801 : recompile(fop);
302 1278 : enabledCount++;
303 1278 : }
304 :
305 : void
306 1278 : BreakpointSite::dec(FreeOp *fop)
307 : {
308 1278 : JS_ASSERT(enabledCount > 0);
309 1278 : enabledCount--;
310 1278 : if (enabledCount == 0 && !trapHandler)
311 810 : recompile(fop);
312 1278 : }
313 :
314 : void
315 371 : BreakpointSite::setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure)
316 : {
317 371 : if (enabledCount == 0)
318 371 : recompile(fop);
319 371 : trapHandler = handler;
320 371 : trapClosure = closure;
321 371 : }
322 :
323 : void
324 659 : BreakpointSite::clearTrap(FreeOp *fop, JSTrapHandler *handlerp, Value *closurep)
325 : {
326 659 : if (handlerp)
327 0 : *handlerp = trapHandler;
328 659 : if (closurep)
329 0 : *closurep = trapClosure;
330 :
331 659 : trapHandler = NULL;
332 659 : trapClosure = UndefinedValue();
333 659 : if (enabledCount == 0) {
334 362 : if (!fop->runtime()->gcRunning) {
335 : /* If the GC is running then the script is being destroyed. */
336 362 : recompile(fop);
337 : }
338 362 : destroyIfEmpty(fop);
339 : }
340 659 : }
341 :
342 : void
343 1631 : BreakpointSite::destroyIfEmpty(FreeOp *fop)
344 : {
345 1631 : if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler)
346 1172 : script->destroyBreakpointSite(fop, pc);
347 1631 : }
348 :
349 : Breakpoint *
350 3755 : BreakpointSite::firstBreakpoint() const
351 : {
352 3755 : if (JS_CLIST_IS_EMPTY(&breakpoints))
353 344 : return NULL;
354 3411 : return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints));
355 : }
356 :
357 : bool
358 1359 : BreakpointSite::hasBreakpoint(Breakpoint *bp)
359 : {
360 2349 : for (Breakpoint *p = firstBreakpoint(); p; p = p->nextInSite())
361 2340 : if (p == bp)
362 1350 : return true;
363 9 : return false;
364 : }
365 :
366 1269 : Breakpoint::Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler)
367 1269 : : debugger(debugger), site(site), handler(handler)
368 : {
369 1269 : JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints);
370 1269 : JS_APPEND_LINK(&siteLinks, &site->breakpoints);
371 1269 : }
372 :
373 : Breakpoint *
374 583 : Breakpoint::fromDebuggerLinks(JSCList *links)
375 : {
376 583 : return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, debuggerLinks));
377 : }
378 :
379 : Breakpoint *
380 5760 : Breakpoint::fromSiteLinks(JSCList *links)
381 : {
382 5760 : return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, siteLinks));
383 : }
384 :
385 : void
386 1269 : Breakpoint::destroy(FreeOp *fop)
387 : {
388 1269 : if (debugger->enabled)
389 1269 : site->dec(fop);
390 1269 : JS_REMOVE_LINK(&debuggerLinks);
391 1269 : JS_REMOVE_LINK(&siteLinks);
392 1269 : site->destroyIfEmpty(fop);
393 1269 : fop->delete_(this);
394 1269 : }
395 :
396 : Breakpoint *
397 549 : Breakpoint::nextInDebugger()
398 : {
399 549 : JSCList *link = JS_NEXT_LINK(&debuggerLinks);
400 549 : return (link == &debugger->breakpoints) ? NULL : fromDebuggerLinks(link);
401 : }
402 :
403 : Breakpoint *
404 4410 : Breakpoint::nextInSite()
405 : {
406 4410 : JSCList *link = JS_NEXT_LINK(&siteLinks);
407 4410 : return (link == &site->breakpoints) ? NULL : fromSiteLinks(link);
408 : }
409 :
410 :
411 : /*** Debugger hook dispatch **********************************************************************/
412 :
413 4259 : Debugger::Debugger(JSContext *cx, JSObject *dbg)
414 : : object(dbg), uncaughtExceptionHook(NULL), enabled(true),
415 4259 : frames(cx), scripts(cx), objects(cx), environments(cx)
416 : {
417 4259 : assertSameCompartment(cx, dbg);
418 :
419 4259 : JSRuntime *rt = cx->runtime;
420 4259 : JS_APPEND_LINK(&link, &rt->debuggerList);
421 4259 : JS_INIT_CLIST(&breakpoints);
422 4259 : }
423 :
424 8518 : Debugger::~Debugger()
425 : {
426 4259 : JS_ASSERT(debuggees.empty());
427 :
428 : /* This always happens in the GC thread, so no locking is required. */
429 4259 : JS_ASSERT(object->compartment()->rt->gcRunning);
430 4259 : JS_REMOVE_LINK(&link);
431 4259 : }
432 :
433 : bool
434 4259 : Debugger::init(JSContext *cx)
435 : {
436 4259 : bool ok = debuggees.init() &&
437 4259 : frames.init() &&
438 4259 : scripts.init() &&
439 4259 : objects.init() &&
440 17036 : environments.init();
441 4259 : if (!ok)
442 0 : js_ReportOutOfMemory(cx);
443 4259 : return ok;
444 : }
445 :
446 : JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER));
447 : JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER));
448 : JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGENV_OWNER));
449 :
450 : Debugger *
451 73027 : Debugger::fromChildJSObject(JSObject *obj)
452 : {
453 132868 : JS_ASSERT(obj->getClass() == &DebuggerFrame_class ||
454 : obj->getClass() == &DebuggerScript_class ||
455 : obj->getClass() == &DebuggerObject_class ||
456 132868 : obj->getClass() == &DebuggerEnv_class);
457 73027 : JSObject *dbgobj = &obj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject();
458 73027 : return fromJSObject(dbgobj);
459 : }
460 :
461 : bool
462 19460 : Debugger::getScriptFrame(JSContext *cx, StackFrame *fp, Value *vp)
463 : {
464 19460 : JS_ASSERT(fp->isScriptFrame());
465 38920 : FrameMap::AddPtr p = frames.lookupForAdd(fp);
466 19460 : if (!p) {
467 : /* Create and populate the Debugger.Frame object. */
468 14292 : JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject();
469 : JSObject *frameobj =
470 14292 : NewObjectWithGivenProto(cx, &DebuggerFrame_class, proto, NULL);
471 14292 : if (!frameobj)
472 0 : return false;
473 14292 : frameobj->setPrivate(fp);
474 14292 : frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
475 :
476 14292 : if (!frames.add(p, fp, frameobj)) {
477 0 : js_ReportOutOfMemory(cx);
478 0 : return false;
479 : }
480 : }
481 19460 : vp->setObject(*p->value);
482 19460 : return true;
483 : }
484 :
485 : JSObject *
486 78913 : Debugger::getHook(Hook hook) const
487 : {
488 78913 : JS_ASSERT(hook >= 0 && hook < HookCount);
489 78913 : const Value &v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook);
490 78913 : return v.isUndefined() ? NULL : &v.toObject();
491 : }
492 :
493 : bool
494 924 : Debugger::hasAnyLiveHooks() const
495 : {
496 924 : if (!enabled)
497 122 : return false;
498 :
499 1083 : if (getHook(OnDebuggerStatement) ||
500 213 : getHook(OnExceptionUnwind) ||
501 34 : getHook(OnNewScript) ||
502 34 : getHook(OnEnterFrame))
503 : {
504 768 : return true;
505 : }
506 :
507 : /* If any breakpoints are in live scripts, return true. */
508 34 : for (Breakpoint *bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
509 34 : if (!IsAboutToBeFinalized(bp->site->script))
510 34 : return true;
511 : }
512 :
513 0 : for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
514 0 : JSObject *frameObj = r.front().value;
515 0 : if (!frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() ||
516 0 : !frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined())
517 0 : return true;
518 : }
519 :
520 0 : return false;
521 : }
522 :
523 : JSTrapStatus
524 28174 : Debugger::slowPathOnEnterFrame(JSContext *cx, Value *vp)
525 : {
526 : /* Build the list of recipients. */
527 56348 : AutoValueVector triggered(cx);
528 28174 : GlobalObject *global = &cx->fp()->scopeChain().global();
529 28174 : if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
530 57514 : for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
531 29648 : Debugger *dbg = *p;
532 29648 : JS_ASSERT(dbg->observesFrame(cx->fp()));
533 29648 : if (dbg->observesEnterFrame() && !triggered.append(ObjectValue(*dbg->toJSObject())))
534 0 : return JSTRAP_ERROR;
535 : }
536 : }
537 :
538 : /* Deliver the event, checking again as in dispatchHook. */
539 30316 : for (Value *p = triggered.begin(); p != triggered.end(); p++) {
540 4255 : Debugger *dbg = Debugger::fromJSObject(&p->toObject());
541 4255 : if (dbg->debuggees.has(global) && dbg->observesEnterFrame()) {
542 4255 : JSTrapStatus status = dbg->fireEnterFrame(cx, vp);
543 4255 : if (status != JSTRAP_CONTINUE)
544 2113 : return status;
545 : }
546 : }
547 :
548 26061 : return JSTRAP_CONTINUE;
549 : }
550 :
551 : /*
552 : * Handle leaving a frame with debuggers watching. |frameOk| indicates whether
553 : * the frame is exiting normally or abruptly. Set |cx|'s exception and/or
554 : * |cx->fp()|'s return value, and return a new success value.
555 : */
556 : bool
557 28368 : Debugger::slowPathOnLeaveFrame(JSContext *cx, bool frameOk)
558 : {
559 28368 : StackFrame *fp = cx->fp();
560 28368 : GlobalObject *global = &fp->scopeChain().global();
561 :
562 : /* Save the frame's completion value. */
563 : JSTrapStatus status;
564 : Value value;
565 28368 : Debugger::resultToCompletion(cx, frameOk, fp->returnValue(), &status, &value);
566 :
567 : /* Build a list of the recipients. */
568 56736 : AutoObjectVector frames(cx);
569 42615 : for (FrameRange r(cx, fp, global); !r.empty(); r.popFront()) {
570 14247 : if (!frames.append(r.frontFrame())) {
571 0 : cx->clearPendingException();
572 0 : return false;
573 : }
574 : }
575 :
576 : /* For each Debugger.Frame, fire its onPop handler, if any. */
577 42615 : for (JSObject **p = frames.begin(); p != frames.end(); p++) {
578 14247 : JSObject *frameobj = *p;
579 14247 : Debugger *dbg = Debugger::fromChildJSObject(frameobj);
580 :
581 28350 : if (dbg->enabled &&
582 14103 : !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) {
583 981 : const Value &handler = frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER);
584 :
585 1962 : AutoCompartment ac(cx, dbg->object);
586 :
587 981 : if (!ac.enter()) {
588 0 : status = JSTRAP_ERROR;
589 : break;
590 : }
591 :
592 : Value completion;
593 981 : if (!dbg->newCompletionValue(cx, status, value, &completion)) {
594 0 : status = dbg->handleUncaughtException(ac, NULL, false);
595 : break;
596 : }
597 :
598 : /* Call the onPop handler. */
599 : Value rval;
600 981 : bool hookOk = Invoke(cx, ObjectValue(*frameobj), handler, 1, &completion, &rval);
601 : Value nextValue;
602 981 : JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval, &nextValue);
603 :
604 : /*
605 : * At this point, we are back in the debuggee compartment, and any error has
606 : * been wrapped up as a completion value.
607 : */
608 981 : JS_ASSERT(cx->compartment == global->compartment());
609 981 : JS_ASSERT(!cx->isExceptionPending());
610 :
611 : /* JSTRAP_CONTINUE means "make no change". */
612 981 : if (nextStatus != JSTRAP_CONTINUE) {
613 414 : status = nextStatus;
614 414 : value = nextValue;
615 : }
616 : }
617 : }
618 :
619 : /*
620 : * Clean up all Debugger.Frame instances. Use a fresh FrameRange, as one
621 : * debugger's onPop handler could have caused another debugger to create its
622 : * own Debugger.Frame instance.
623 : */
624 42624 : for (FrameRange r(cx, fp, global); !r.empty(); r.popFront()) {
625 14256 : JSObject *frameobj = r.frontFrame();
626 14256 : Debugger *dbg = r.frontDebugger();
627 14256 : JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
628 :
629 14256 : frameobj->setPrivate(NULL);
630 :
631 : /* If this frame had an onStep handler, adjust the script's count. */
632 15138 : if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() &&
633 441 : fp->isScriptFrame() &&
634 441 : !fp->script()->changeStepModeCount(cx, -1))
635 : {
636 0 : status = JSTRAP_ERROR;
637 : /* Don't exit the loop; we must mark all frames as dead. */
638 : }
639 :
640 14256 : dbg->frames.remove(fp);
641 : }
642 :
643 : /*
644 : * If this is an eval frame, then from the debugger's perspective the
645 : * script is about to be destroyed. Remove any breakpoints in it.
646 : */
647 28368 : if (fp->isEvalFrame()) {
648 8675 : JSScript *script = fp->script();
649 8675 : script->clearBreakpointsIn(cx, NULL, NULL);
650 : }
651 :
652 : /* Establish (status, value) as our resumption value. */
653 28368 : switch (status) {
654 : case JSTRAP_RETURN:
655 25080 : fp->setReturnValue(value);
656 25080 : return true;
657 :
658 : case JSTRAP_THROW:
659 745 : cx->setPendingException(value);
660 745 : return false;
661 :
662 : case JSTRAP_ERROR:
663 2543 : JS_ASSERT(!cx->isExceptionPending());
664 2543 : return false;
665 :
666 : default:
667 0 : JS_NOT_REACHED("bad final trap status");
668 : }
669 : }
670 :
671 : bool
672 8973 : Debugger::wrapEnvironment(JSContext *cx, Env *env, Value *rval)
673 : {
674 8973 : if (!env) {
675 135 : rval->setNull();
676 135 : return true;
677 : }
678 :
679 : JSObject *envobj;
680 17676 : ObjectWeakMap::AddPtr p = environments.lookupForAdd(env);
681 8838 : if (p) {
682 1233 : envobj = p->value;
683 : } else {
684 : /* Create a new Debugger.Environment for env. */
685 7605 : JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject();
686 7605 : envobj = NewObjectWithGivenProto(cx, &DebuggerEnv_class, proto, NULL);
687 7605 : if (!envobj)
688 0 : return false;
689 7605 : envobj->setPrivate(env);
690 7605 : envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object));
691 7605 : if (!environments.relookupOrAdd(p, env, envobj)) {
692 0 : js_ReportOutOfMemory(cx);
693 0 : return false;
694 : }
695 : }
696 8838 : rval->setObject(*envobj);
697 8838 : return true;
698 : }
699 :
700 : bool
701 12614 : Debugger::wrapDebuggeeValue(JSContext *cx, Value *vp)
702 : {
703 12614 : assertSameCompartment(cx, object.get());
704 :
705 12614 : if (vp->isObject()) {
706 7039 : JSObject *obj = &vp->toObject();
707 :
708 14078 : ObjectWeakMap::AddPtr p = objects.lookupForAdd(obj);
709 7039 : if (p) {
710 2782 : vp->setObject(*p->value);
711 : } else {
712 : /* Create a new Debugger.Object for obj. */
713 4257 : JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject();
714 : JSObject *dobj =
715 4257 : NewObjectWithGivenProto(cx, &DebuggerObject_class, proto, NULL);
716 4257 : if (!dobj)
717 0 : return false;
718 4257 : dobj->setPrivate(obj);
719 4257 : dobj->setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*object));
720 4257 : if (!objects.relookupOrAdd(p, obj, dobj)) {
721 0 : js_ReportOutOfMemory(cx);
722 0 : return false;
723 : }
724 4257 : vp->setObject(*dobj);
725 : }
726 5575 : } else if (!cx->compartment->wrap(cx, vp)) {
727 0 : vp->setUndefined();
728 0 : return false;
729 : }
730 :
731 12614 : return true;
732 : }
733 :
734 : bool
735 2889 : Debugger::unwrapDebuggeeValue(JSContext *cx, Value *vp)
736 : {
737 2889 : assertSameCompartment(cx, object.get(), *vp);
738 2889 : if (vp->isObject()) {
739 540 : JSObject *dobj = &vp->toObject();
740 540 : if (dobj->getClass() != &DebuggerObject_class) {
741 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPECTED_TYPE,
742 54 : "Debugger", "Debugger.Object", dobj->getClass()->name);
743 54 : return false;
744 : }
745 :
746 486 : Value owner = dobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
747 486 : if (owner.toObjectOrNull() != object) {
748 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
749 27 : owner.isNull()
750 : ? JSMSG_DEBUG_OBJECT_PROTO
751 27 : : JSMSG_DEBUG_OBJECT_WRONG_OWNER);
752 27 : return false;
753 : }
754 :
755 459 : vp->setObject(*(JSObject *) dobj->getPrivate());
756 : }
757 2808 : return true;
758 : }
759 :
760 : JSTrapStatus
761 2140 : Debugger::handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook)
762 : {
763 2140 : JSContext *cx = ac.context;
764 2140 : if (cx->isExceptionPending()) {
765 108 : if (callHook && uncaughtExceptionHook) {
766 81 : Value fval = ObjectValue(*uncaughtExceptionHook);
767 81 : Value exc = cx->getPendingException();
768 : Value rv;
769 81 : cx->clearPendingException();
770 81 : if (Invoke(cx, ObjectValue(*object), fval, 1, &exc, &rv))
771 81 : return vp ? parseResumptionValue(ac, true, rv, vp, false) : JSTRAP_CONTINUE;
772 : }
773 :
774 27 : if (cx->isExceptionPending()) {
775 27 : JS_ReportPendingException(cx);
776 27 : cx->clearPendingException();
777 : }
778 : }
779 2059 : ac.leave();
780 2059 : return JSTRAP_ERROR;
781 : }
782 :
783 : void
784 30870 : Debugger::resultToCompletion(JSContext *cx, bool ok, const Value &rv,
785 : JSTrapStatus *status, Value *value)
786 : {
787 30870 : JS_ASSERT_IF(ok, !cx->isExceptionPending());
788 :
789 30870 : if (ok) {
790 27222 : *status = JSTRAP_RETURN;
791 27222 : *value = rv;
792 3648 : } else if (cx->isExceptionPending()) {
793 817 : *status = JSTRAP_THROW;
794 817 : *value = cx->getPendingException();
795 817 : cx->clearPendingException();
796 : } else {
797 2831 : *status = JSTRAP_ERROR;
798 2831 : value->setUndefined();
799 : }
800 30870 : }
801 :
802 : bool
803 3483 : Debugger::newCompletionValue(JSContext *cx, JSTrapStatus status, Value value, Value *result)
804 : {
805 : /*
806 : * We must be in the debugger's compartment, since that's where we want
807 : * to construct the completion value.
808 : */
809 3483 : assertSameCompartment(cx, object.get());
810 :
811 : jsid key;
812 :
813 3483 : switch (status) {
814 : case JSTRAP_RETURN:
815 2700 : key = ATOM_TO_JSID(cx->runtime->atomState.returnAtom);
816 2700 : break;
817 :
818 : case JSTRAP_THROW:
819 297 : key = ATOM_TO_JSID(cx->runtime->atomState.throwAtom);
820 297 : break;
821 :
822 : case JSTRAP_ERROR:
823 486 : result->setNull();
824 486 : return true;
825 :
826 : default:
827 0 : JS_NOT_REACHED("bad status passed to Debugger::newCompletionValue");
828 : }
829 :
830 : /* Common tail for JSTRAP_RETURN and JSTRAP_THROW. */
831 2997 : JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
832 8991 : if (!obj ||
833 2997 : !wrapDebuggeeValue(cx, &value) ||
834 : !DefineNativeProperty(cx, obj, key, value, JS_PropertyStub, JS_StrictPropertyStub,
835 2997 : JSPROP_ENUMERATE, 0, 0))
836 : {
837 0 : return false;
838 : }
839 :
840 2997 : result->setObject(*obj);
841 2997 : return true;
842 : }
843 :
844 : bool
845 2502 : Debugger::receiveCompletionValue(AutoCompartment &ac, bool ok, Value val, Value *vp)
846 : {
847 2502 : JSContext *cx = ac.context;
848 :
849 : JSTrapStatus status;
850 : Value value;
851 2502 : resultToCompletion(cx, ok, val, &status, &value);
852 2502 : ac.leave();
853 2502 : return newCompletionValue(cx, status, value, vp);
854 : }
855 :
856 : JSTrapStatus
857 21320 : Debugger::parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp,
858 : bool callHook)
859 : {
860 21320 : vp->setUndefined();
861 21320 : if (!ok)
862 2131 : return handleUncaughtException(ac, vp, callHook);
863 19189 : if (rv.isUndefined()) {
864 18001 : ac.leave();
865 18001 : return JSTRAP_CONTINUE;
866 : }
867 1188 : if (rv.isNull()) {
868 378 : ac.leave();
869 378 : return JSTRAP_ERROR;
870 : }
871 :
872 : /* Check that rv is {return: val} or {throw: val}. */
873 810 : JSContext *cx = ac.context;
874 : JSObject *obj;
875 : const Shape *shape;
876 810 : jsid returnId = ATOM_TO_JSID(cx->runtime->atomState.returnAtom);
877 810 : jsid throwId = ATOM_TO_JSID(cx->runtime->atomState.throwAtom);
878 810 : bool okResumption = rv.isObject();
879 810 : if (okResumption) {
880 810 : obj = &rv.toObject();
881 810 : okResumption = obj->isObject();
882 : }
883 810 : if (okResumption) {
884 810 : shape = obj->lastProperty();
885 810 : okResumption = shape->previous() &&
886 1620 : !shape->previous()->previous() &&
887 1143 : (shape->propid() == returnId || shape->propid() == throwId) &&
888 2763 : shape->isDataDescriptor();
889 : }
890 810 : if (!okResumption) {
891 9 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_RESUMPTION);
892 9 : return handleUncaughtException(ac, vp, callHook);
893 : }
894 :
895 801 : if (!js_NativeGet(cx, obj, obj, shape, 0, vp) || !unwrapDebuggeeValue(cx, vp))
896 0 : return handleUncaughtException(ac, vp, callHook);
897 :
898 801 : ac.leave();
899 801 : if (!cx->compartment->wrap(cx, vp)) {
900 0 : vp->setUndefined();
901 0 : return JSTRAP_ERROR;
902 : }
903 801 : return shape->propid() == returnId ? JSTRAP_RETURN : JSTRAP_THROW;
904 : }
905 :
906 : bool
907 1350 : CallMethodIfPresent(JSContext *cx, JSObject *obj, const char *name, int argc, Value *argv,
908 : Value *rval)
909 : {
910 1350 : rval->setUndefined();
911 1350 : JSAtom *atom = js_Atomize(cx, name, strlen(name));
912 : Value fval;
913 : return atom &&
914 1350 : js_GetMethod(cx, obj, ATOM_TO_JSID(atom), 0, &fval) &&
915 1350 : (!js_IsCallable(fval) ||
916 4050 : Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval));
917 : }
918 :
919 : JSTrapStatus
920 8662 : Debugger::fireDebuggerStatement(JSContext *cx, Value *vp)
921 : {
922 8662 : JSObject *hook = getHook(OnDebuggerStatement);
923 8662 : JS_ASSERT(hook);
924 8662 : JS_ASSERT(hook->isCallable());
925 :
926 : /* Grab cx->fp() before pushing a dummy frame. */
927 8662 : StackFrame *fp = cx->fp();
928 17324 : AutoCompartment ac(cx, object);
929 8662 : if (!ac.enter())
930 0 : return JSTRAP_ERROR;
931 :
932 : Value argv[1];
933 8662 : if (!getScriptFrame(cx, fp, argv))
934 0 : return handleUncaughtException(ac, vp, false);
935 :
936 : Value rv;
937 8662 : bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv, &rv);
938 8662 : return parseResumptionValue(ac, ok, rv, vp);
939 : }
940 :
941 : JSTrapStatus
942 450 : Debugger::fireExceptionUnwind(JSContext *cx, Value *vp)
943 : {
944 450 : JSObject *hook = getHook(OnExceptionUnwind);
945 450 : JS_ASSERT(hook);
946 450 : JS_ASSERT(hook->isCallable());
947 :
948 450 : StackFrame *fp = cx->fp();
949 450 : Value exc = cx->getPendingException();
950 450 : cx->clearPendingException();
951 :
952 900 : AutoCompartment ac(cx, object);
953 450 : if (!ac.enter())
954 0 : return JSTRAP_ERROR;
955 :
956 : Value argv[2];
957 450 : argv[1] = exc;
958 450 : if (!getScriptFrame(cx, fp, &argv[0]) || !wrapDebuggeeValue(cx, &argv[1]))
959 0 : return handleUncaughtException(ac, vp, false);
960 :
961 : Value rv;
962 450 : bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 2, argv, &rv);
963 450 : JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp);
964 450 : if (st == JSTRAP_CONTINUE)
965 387 : cx->setPendingException(exc);
966 450 : return st;
967 : }
968 :
969 : JSTrapStatus
970 4255 : Debugger::fireEnterFrame(JSContext *cx, Value *vp)
971 : {
972 4255 : JSObject *hook = getHook(OnEnterFrame);
973 4255 : JS_ASSERT(hook);
974 4255 : JS_ASSERT(hook->isCallable());
975 :
976 4255 : StackFrame *fp = cx->fp();
977 8510 : AutoCompartment ac(cx, object);
978 4255 : if (!ac.enter())
979 0 : return JSTRAP_ERROR;
980 :
981 : Value argv[1];
982 4255 : if (!getScriptFrame(cx, fp, &argv[0]))
983 0 : return handleUncaughtException(ac, vp, false);
984 :
985 : Value rv;
986 4255 : bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv, &rv);
987 4255 : return parseResumptionValue(ac, ok, rv, vp);
988 : }
989 :
990 : void
991 200 : Debugger::fireNewScript(JSContext *cx, JSScript *script)
992 : {
993 200 : JSObject *hook = getHook(OnNewScript);
994 200 : JS_ASSERT(hook);
995 200 : JS_ASSERT(hook->isCallable());
996 :
997 400 : AutoCompartment ac(cx, object);
998 200 : if (!ac.enter())
999 : return;
1000 :
1001 200 : JSObject *dsobj = wrapScript(cx, script);
1002 200 : if (!dsobj) {
1003 0 : handleUncaughtException(ac, NULL, false);
1004 : return;
1005 : }
1006 :
1007 : Value argv[1];
1008 200 : argv[0].setObject(*dsobj);
1009 : Value rv;
1010 200 : if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv, &rv))
1011 0 : handleUncaughtException(ac, NULL, true);
1012 : }
1013 :
1014 : JSTrapStatus
1015 9191 : Debugger::dispatchHook(JSContext *cx, Value *vp, Hook which)
1016 : {
1017 9191 : JS_ASSERT(which == OnDebuggerStatement || which == OnExceptionUnwind);
1018 :
1019 : /*
1020 : * Determine which debuggers will receive this event, and in what order.
1021 : * Make a copy of the list, since the original is mutable and we will be
1022 : * calling into arbitrary JS.
1023 : *
1024 : * Note: In the general case, 'triggered' contains references to objects in
1025 : * different compartments--every compartment *except* this one.
1026 : */
1027 18382 : AutoValueVector triggered(cx);
1028 9191 : GlobalObject *global = &cx->fp()->scopeChain().global();
1029 9191 : if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
1030 19354 : for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
1031 10181 : Debugger *dbg = *p;
1032 10181 : if (dbg->enabled && dbg->getHook(which)) {
1033 9184 : if (!triggered.append(ObjectValue(*dbg->toJSObject())))
1034 0 : return JSTRAP_ERROR;
1035 : }
1036 : }
1037 : }
1038 :
1039 : /*
1040 : * Deliver the event to each debugger, checking again to make sure it
1041 : * should still be delivered.
1042 : */
1043 17682 : for (Value *p = triggered.begin(); p != triggered.end(); p++) {
1044 9139 : Debugger *dbg = Debugger::fromJSObject(&p->toObject());
1045 9139 : if (dbg->debuggees.has(global) && dbg->enabled && dbg->getHook(which)) {
1046 : JSTrapStatus st = (which == OnDebuggerStatement)
1047 : ? dbg->fireDebuggerStatement(cx, vp)
1048 9112 : : dbg->fireExceptionUnwind(cx, vp);
1049 9112 : if (st != JSTRAP_CONTINUE)
1050 648 : return st;
1051 : }
1052 : }
1053 8543 : return JSTRAP_CONTINUE;
1054 : }
1055 :
1056 : static bool
1057 9607 : AddNewScriptRecipients(GlobalObject::DebuggerVector *src, AutoValueVector *dest)
1058 : {
1059 9607 : bool wasEmpty = dest->length() == 0;
1060 20717 : for (Debugger **p = src->begin(); p != src->end(); p++) {
1061 11110 : Debugger *dbg = *p;
1062 11110 : Value v = ObjectValue(*dbg->toJSObject());
1063 11310 : if (dbg->observesNewScript() &&
1064 0 : (wasEmpty || Find(dest->begin(), dest->end(), v) == dest->end()) &&
1065 200 : !dest->append(v))
1066 : {
1067 0 : return false;
1068 : }
1069 : }
1070 9607 : return true;
1071 : }
1072 :
1073 : void
1074 9834 : Debugger::slowPathOnNewScript(JSContext *cx, JSScript *script, GlobalObject *compileAndGoGlobal)
1075 : {
1076 9834 : JS_ASSERT(script->compileAndGo == !!compileAndGoGlobal);
1077 :
1078 : /*
1079 : * Build the list of recipients. For compile-and-go scripts, this is the
1080 : * same as the generic Debugger::dispatchHook code, but non-compile-and-go
1081 : * scripts are not tied to particular globals. We deliver them to every
1082 : * debugger observing any global in the script's compartment.
1083 : */
1084 19668 : AutoValueVector triggered(cx);
1085 9834 : if (script->compileAndGo) {
1086 8790 : if (GlobalObject::DebuggerVector *debuggers = compileAndGoGlobal->getDebuggers()) {
1087 8563 : if (!AddNewScriptRecipients(debuggers, &triggered))
1088 : return;
1089 : }
1090 : } else {
1091 1044 : GlobalObjectSet &debuggees = script->compartment()->getDebuggees();
1092 2088 : for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
1093 1044 : if (!AddNewScriptRecipients(r.front()->getDebuggers(), &triggered))
1094 : return;
1095 : }
1096 : }
1097 :
1098 : /*
1099 : * Deliver the event to each debugger, checking again as in
1100 : * Debugger::dispatchHook.
1101 : */
1102 10034 : for (Value *p = triggered.begin(); p != triggered.end(); p++) {
1103 200 : Debugger *dbg = Debugger::fromJSObject(&p->toObject());
1104 400 : if ((!compileAndGoGlobal || dbg->debuggees.has(compileAndGoGlobal)) &&
1105 200 : dbg->enabled && dbg->getHook(OnNewScript)) {
1106 200 : dbg->fireNewScript(cx, script);
1107 : }
1108 : }
1109 : }
1110 :
1111 : JSTrapStatus
1112 1262 : Debugger::onTrap(JSContext *cx, Value *vp)
1113 : {
1114 1262 : StackFrame *fp = cx->fp();
1115 1262 : JSScript *script = fp->script();
1116 1262 : GlobalObject *scriptGlobal = &fp->scopeChain().global();
1117 1262 : jsbytecode *pc = cx->regs().pc;
1118 1262 : BreakpointSite *site = script->getBreakpointSite(pc);
1119 1262 : JSOp op = JSOp(*pc);
1120 :
1121 : /* Build list of breakpoint handlers. */
1122 2524 : Vector<Breakpoint *> triggered(cx);
1123 2648 : for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
1124 1386 : if (!triggered.append(bp))
1125 0 : return JSTRAP_ERROR;
1126 : }
1127 :
1128 2621 : for (Breakpoint **p = triggered.begin(); p != triggered.end(); p++) {
1129 1386 : Breakpoint *bp = *p;
1130 :
1131 : /* Handlers can clear breakpoints. Check that bp still exists. */
1132 1386 : if (!site || !site->hasBreakpoint(bp))
1133 36 : continue;
1134 :
1135 1350 : Debugger *dbg = bp->debugger;
1136 1350 : if (dbg->enabled && dbg->debuggees.lookup(scriptGlobal)) {
1137 2700 : AutoCompartment ac(cx, dbg->object);
1138 1350 : if (!ac.enter())
1139 0 : return JSTRAP_ERROR;
1140 :
1141 : Value argv[1];
1142 1350 : if (!dbg->getScriptFrame(cx, fp, &argv[0]))
1143 0 : return dbg->handleUncaughtException(ac, vp, false);
1144 : Value rv;
1145 1350 : bool ok = CallMethodIfPresent(cx, bp->handler, "hit", 1, argv, &rv);
1146 1350 : JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true);
1147 1350 : if (st != JSTRAP_CONTINUE)
1148 27 : return st;
1149 :
1150 : /* Calling JS code invalidates site. Reload it. */
1151 2673 : site = script->getBreakpointSite(pc);
1152 : }
1153 : }
1154 :
1155 1235 : if (site && site->trapHandler) {
1156 353 : JSTrapStatus st = site->trapHandler(cx, fp->script(), pc, vp, site->trapClosure);
1157 353 : if (st != JSTRAP_CONTINUE)
1158 45 : return st;
1159 : }
1160 :
1161 : /* By convention, return the true op to the interpreter in vp. */
1162 1190 : vp->setInt32(op);
1163 1190 : return JSTRAP_CONTINUE;
1164 : }
1165 :
1166 : JSTrapStatus
1167 5813 : Debugger::onSingleStep(JSContext *cx, Value *vp)
1168 : {
1169 5813 : StackFrame *fp = cx->fp();
1170 :
1171 : /*
1172 : * We may be stepping over a JSOP_EXCEPTION, that pushes the context's
1173 : * pending exception for a 'catch' clause to handle. Don't let the
1174 : * onStep handlers mess with that (other than by returning a resumption
1175 : * value).
1176 : */
1177 5813 : Value exception = UndefinedValue();
1178 5813 : bool exceptionPending = cx->isExceptionPending();
1179 5813 : if (exceptionPending) {
1180 44 : exception = cx->getPendingException();
1181 44 : cx->clearPendingException();
1182 : }
1183 :
1184 : /* We should only receive single-step traps for scripted frames. */
1185 5813 : JS_ASSERT(fp->isScriptFrame());
1186 :
1187 : /*
1188 : * Build list of Debugger.Frame instances referring to this frame with
1189 : * onStep handlers.
1190 : */
1191 11626 : AutoObjectVector frames(cx);
1192 11609 : for (FrameRange r(cx, fp); !r.empty(); r.popFront()) {
1193 5796 : JSObject *frame = r.frontFrame();
1194 11337 : if (!frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() &&
1195 5541 : !frames.append(frame))
1196 : {
1197 0 : return JSTRAP_ERROR;
1198 : }
1199 : }
1200 :
1201 : #ifdef DEBUG
1202 : /*
1203 : * Validate the single-step count on this frame's script, to ensure that
1204 : * we're not receiving traps we didn't ask for. Even when frames is
1205 : * non-empty (and thus we know this trap was requested), do the check
1206 : * anyway, to make sure the count has the correct non-zero value.
1207 : *
1208 : * The converse --- ensuring that we do receive traps when we should --- can
1209 : * be done with unit tests.
1210 : */
1211 : {
1212 5813 : uint32_t stepperCount = 0;
1213 5813 : JSScript *trappingScript = fp->script();
1214 5813 : GlobalObject *global = &fp->scopeChain().global();
1215 5813 : if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
1216 11592 : for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
1217 5796 : Debugger *dbg = *p;
1218 13777 : for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
1219 7981 : StackFrame *frame = r.front().key;
1220 7981 : JSObject *frameobj = r.front().value;
1221 23925 : if (frame->isScriptFrame() &&
1222 7981 : frame->script() == trappingScript &&
1223 7963 : !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
1224 : {
1225 6456 : stepperCount++;
1226 : }
1227 : }
1228 : }
1229 : }
1230 5813 : if (trappingScript->compileAndGo)
1231 5813 : JS_ASSERT(stepperCount == trappingScript->stepModeCount());
1232 : else
1233 0 : JS_ASSERT(stepperCount <= trappingScript->stepModeCount());
1234 : }
1235 : #endif
1236 :
1237 : /* Call all the onStep handlers we found. */
1238 11318 : for (JSObject **p = frames.begin(); p != frames.end(); p++) {
1239 5541 : JSObject *frame = *p;
1240 5541 : Debugger *dbg = Debugger::fromChildJSObject(frame);
1241 11082 : AutoCompartment ac(cx, dbg->object);
1242 5541 : if (!ac.enter())
1243 0 : return JSTRAP_ERROR;
1244 5541 : const Value &handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
1245 : Value rval;
1246 5541 : bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, NULL, &rval);
1247 5541 : JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp);
1248 5541 : if (st != JSTRAP_CONTINUE)
1249 36 : return st;
1250 : }
1251 :
1252 5777 : vp->setUndefined();
1253 5777 : if (exceptionPending)
1254 44 : cx->setPendingException(exception);
1255 5777 : return JSTRAP_CONTINUE;
1256 : }
1257 :
1258 :
1259 : /*** Debugger JSObjects **************************************************************************/
1260 :
1261 : void
1262 54 : Debugger::markKeysInCompartment(JSTracer *tracer)
1263 : {
1264 : /*
1265 : * WeakMap::Range is deliberately private, to discourage C++ code from
1266 : * enumerating WeakMap keys. However in this case we need access, so we
1267 : * make a base-class reference. Range is public in HashMap.
1268 : */
1269 : typedef HashMap<HeapPtrObject, HeapPtrObject, DefaultHasher<HeapPtrObject>, RuntimeAllocPolicy>
1270 : ObjectMap;
1271 54 : const ObjectMap &objStorage = objects;
1272 162 : for (ObjectMap::Range r = objStorage.all(); !r.empty(); r.popFront()) {
1273 108 : const HeapPtrObject &key = r.front().key;
1274 216 : HeapPtrObject tmp(key);
1275 108 : gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key");
1276 108 : JS_ASSERT(tmp == key);
1277 : }
1278 :
1279 54 : const ObjectMap &envStorage = environments;
1280 99 : for (ObjectMap::Range r = envStorage.all(); !r.empty(); r.popFront()) {
1281 45 : const HeapPtrObject &key = r.front().key;
1282 90 : HeapPtrObject tmp(key);
1283 45 : js::gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key");
1284 45 : JS_ASSERT(tmp == key);
1285 : }
1286 :
1287 : typedef HashMap<HeapPtrScript, HeapPtrObject, DefaultHasher<HeapPtrScript>, RuntimeAllocPolicy>
1288 : ScriptMap;
1289 54 : const ScriptMap &scriptStorage = scripts;
1290 954 : for (ScriptMap::Range r = scriptStorage.all(); !r.empty(); r.popFront()) {
1291 900 : const HeapPtrScript &key = r.front().key;
1292 1800 : HeapPtrScript tmp(key);
1293 900 : gc::MarkScript(tracer, &tmp, "cross-compartment WeakMap key");
1294 900 : JS_ASSERT(tmp == key);
1295 : }
1296 54 : }
1297 :
1298 : /*
1299 : * Ordinarily, WeakMap keys and values are marked because at some point it was
1300 : * discovered that the WeakMap was live; that is, some object containing the
1301 : * WeakMap was marked during mark phase.
1302 : *
1303 : * However, during compartment GC, we have to do something about
1304 : * cross-compartment WeakMaps in non-GC'd compartments. If their keys and values
1305 : * might need to be marked, we have to do it manually.
1306 : *
1307 : * Each Debugger object keeps three cross-compartment WeakMaps: objects, script,
1308 : * and environments. They have the nice property that all their values are in
1309 : * the same compartment as the Debugger object, so we only need to mark the
1310 : * keys. We must simply mark all keys that are in a compartment being GC'd.
1311 : *
1312 : * We must scan all Debugger objects regardless of whether they *currently*
1313 : * have any debuggees in a compartment being GC'd, because the WeakMap
1314 : * entries persist even when debuggees are removed.
1315 : *
1316 : * This happens during the initial mark phase, not iterative marking, because
1317 : * all the edges being reported here are strong references.
1318 : */
1319 : void
1320 38463 : Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer)
1321 : {
1322 38463 : JSRuntime *rt = tracer->runtime;
1323 :
1324 : /*
1325 : * Mark all objects in comp that are referents of Debugger.Objects in other
1326 : * compartments.
1327 : */
1328 84143 : for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) {
1329 7217 : Debugger *dbg = Debugger::fromLinks(p);
1330 7217 : if (!dbg->object->compartment()->isCollecting())
1331 54 : dbg->markKeysInCompartment(tracer);
1332 : }
1333 38463 : }
1334 :
1335 : /*
1336 : * This method has two tasks:
1337 : * 1. Mark Debugger objects that are unreachable except for debugger hooks that
1338 : * may yet be called.
1339 : * 2. Mark breakpoint handlers.
1340 : *
1341 : * This happens during the iterative part of the GC mark phase. This method
1342 : * returns true if it has to mark anything; GC calls it repeatedly until it
1343 : * returns false.
1344 : */
1345 : bool
1346 76944 : Debugger::markAllIteratively(GCMarker *trc)
1347 : {
1348 76944 : bool markedAny = false;
1349 :
1350 : /*
1351 : * Find all Debugger objects in danger of GC. This code is a little
1352 : * convoluted since the easiest way to find them is via their debuggees.
1353 : */
1354 76944 : JSRuntime *rt = trc->runtime;
1355 245112 : for (CompartmentsIter c(rt); !c.done(); c.next()) {
1356 168168 : const GlobalObjectSet &debuggees = c->getDebuggees();
1357 177484 : for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
1358 9316 : GlobalObject *global = r.front();
1359 9316 : if (IsAboutToBeFinalized(global))
1360 6470 : continue;
1361 :
1362 : /*
1363 : * Every debuggee has at least one debugger, so in this case
1364 : * getDebuggers can't return NULL.
1365 : */
1366 2846 : const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
1367 2846 : JS_ASSERT(debuggers);
1368 9703 : for (Debugger * const *p = debuggers->begin(); p != debuggers->end(); p++) {
1369 6857 : Debugger *dbg = *p;
1370 :
1371 : /*
1372 : * dbg is a Debugger with at least one debuggee. Check three things:
1373 : * - dbg is actually in a compartment being GC'd
1374 : * - it isn't already marked
1375 : * - it actually has hooks that might be called
1376 : */
1377 6857 : HeapPtrObject &dbgobj = dbg->toJSObjectRef();
1378 6857 : if (!dbgobj->compartment()->isCollecting())
1379 108 : continue;
1380 :
1381 6749 : bool dbgMarked = !IsAboutToBeFinalized(dbgobj);
1382 6749 : if (!dbgMarked && dbg->hasAnyLiveHooks()) {
1383 : /*
1384 : * obj could be reachable only via its live, enabled
1385 : * debugger hooks, which may yet be called.
1386 : */
1387 802 : MarkObject(trc, &dbgobj, "enabled Debugger");
1388 802 : markedAny = true;
1389 802 : dbgMarked = true;
1390 : }
1391 :
1392 6749 : if (dbgMarked) {
1393 : /* Search for breakpoints to mark. */
1394 7158 : for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
1395 531 : if (!IsAboutToBeFinalized(bp->site->script)) {
1396 : /*
1397 : * The debugger and the script are both live.
1398 : * Therefore the breakpoint handler is live.
1399 : */
1400 531 : if (IsAboutToBeFinalized(bp->getHandler())) {
1401 163 : MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler");
1402 163 : markedAny = true;
1403 : }
1404 : }
1405 : }
1406 : }
1407 : }
1408 : }
1409 : }
1410 76944 : return markedAny;
1411 : }
1412 :
1413 : void
1414 53253 : Debugger::traceObject(JSTracer *trc, JSObject *obj)
1415 : {
1416 53253 : if (Debugger *dbg = Debugger::fromJSObject(obj))
1417 6824 : dbg->trace(trc);
1418 53253 : }
1419 :
1420 : void
1421 6824 : Debugger::trace(JSTracer *trc)
1422 : {
1423 6824 : if (uncaughtExceptionHook)
1424 0 : MarkObject(trc, &uncaughtExceptionHook, "hooks");
1425 :
1426 : /*
1427 : * Mark Debugger.Frame objects. These are all reachable from JS, because the
1428 : * corresponding StackFrames are still on the stack.
1429 : *
1430 : * (Once we support generator frames properly, we will need
1431 : * weakly-referenced Debugger.Frame objects as well, for suspended generator
1432 : * frames.)
1433 : */
1434 10928 : for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
1435 4104 : HeapPtrObject &frameobj = r.front().value;
1436 4104 : JS_ASSERT(frameobj->getPrivate());
1437 4104 : MarkObject(trc, &frameobj, "live Debugger.Frame");
1438 : }
1439 :
1440 : /* Trace the weak map from JSScript instances to Debugger.Script objects. */
1441 6824 : scripts.trace(trc);
1442 :
1443 : /* Trace the referent -> Debugger.Object weak map. */
1444 6824 : objects.trace(trc);
1445 :
1446 : /* Trace the referent -> Debugger.Environment weak map. */
1447 6824 : environments.trace(trc);
1448 6824 : }
1449 :
1450 : void
1451 38427 : Debugger::sweepAll(FreeOp *fop)
1452 : {
1453 38427 : JSRuntime *rt = fop->runtime();
1454 :
1455 84071 : for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) {
1456 7217 : Debugger *dbg = Debugger::fromLinks(p);
1457 :
1458 7217 : if (IsAboutToBeFinalized(dbg->object)) {
1459 : /*
1460 : * dbg is being GC'd. Detach it from its debuggees. The debuggee
1461 : * might be GC'd too. Since detaching requires access to both
1462 : * objects, this must be done before finalize time.
1463 : */
1464 8500 : for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
1465 4241 : dbg->removeDebuggeeGlobal(fop, e.front(), NULL, &e);
1466 : }
1467 :
1468 : }
1469 :
1470 122385 : for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++) {
1471 : /* For each debuggee being GC'd, detach it from all its debuggers. */
1472 83958 : GlobalObjectSet &debuggees = (*c)->getDebuggees();
1473 85407 : for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
1474 1449 : GlobalObject *global = e.front();
1475 1449 : if (IsAboutToBeFinalized(global))
1476 90 : detachAllDebuggersFromGlobal(fop, global, &e);
1477 : }
1478 : }
1479 38427 : }
1480 :
1481 : void
1482 90 : Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global,
1483 : GlobalObjectSet::Enum *compartmentEnum)
1484 : {
1485 90 : const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
1486 90 : JS_ASSERT(!debuggers->empty());
1487 270 : while (!debuggers->empty())
1488 90 : debuggers->back()->removeDebuggeeGlobal(fop, global, compartmentEnum, NULL);
1489 90 : }
1490 :
1491 : void
1492 27589 : Debugger::finalize(FreeOp *fop, JSObject *obj)
1493 : {
1494 27589 : Debugger *dbg = fromJSObject(obj);
1495 27589 : if (!dbg)
1496 23330 : return;
1497 4259 : JS_ASSERT(dbg->debuggees.empty());
1498 4259 : fop->delete_(dbg);
1499 : }
1500 :
1501 : Class Debugger::jsclass = {
1502 : "Debugger",
1503 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
1504 : JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
1505 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
1506 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Debugger::finalize,
1507 : NULL, /* checkAccess */
1508 : NULL, /* call */
1509 : NULL, /* construct */
1510 : NULL, /* hasInstance */
1511 : Debugger::traceObject
1512 : };
1513 :
1514 : Debugger *
1515 10784 : Debugger::fromThisValue(JSContext *cx, const CallArgs &args, const char *fnname)
1516 : {
1517 10784 : if (!args.thisv().isObject()) {
1518 9 : ReportObjectRequired(cx);
1519 9 : return NULL;
1520 : }
1521 10775 : JSObject *thisobj = &args.thisv().toObject();
1522 10775 : if (thisobj->getClass() != &Debugger::jsclass) {
1523 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
1524 18 : "Debugger", fnname, thisobj->getClass()->name);
1525 18 : return NULL;
1526 : }
1527 :
1528 : /*
1529 : * Forbid Debugger.prototype, which is of the Debugger JSClass but isn't
1530 : * really a Debugger object. The prototype object is distinguished by
1531 : * having a NULL private value.
1532 : */
1533 10757 : Debugger *dbg = fromJSObject(thisobj);
1534 10757 : if (!dbg) {
1535 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
1536 27 : "Debugger", fnname, "prototype object");
1537 : }
1538 10757 : return dbg;
1539 : }
1540 :
1541 : #define THIS_DEBUGGER(cx, argc, vp, fnname, args, dbg) \
1542 : CallArgs args = CallArgsFromVp(argc, vp); \
1543 : Debugger *dbg = Debugger::fromThisValue(cx, args, fnname); \
1544 : if (!dbg) \
1545 : return false
1546 :
1547 : JSBool
1548 90 : Debugger::getEnabled(JSContext *cx, unsigned argc, Value *vp)
1549 : {
1550 90 : THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg);
1551 90 : args.rval().setBoolean(dbg->enabled);
1552 90 : return true;
1553 : }
1554 :
1555 : JSBool
1556 351 : Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp)
1557 : {
1558 351 : REQUIRE_ARGC("Debugger.set enabled", 1);
1559 351 : THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg);
1560 351 : bool enabled = js_ValueToBoolean(args[0]);
1561 :
1562 351 : if (enabled != dbg->enabled) {
1563 297 : for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
1564 18 : if (enabled)
1565 9 : bp->site->inc(cx->runtime->defaultFreeOp());
1566 : else
1567 9 : bp->site->dec(cx->runtime->defaultFreeOp());
1568 : }
1569 : }
1570 :
1571 351 : dbg->enabled = enabled;
1572 351 : args.rval().setUndefined();
1573 351 : return true;
1574 : }
1575 :
1576 : JSBool
1577 54 : Debugger::getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which)
1578 : {
1579 54 : JS_ASSERT(which >= 0 && which < HookCount);
1580 54 : THIS_DEBUGGER(cx, argc, vp, "getHook", args, dbg);
1581 27 : args.rval() = dbg->object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + which);
1582 27 : return true;
1583 : }
1584 :
1585 : JSBool
1586 5807 : Debugger::setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which)
1587 : {
1588 5807 : JS_ASSERT(which >= 0 && which < HookCount);
1589 5807 : REQUIRE_ARGC("Debugger.setHook", 1);
1590 5789 : THIS_DEBUGGER(cx, argc, vp, "setHook", args, dbg);
1591 5771 : const Value &v = args[0];
1592 5771 : if (v.isObject()) {
1593 5555 : if (!v.toObject().isCallable()) {
1594 9 : js_ReportIsNotFunction(cx, vp, JSV2F_SEARCH_STACK);
1595 9 : return false;
1596 : }
1597 216 : } else if (!v.isUndefined()) {
1598 18 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
1599 18 : return false;
1600 : }
1601 5744 : dbg->object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, v);
1602 5744 : args.rval().setUndefined();
1603 5744 : return true;
1604 : }
1605 :
1606 : JSBool
1607 54 : Debugger::getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
1608 : {
1609 54 : return getHookImpl(cx, argc, vp, OnDebuggerStatement);
1610 : }
1611 :
1612 : JSBool
1613 3898 : Debugger::setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
1614 : {
1615 3898 : return setHookImpl(cx, argc, vp, OnDebuggerStatement);
1616 : }
1617 :
1618 : JSBool
1619 0 : Debugger::getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp)
1620 : {
1621 0 : return getHookImpl(cx, argc, vp, OnExceptionUnwind);
1622 : }
1623 :
1624 : JSBool
1625 360 : Debugger::setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp)
1626 : {
1627 360 : return setHookImpl(cx, argc, vp, OnExceptionUnwind);
1628 : }
1629 :
1630 : JSBool
1631 0 : Debugger::getOnNewScript(JSContext *cx, unsigned argc, Value *vp)
1632 : {
1633 0 : return getHookImpl(cx, argc, vp, OnNewScript);
1634 : }
1635 :
1636 : JSBool
1637 28 : Debugger::setOnNewScript(JSContext *cx, unsigned argc, Value *vp)
1638 : {
1639 28 : return setHookImpl(cx, argc, vp, OnNewScript);
1640 : }
1641 :
1642 : JSBool
1643 0 : Debugger::getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
1644 : {
1645 0 : return getHookImpl(cx, argc, vp, OnEnterFrame);
1646 : }
1647 :
1648 : JSBool
1649 1521 : Debugger::setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
1650 : {
1651 1521 : return setHookImpl(cx, argc, vp, OnEnterFrame);
1652 : }
1653 :
1654 : JSBool
1655 27 : Debugger::getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
1656 : {
1657 27 : THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg);
1658 27 : args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
1659 27 : return true;
1660 : }
1661 :
1662 : JSBool
1663 153 : Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
1664 : {
1665 153 : REQUIRE_ARGC("Debugger.set uncaughtExceptionHook", 1);
1666 153 : THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg);
1667 144 : if (!args[0].isNull() && (!args[0].isObject() || !args[0].toObject().isCallable())) {
1668 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_ASSIGN_FUNCTION_OR_NULL,
1669 18 : "uncaughtExceptionHook");
1670 18 : return false;
1671 : }
1672 :
1673 126 : dbg->uncaughtExceptionHook = args[0].toObjectOrNull();
1674 126 : args.rval().setUndefined();
1675 126 : return true;
1676 : }
1677 :
1678 : JSObject *
1679 3105 : Debugger::unwrapDebuggeeArgument(JSContext *cx, const Value &v)
1680 : {
1681 : /*
1682 : * The argument to {add,remove,has}Debuggee may be
1683 : * - a Debugger.Object belonging to this Debugger: return its referent
1684 : * - a cross-compartment wrapper: return the wrapped object
1685 : * - any other non-Debugger.Object object: return it
1686 : * If it is a primitive, or a Debugger.Object that belongs to some other
1687 : * Debugger, throw a TypeError.
1688 : */
1689 3105 : JSObject *obj = NonNullObject(cx, v);
1690 3105 : if (obj) {
1691 2907 : if (obj->getClass() == &DebuggerObject_class) {
1692 180 : Value rv = v;
1693 180 : if (!unwrapDebuggeeValue(cx, &rv))
1694 27 : return NULL;
1695 153 : return &rv.toObject();
1696 : }
1697 2727 : if (IsCrossCompartmentWrapper(obj))
1698 2619 : return &GetProxyPrivate(obj).toObject();
1699 : }
1700 306 : return obj;
1701 : }
1702 :
1703 : JSBool
1704 2034 : Debugger::addDebuggee(JSContext *cx, unsigned argc, Value *vp)
1705 : {
1706 2034 : REQUIRE_ARGC("Debugger.addDebuggee", 1);
1707 2034 : THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg);
1708 2034 : JSObject *referent = dbg->unwrapDebuggeeArgument(cx, args[0]);
1709 2034 : if (!referent)
1710 63 : return false;
1711 1971 : GlobalObject *global = &referent->global();
1712 1971 : if (!dbg->addDebuggeeGlobal(cx, global))
1713 36 : return false;
1714 :
1715 1935 : Value v = ObjectValue(*referent);
1716 1935 : if (!dbg->wrapDebuggeeValue(cx, &v))
1717 0 : return false;
1718 1935 : args.rval() = v;
1719 1935 : return true;
1720 : }
1721 :
1722 : JSBool
1723 225 : Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp)
1724 : {
1725 225 : REQUIRE_ARGC("Debugger.removeDebuggee", 1);
1726 225 : THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);
1727 225 : JSObject *referent = dbg->unwrapDebuggeeArgument(cx, args[0]);
1728 225 : if (!referent)
1729 63 : return false;
1730 162 : GlobalObject *global = &referent->global();
1731 162 : if (dbg->debuggees.has(global))
1732 108 : dbg->removeDebuggeeGlobal(cx->runtime->defaultFreeOp(), global, NULL, NULL);
1733 162 : args.rval().setUndefined();
1734 162 : return true;
1735 : }
1736 :
1737 : JSBool
1738 432 : Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp)
1739 : {
1740 432 : REQUIRE_ARGC("Debugger.hasDebuggee", 1);
1741 432 : THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg);
1742 432 : JSObject *referent = dbg->unwrapDebuggeeArgument(cx, args[0]);
1743 432 : if (!referent)
1744 63 : return false;
1745 369 : args.rval().setBoolean(!!dbg->debuggees.lookup(&referent->global()));
1746 369 : return true;
1747 : }
1748 :
1749 : JSBool
1750 126 : Debugger::getDebuggees(JSContext *cx, unsigned argc, Value *vp)
1751 : {
1752 126 : THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg);
1753 126 : JSObject *arrobj = NewDenseAllocatedArray(cx, dbg->debuggees.count(), NULL);
1754 126 : if (!arrobj)
1755 0 : return false;
1756 126 : arrobj->ensureDenseArrayInitializedLength(cx, 0, dbg->debuggees.count());
1757 126 : unsigned i = 0;
1758 217 : for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
1759 91 : Value v = ObjectValue(*e.front());
1760 91 : if (!dbg->wrapDebuggeeValue(cx, &v))
1761 0 : return false;
1762 91 : arrobj->setDenseArrayElement(i++, v);
1763 : }
1764 126 : args.rval().setObject(*arrobj);
1765 126 : return true;
1766 : }
1767 :
1768 : JSBool
1769 414 : Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp)
1770 : {
1771 414 : THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
1772 :
1773 : /*
1774 : * cx->fp() would return the topmost frame in the current context.
1775 : * Since there may be multiple contexts, use AllFramesIter instead.
1776 : */
1777 1251 : for (AllFramesIter i(cx->stack.space()); !i.done(); ++i) {
1778 1233 : if (dbg->observesFrame(i.fp()))
1779 396 : return dbg->getScriptFrame(cx, i.fp(), vp);
1780 : }
1781 18 : args.rval().setNull();
1782 18 : return true;
1783 : }
1784 :
1785 : JSBool
1786 36 : Debugger::clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp)
1787 : {
1788 36 : THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg);
1789 72 : for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront())
1790 36 : r.front()->compartment()->clearBreakpointsIn(cx, dbg, NULL);
1791 36 : return true;
1792 : }
1793 :
1794 : JSBool
1795 4394 : Debugger::construct(JSContext *cx, unsigned argc, Value *vp)
1796 : {
1797 4394 : CallArgs args = CallArgsFromVp(argc, vp);
1798 :
1799 : /* Check that the arguments, if any, are cross-compartment wrappers. */
1800 8239 : for (unsigned i = 0; i < argc; i++) {
1801 3980 : const Value &arg = args[i];
1802 3980 : if (!arg.isObject())
1803 72 : return ReportObjectRequired(cx);
1804 3908 : JSObject *argobj = &arg.toObject();
1805 3908 : if (!IsCrossCompartmentWrapper(argobj)) {
1806 63 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CCW_REQUIRED, "Debugger");
1807 63 : return false;
1808 : }
1809 : }
1810 :
1811 : /* Get Debugger.prototype. */
1812 : Value v;
1813 4259 : if (!args.callee().getProperty(cx, cx->runtime->atomState.classPrototypeAtom, &v))
1814 0 : return false;
1815 4259 : JSObject *proto = &v.toObject();
1816 4259 : JS_ASSERT(proto->getClass() == &Debugger::jsclass);
1817 :
1818 : /*
1819 : * Make the new Debugger object. Each one has a reference to
1820 : * Debugger.{Frame,Object,Script}.prototype in reserved slots. The rest of
1821 : * the reserved slots are for hooks; they default to undefined.
1822 : */
1823 4259 : JSObject *obj = NewObjectWithGivenProto(cx, &Debugger::jsclass, proto, NULL);
1824 4259 : if (!obj)
1825 0 : return false;
1826 21295 : for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
1827 17036 : obj->setReservedSlot(slot, proto->getReservedSlot(slot));
1828 :
1829 4259 : Debugger *dbg = cx->new_<Debugger>(cx, obj);
1830 4259 : if (!dbg)
1831 0 : return false;
1832 4259 : obj->setPrivate(dbg);
1833 4259 : if (!dbg->init(cx)) {
1834 0 : cx->delete_(dbg);
1835 0 : return false;
1836 : }
1837 :
1838 : /* Add the initial debuggees, if any. */
1839 8086 : for (unsigned i = 0; i < argc; i++) {
1840 3845 : GlobalObject *debuggee = &GetProxyPrivate(&args[i].toObject()).toObject().global();
1841 3845 : if (!dbg->addDebuggeeGlobal(cx, debuggee))
1842 18 : return false;
1843 : }
1844 :
1845 4241 : args.rval().setObject(*obj);
1846 4241 : return true;
1847 : }
1848 :
1849 : bool
1850 5816 : Debugger::addDebuggeeGlobal(JSContext *cx, GlobalObject *global)
1851 : {
1852 5816 : if (debuggees.has(global))
1853 1323 : return true;
1854 :
1855 4493 : JSCompartment *debuggeeCompartment = global->compartment();
1856 :
1857 : /*
1858 : * Check for cycles. If global's compartment is reachable from this
1859 : * Debugger object's compartment by following debuggee-to-debugger links,
1860 : * then adding global would create a cycle. (Typically nobody is debugging
1861 : * the debugger, in which case we zip through this code without looping.)
1862 : */
1863 8986 : Vector<JSCompartment *> visited(cx);
1864 4493 : if (!visited.append(object->compartment()))
1865 0 : return false;
1866 9031 : for (size_t i = 0; i < visited.length(); i++) {
1867 4592 : JSCompartment *c = visited[i];
1868 4592 : if (c == debuggeeCompartment) {
1869 54 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_LOOP);
1870 54 : return false;
1871 : }
1872 :
1873 : /*
1874 : * Find all compartments containing debuggers debugging global objects
1875 : * in c. Add those compartments to visited.
1876 : */
1877 4637 : for (GlobalObjectSet::Range r = c->getDebuggees().all(); !r.empty(); r.popFront()) {
1878 99 : GlobalObject::DebuggerVector *v = r.front()->getDebuggers();
1879 198 : for (Debugger **p = v->begin(); p != v->end(); p++) {
1880 99 : JSCompartment *next = (*p)->object->compartment();
1881 99 : if (Find(visited, next) == visited.end() && !visited.append(next))
1882 0 : return false;
1883 : }
1884 : }
1885 : }
1886 :
1887 : /* Refuse to enable debug mode for a compartment that has running scripts. */
1888 4439 : if (!debuggeeCompartment->debugMode() && debuggeeCompartment->hasScriptsOnStack()) {
1889 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_IDLE);
1890 0 : return false;
1891 : }
1892 :
1893 : /*
1894 : * Each debugger-debuggee relation must be stored in up to three places.
1895 : * JSCompartment::addDebuggee enables debug mode if needed.
1896 : */
1897 8878 : AutoCompartment ac(cx, global);
1898 4439 : if (!ac.enter())
1899 0 : return false;
1900 4439 : GlobalObject::DebuggerVector *v = global->getOrCreateDebuggers(cx);
1901 4439 : if (!v || !v->append(this)) {
1902 0 : js_ReportOutOfMemory(cx);
1903 : } else {
1904 4439 : if (!debuggees.put(global)) {
1905 0 : js_ReportOutOfMemory(cx);
1906 : } else {
1907 4439 : if (global->getDebuggers()->length() > 1)
1908 1086 : return true;
1909 3353 : if (debuggeeCompartment->addDebuggee(cx, global))
1910 3353 : return true;
1911 :
1912 : /* Maintain consistency on error. */
1913 0 : debuggees.remove(global);
1914 : }
1915 0 : JS_ASSERT(v->back() == this);
1916 0 : v->popBack();
1917 : }
1918 0 : return false;
1919 : }
1920 :
1921 : void
1922 4439 : Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
1923 : GlobalObjectSet::Enum *compartmentEnum,
1924 : GlobalObjectSet::Enum *debugEnum)
1925 : {
1926 : /*
1927 : * Each debuggee is in two HashSets: one for its compartment and one for
1928 : * its debugger (this). The caller might be enumerating either set; if so,
1929 : * use HashSet::Enum::removeFront rather than HashSet::remove below, to
1930 : * avoid invalidating the live enumerator.
1931 : */
1932 4439 : JS_ASSERT(global->compartment()->getDebuggees().has(global));
1933 4439 : JS_ASSERT_IF(compartmentEnum, compartmentEnum->front() == global);
1934 4439 : JS_ASSERT(debuggees.has(global));
1935 4439 : JS_ASSERT_IF(debugEnum, debugEnum->front() == global);
1936 :
1937 : /*
1938 : * FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame
1939 : * objects referring to a particular js::StackFrame. This is hard if
1940 : * Debugger objects that are no longer debugging the relevant global might
1941 : * have live Frame objects. So we take the easy way out and kill them here.
1942 : * This is a bug, since it's observable and contrary to the spec. One
1943 : * possible fix would be to put such objects into a compartment-wide bag
1944 : * which slowPathOnLeaveFrame would have to examine.
1945 : */
1946 4484 : for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
1947 45 : StackFrame *fp = e.front().key;
1948 45 : if (&fp->scopeChain().global() == global) {
1949 36 : e.front().value->setPrivate(NULL);
1950 36 : e.removeFront();
1951 : }
1952 : }
1953 :
1954 4439 : GlobalObject::DebuggerVector *v = global->getDebuggers();
1955 : Debugger **p;
1956 4508 : for (p = v->begin(); p != v->end(); p++) {
1957 4508 : if (*p == this)
1958 4439 : break;
1959 : }
1960 4439 : JS_ASSERT(p != v->end());
1961 :
1962 : /*
1963 : * The relation must be removed from up to three places: *v and debuggees
1964 : * for sure, and possibly the compartment's debuggee set.
1965 : */
1966 4439 : v->erase(p);
1967 4439 : if (v->empty())
1968 3353 : global->compartment()->removeDebuggee(fop, global, compartmentEnum);
1969 4439 : if (debugEnum)
1970 4241 : debugEnum->removeFront();
1971 : else
1972 198 : debuggees.remove(global);
1973 4439 : }
1974 :
1975 : /*
1976 : * A class for parsing 'findScripts' query arguments and searching for
1977 : * scripts that match the criteria they represent.
1978 : */
1979 1053 : class Debugger::ScriptQuery {
1980 : public:
1981 : /* Construct a ScriptQuery to use matching scripts for |dbg|. */
1982 1053 : ScriptQuery(JSContext *cx, Debugger *dbg):
1983 1053 : cx(cx), debugger(dbg), compartments(cx), innermostForGlobal(cx) {}
1984 :
1985 : /*
1986 : * Initialize this ScriptQuery. Raise an error and return false if we
1987 : * haven't enough memory.
1988 : */
1989 1053 : bool init() {
1990 3159 : if (!globals.init() ||
1991 1053 : !compartments.init() ||
1992 1053 : !innermostForGlobal.init())
1993 : {
1994 0 : js_ReportOutOfMemory(cx);
1995 0 : return false;
1996 : }
1997 :
1998 1053 : return true;
1999 : }
2000 :
2001 : /*
2002 : * Parse the query object |query|, and prepare to match only the scripts
2003 : * it specifies.
2004 : */
2005 972 : bool parseQuery(JSObject *query) {
2006 : /*
2007 : * Check for a 'global' property, which limits the results to those
2008 : * scripts scoped to a particular global object.
2009 : */
2010 : Value global;
2011 972 : if (!query->getProperty(cx, cx->runtime->atomState.globalAtom, &global))
2012 0 : return false;
2013 972 : if (global.isUndefined()) {
2014 558 : matchAllDebuggeeGlobals();
2015 : } else {
2016 414 : JSObject *referent = debugger->unwrapDebuggeeArgument(cx, global);
2017 414 : if (!referent)
2018 36 : return false;
2019 378 : GlobalObject *globalObject = &referent->global();
2020 :
2021 : /*
2022 : * If the given global isn't a debuggee, just leave the set of
2023 : * acceptable globals empty; we'll return no scripts.
2024 : */
2025 378 : if (debugger->debuggees.has(globalObject)) {
2026 342 : if (!matchSingleGlobal(globalObject))
2027 0 : return false;
2028 : }
2029 : }
2030 :
2031 : /* Check for a 'url' property. */
2032 936 : if (!query->getProperty(cx, cx->runtime->atomState.urlAtom, &url))
2033 0 : return false;
2034 936 : if (!url.isUndefined() && !url.isString()) {
2035 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
2036 36 : "query object's 'url' property", "neither undefined nor a string");
2037 36 : return false;
2038 : }
2039 :
2040 : /* Check for a 'line' property. */
2041 : Value lineProperty;
2042 900 : if (!query->getProperty(cx, cx->runtime->atomState.lineAtom, &lineProperty))
2043 0 : return false;
2044 900 : if (lineProperty.isUndefined()) {
2045 270 : hasLine = false;
2046 630 : } else if (lineProperty.isNumber()) {
2047 594 : if (url.isUndefined()) {
2048 18 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_QUERY_LINE_WITHOUT_URL);
2049 18 : return false;
2050 : }
2051 576 : double doubleLine = lineProperty.toNumber();
2052 576 : if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) {
2053 27 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_LINE);
2054 27 : return false;
2055 : }
2056 549 : hasLine = true;
2057 549 : line = doubleLine;
2058 : } else {
2059 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
2060 : "query object's 'line' property",
2061 36 : "neither undefined nor an integer");
2062 36 : return false;
2063 : }
2064 :
2065 : /* Check for an 'innermost' property. */
2066 : Value innermostProperty;
2067 819 : if (!query->getProperty(cx, cx->runtime->atomState.innermostAtom, &innermostProperty))
2068 0 : return false;
2069 819 : innermost = js_ValueToBoolean(innermostProperty);
2070 819 : if (innermost) {
2071 : /* Technically, we need only check hasLine, but this is clearer. */
2072 252 : if (url.isUndefined() || !hasLine) {
2073 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2074 18 : JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL);
2075 18 : return false;
2076 : }
2077 : }
2078 :
2079 801 : return true;
2080 : }
2081 :
2082 : /* Set up this ScriptQuery appropriately for a missing query argument. */
2083 81 : bool omittedQuery() {
2084 81 : url.setUndefined();
2085 81 : hasLine = false;
2086 81 : innermost = false;
2087 81 : return matchAllDebuggeeGlobals();
2088 : }
2089 :
2090 : /*
2091 : * Search all relevant compartments and the stack for scripts matching
2092 : * this query, and append the matching scripts to |vector|.
2093 : */
2094 882 : bool findScripts(AutoScriptVector *vector) {
2095 882 : if (!prepareQuery())
2096 0 : return false;
2097 :
2098 : /* Search each compartment for debuggee scripts. */
2099 1989 : for (CompartmentSet::Range r = compartments.all(); !r.empty(); r.popFront()) {
2100 8019 : for (gc::CellIter i(r.front(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
2101 6912 : JSScript *script = i.get<JSScript>();
2102 6912 : GlobalObject *global = script->getGlobalObjectOrNull();
2103 6912 : if (global && !consider(script, global, vector))
2104 0 : return false;
2105 : }
2106 : }
2107 :
2108 : /*
2109 : * Since eval scripts have no global, we need to find them via the call
2110 : * stack, where frame's scope tells us the global in use.
2111 : */
2112 2223 : for (FrameRegsIter fri(cx); !fri.done(); ++fri) {
2113 1341 : if (fri.fp()->isEvalFrame()) {
2114 27 : JSScript *script = fri.fp()->script();
2115 :
2116 : /*
2117 : * If eval scripts never have global objects set, then we don't need
2118 : * to check the existing script vector for duplicates, since we only
2119 : * include scripts with globals above.
2120 : */
2121 27 : JS_ASSERT(!script->getGlobalObjectOrNull());
2122 :
2123 27 : GlobalObject *global = &fri.fp()->scopeChain().global();
2124 27 : if (!consider(script, global, vector))
2125 0 : return false;
2126 : }
2127 : }
2128 :
2129 : /*
2130 : * For most queries, we just accumulate results in 'vector' as we find
2131 : * them. But if this is an 'innermost' query, then we've accumulated the
2132 : * results in the 'innermostForGlobal' map. In that case, we now need to
2133 : * walk that map and populate 'vector'.
2134 : */
2135 882 : if (innermost) {
2136 477 : for (GlobalToScriptMap::Range r = innermostForGlobal.all(); !r.empty(); r.popFront()) {
2137 243 : if (!vector->append(r.front().value)) {
2138 0 : js_ReportOutOfMemory(cx);
2139 0 : return false;
2140 : }
2141 : }
2142 : }
2143 :
2144 882 : return true;
2145 : }
2146 :
2147 : private:
2148 : /* The context in which we should do our work. */
2149 : JSContext *cx;
2150 :
2151 : /* The debugger for which we conduct queries. */
2152 : Debugger *debugger;
2153 :
2154 : /* A script must run in one of these globals to match the query. */
2155 : GlobalObjectSet globals;
2156 :
2157 : typedef HashSet<JSCompartment *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy>
2158 : CompartmentSet;
2159 :
2160 : /* The smallest set of compartments that contains all globals in globals. */
2161 : CompartmentSet compartments;
2162 :
2163 : /* If this is a string, matching scripts have urls equal to it. */
2164 : Value url;
2165 :
2166 : /* url as a C string. */
2167 : JSAutoByteString urlCString;
2168 :
2169 : /* True if the query contained a 'line' property. */
2170 : bool hasLine;
2171 :
2172 : /* The line matching scripts must cover. */
2173 : unsigned int line;
2174 :
2175 : /* True if the query has an 'innermost' property whose value is true. */
2176 : bool innermost;
2177 :
2178 : typedef HashMap<GlobalObject *, JSScript *, DefaultHasher<GlobalObject *>, RuntimeAllocPolicy>
2179 : GlobalToScriptMap;
2180 :
2181 : /*
2182 : * For 'innermost' queries, a map from global objects to the innermost
2183 : * script we've seen so far in that global. (Instantiation code size
2184 : * explosion ho!)
2185 : */
2186 : GlobalToScriptMap innermostForGlobal;
2187 :
2188 : /* Arrange for this ScriptQuery to match only scripts that run in |global|. */
2189 342 : bool matchSingleGlobal(GlobalObject *global) {
2190 342 : JS_ASSERT(globals.count() == 0);
2191 342 : if (!globals.put(global)) {
2192 0 : js_ReportOutOfMemory(cx);
2193 0 : return false;
2194 : }
2195 342 : return true;
2196 : }
2197 :
2198 : /*
2199 : * Arrange for this ScriptQuery to match all scripts running in debuggee
2200 : * globals.
2201 : */
2202 639 : bool matchAllDebuggeeGlobals() {
2203 639 : JS_ASSERT(globals.count() == 0);
2204 : /* Copy the debugger's set of debuggee globals to our global set. */
2205 1404 : for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
2206 765 : if (!globals.put(r.front())) {
2207 0 : js_ReportOutOfMemory(cx);
2208 0 : return false;
2209 : }
2210 : }
2211 639 : return true;
2212 : }
2213 :
2214 : /*
2215 : * Given that parseQuery or omittedQuery has been called, prepare to
2216 : * match scripts. Set urlCString as appropriate.
2217 : */
2218 882 : bool prepareQuery() {
2219 : /*
2220 : * Compute the proper value for |compartments|, given the present
2221 : * value of |globals|.
2222 : */
2223 1989 : for (GlobalObjectSet::Range r = globals.all(); !r.empty(); r.popFront()) {
2224 1107 : if (!compartments.put(r.front()->compartment())) {
2225 0 : js_ReportOutOfMemory(cx);
2226 0 : return false;
2227 : }
2228 : }
2229 :
2230 : /* Compute urlCString, if a url was given. */
2231 882 : if (url.isString()) {
2232 684 : if (!urlCString.encode(cx, url.toString()))
2233 0 : return false;
2234 : }
2235 :
2236 882 : return true;
2237 : }
2238 :
2239 : /*
2240 : * If |script|, a script in |global|, matches this query, append it to
2241 : * |vector| or place it in |innermostForGlobal|, as appropriate. Return true
2242 : * if no error occurs, false if an error occurs.
2243 : */
2244 5409 : bool consider(JSScript *script, GlobalObject *global, AutoScriptVector *vector) {
2245 5409 : if (!globals.has(global))
2246 0 : return true;
2247 5409 : if (urlCString.ptr()) {
2248 4860 : if (!script->filename || strcmp(script->filename, urlCString.ptr()) != 0)
2249 2232 : return true;
2250 : }
2251 3177 : if (hasLine) {
2252 2268 : if (line < script->lineno || script->lineno + js_GetScriptLineExtent(script) < line)
2253 963 : return true;
2254 : }
2255 :
2256 2214 : if (innermost) {
2257 : /*
2258 : * For 'innermost' queries, we don't place scripts in |vector| right
2259 : * away; we may later find another script that is nested inside this
2260 : * one. Instead, we record the innermost script we've found so far
2261 : * for each global in innermostForGlobal, and only populate |vector|
2262 : * at the bottom of findScripts, when we've traversed all the
2263 : * scripts.
2264 : *
2265 : * So: check this script against the innermost one we've found so
2266 : * far (if any), as recorded in innermostForGlobal, and replace that
2267 : * if it's better.
2268 : */
2269 1170 : GlobalToScriptMap::AddPtr p = innermostForGlobal.lookupForAdd(global);
2270 585 : if (p) {
2271 : /* Is our newly found script deeper than the last one we found? */
2272 342 : JSScript *incumbent = p->value;
2273 342 : if (script->staticLevel > incumbent->staticLevel)
2274 0 : p->value = script;
2275 : } else {
2276 : /*
2277 : * This is the first matching script we've encountered for this
2278 : * global, so it is thus the innermost such script.
2279 : */
2280 243 : if (!innermostForGlobal.add(p, global, script)) {
2281 0 : js_ReportOutOfMemory(cx);
2282 0 : return false;
2283 : }
2284 : }
2285 : } else {
2286 : /* Record this matching script in the results vector. */
2287 1629 : if (!vector->append(script)) {
2288 0 : js_ReportOutOfMemory(cx);
2289 0 : return false;
2290 : }
2291 : }
2292 :
2293 2214 : return true;
2294 : }
2295 : };
2296 :
2297 : JSBool
2298 1053 : Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp)
2299 : {
2300 1053 : THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg);
2301 :
2302 2106 : ScriptQuery query(cx, dbg);
2303 1053 : if (!query.init())
2304 0 : return false;
2305 :
2306 1053 : if (argc >= 1) {
2307 972 : JSObject *queryObject = NonNullObject(cx, args[0]);
2308 972 : if (!queryObject || !query.parseQuery(queryObject))
2309 171 : return false;
2310 : } else {
2311 81 : if (!query.omittedQuery())
2312 0 : return false;
2313 : }
2314 :
2315 : /*
2316 : * Accumulate the scripts in an AutoScriptVector, instead of creating
2317 : * the JS array as we go, because we mustn't allocate JS objects or GC
2318 : * while we use the CellIter.
2319 : */
2320 1764 : AutoScriptVector scripts(cx);
2321 :
2322 882 : if (!query.findScripts(&scripts))
2323 0 : return false;
2324 :
2325 882 : JSObject *result = NewDenseAllocatedArray(cx, scripts.length(), NULL);
2326 882 : if (!result)
2327 0 : return false;
2328 :
2329 882 : result->ensureDenseArrayInitializedLength(cx, 0, scripts.length());
2330 :
2331 2754 : for (size_t i = 0; i < scripts.length(); i++) {
2332 1872 : JSObject *scriptObject = dbg->wrapScript(cx, scripts[i]);
2333 1872 : if (!scriptObject)
2334 0 : return false;
2335 1872 : result->setDenseArrayElement(i, ObjectValue(*scriptObject));
2336 : }
2337 :
2338 882 : args.rval().setObject(*result);
2339 882 : return true;
2340 : }
2341 :
2342 : JSPropertySpec Debugger::properties[] = {
2343 : JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0),
2344 : JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement,
2345 : Debugger::setOnDebuggerStatement, 0),
2346 : JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind,
2347 : Debugger::setOnExceptionUnwind, 0),
2348 : JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript, 0),
2349 : JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0),
2350 : JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
2351 : Debugger::setUncaughtExceptionHook, 0),
2352 : JS_PS_END
2353 : };
2354 :
2355 : JSFunctionSpec Debugger::methods[] = {
2356 : JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0),
2357 : JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
2358 : JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
2359 : JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0),
2360 : JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
2361 : JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 1, 0),
2362 : JS_FN("findScripts", Debugger::findScripts, 1, 0),
2363 : JS_FS_END
2364 : };
2365 :
2366 :
2367 : /*** Debugger.Script *****************************************************************************/
2368 :
2369 : static inline JSScript *
2370 64815 : GetScriptReferent(JSObject *obj)
2371 : {
2372 64815 : JS_ASSERT(obj->getClass() == &DebuggerScript_class);
2373 64815 : return static_cast<JSScript *>(obj->getPrivate());
2374 : }
2375 :
2376 : static inline void
2377 : SetScriptReferent(JSObject *obj, JSScript *script)
2378 : {
2379 : JS_ASSERT(obj->getClass() == &DebuggerScript_class);
2380 : obj->setPrivate(script);
2381 : }
2382 :
2383 : static void
2384 46876 : DebuggerScript_trace(JSTracer *trc, JSObject *obj)
2385 : {
2386 : /* This comes from a private pointer, so no barrier needed. */
2387 46876 : if (JSScript *script = GetScriptReferent(obj)) {
2388 1152 : MarkCrossCompartmentScriptUnbarriered(trc, &script, "Debugger.Script referent");
2389 1152 : obj->setPrivateUnbarriered(script);
2390 : }
2391 46876 : }
2392 :
2393 : Class DebuggerScript_class = {
2394 : "Script",
2395 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
2396 : JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
2397 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
2398 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
2399 : NULL, /* checkAccess */
2400 : NULL, /* call */
2401 : NULL, /* construct */
2402 : NULL, /* hasInstance */
2403 : DebuggerScript_trace
2404 : };
2405 :
2406 : JSObject *
2407 2909 : Debugger::newDebuggerScript(JSContext *cx, JSScript *script)
2408 : {
2409 2909 : assertSameCompartment(cx, object.get());
2410 :
2411 2909 : JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject();
2412 2909 : JS_ASSERT(proto);
2413 2909 : JSObject *scriptobj = NewObjectWithGivenProto(cx, &DebuggerScript_class, proto, NULL);
2414 2909 : if (!scriptobj)
2415 0 : return NULL;
2416 2909 : scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object));
2417 2909 : scriptobj->setPrivate(script);
2418 :
2419 2909 : return scriptobj;
2420 : }
2421 :
2422 : JSObject *
2423 7085 : Debugger::wrapScript(JSContext *cx, JSScript *script)
2424 : {
2425 7085 : assertSameCompartment(cx, object.get());
2426 7085 : JS_ASSERT(cx->compartment != script->compartment());
2427 14170 : ScriptWeakMap::AddPtr p = scripts.lookupForAdd(script);
2428 7085 : if (!p) {
2429 2909 : JSObject *scriptobj = newDebuggerScript(cx, script);
2430 :
2431 : /* The allocation may have caused a GC, which can remove table entries. */
2432 2909 : if (!scriptobj || !scripts.relookupOrAdd(p, script, scriptobj))
2433 0 : return NULL;
2434 : }
2435 :
2436 7085 : JS_ASSERT(GetScriptReferent(p->value) == script);
2437 7085 : return p->value;
2438 : }
2439 :
2440 : static JSObject *
2441 5427 : DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const char *fnname)
2442 : {
2443 5427 : if (!v.isObject()) {
2444 0 : ReportObjectRequired(cx);
2445 0 : return NULL;
2446 : }
2447 5427 : JSObject *thisobj = &v.toObject();
2448 5427 : if (thisobj->getClass() != &DebuggerScript_class) {
2449 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
2450 0 : clsname, fnname, thisobj->getClass()->name);
2451 0 : return NULL;
2452 : }
2453 :
2454 : /*
2455 : * Check for Debugger.Script.prototype, which is of class DebuggerScript_class
2456 : * but whose script is null.
2457 : */
2458 5427 : if (!GetScriptReferent(thisobj)) {
2459 0 : JS_ASSERT(!GetScriptReferent(thisobj));
2460 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
2461 0 : clsname, fnname, "prototype object");
2462 0 : return NULL;
2463 : }
2464 :
2465 5427 : return thisobj;
2466 : }
2467 :
2468 : static JSObject *
2469 5427 : DebuggerScript_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
2470 : {
2471 5427 : return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname);
2472 : }
2473 :
2474 : #define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script) \
2475 : CallArgs args = CallArgsFromVp(argc, vp); \
2476 : JSObject *obj = DebuggerScript_checkThis(cx, args, fnname); \
2477 : if (!obj) \
2478 : return false; \
2479 : JSScript *script = GetScriptReferent(obj)
2480 :
2481 : static JSBool
2482 54 : DebuggerScript_getUrl(JSContext *cx, unsigned argc, Value *vp)
2483 : {
2484 54 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getUrl", args, obj, script);
2485 :
2486 54 : JSString *str = js_NewStringCopyZ(cx, script->filename);
2487 54 : if (!str)
2488 0 : return false;
2489 54 : args.rval().setString(str);
2490 54 : return true;
2491 : }
2492 :
2493 : static JSBool
2494 54 : DebuggerScript_getStartLine(JSContext *cx, unsigned argc, Value *vp)
2495 : {
2496 54 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getStartLine", args, obj, script);
2497 54 : args.rval().setNumber(script->lineno);
2498 54 : return true;
2499 : }
2500 :
2501 : static JSBool
2502 1035 : DebuggerScript_getLineCount(JSContext *cx, unsigned argc, Value *vp)
2503 : {
2504 1035 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineCount", args, obj, script);
2505 :
2506 1035 : unsigned maxLine = js_GetScriptLineExtent(script);
2507 1035 : args.rval().setNumber(double(maxLine));
2508 1035 : return true;
2509 : }
2510 :
2511 : static JSBool
2512 207 : DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp)
2513 : {
2514 207 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script);
2515 207 : Debugger *dbg = Debugger::fromChildJSObject(obj);
2516 :
2517 207 : JSObject *result = NewDenseEmptyArray(cx);
2518 207 : if (!result)
2519 0 : return false;
2520 207 : if (JSScript::isValidOffset(script->objectsOffset)) {
2521 : /*
2522 : * script->savedCallerFun indicates that this is a direct eval script
2523 : * and the calling function is stored as script->objects()->vector[0].
2524 : * It is not really a child script of this script, so skip it.
2525 : */
2526 117 : JSObjectArray *objects = script->objects();
2527 270 : for (uint32_t i = script->savedCallerFun ? 1 : 0; i < objects->length; i++) {
2528 153 : JSObject *obj = objects->vector[i];
2529 153 : if (obj->isFunction()) {
2530 135 : JSFunction *fun = static_cast<JSFunction *>(obj);
2531 135 : JSObject *s = dbg->wrapScript(cx, fun->script());
2532 135 : if (!s || !js_NewbornArrayPush(cx, result, ObjectValue(*s)))
2533 0 : return false;
2534 : }
2535 : }
2536 : }
2537 207 : args.rval().setObject(*result);
2538 207 : return true;
2539 : }
2540 :
2541 : static bool
2542 2817 : ScriptOffset(JSContext *cx, JSScript *script, const Value &v, size_t *offsetp)
2543 : {
2544 : double d;
2545 : size_t off;
2546 :
2547 2817 : bool ok = v.isNumber();
2548 2817 : if (ok) {
2549 2772 : d = v.toNumber();
2550 2772 : off = size_t(d);
2551 : }
2552 2817 : if (!ok || off != d || !IsValidBytecodeOffset(cx, script, off)) {
2553 117 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_OFFSET);
2554 117 : return false;
2555 : }
2556 2700 : *offsetp = off;
2557 2700 : return true;
2558 : }
2559 :
2560 : static JSBool
2561 1521 : DebuggerScript_getOffsetLine(JSContext *cx, unsigned argc, Value *vp)
2562 : {
2563 1521 : REQUIRE_ARGC("Debugger.Script.getOffsetLine", 1);
2564 1512 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script);
2565 : size_t offset;
2566 1512 : if (!ScriptOffset(cx, script, args[0], &offset))
2567 108 : return false;
2568 1404 : unsigned lineno = JS_PCToLineNumber(cx, script, script->code + offset);
2569 1404 : args.rval().setNumber(lineno);
2570 1404 : return true;
2571 : }
2572 :
2573 : class BytecodeRangeWithLineNumbers : private BytecodeRange
2574 : {
2575 : public:
2576 : using BytecodeRange::empty;
2577 : using BytecodeRange::frontPC;
2578 : using BytecodeRange::frontOpcode;
2579 : using BytecodeRange::frontOffset;
2580 :
2581 1962 : BytecodeRangeWithLineNumbers(JSScript *script)
2582 1962 : : BytecodeRange(script), lineno(script->lineno), sn(script->notes()), snpc(script->code)
2583 : {
2584 1962 : if (!SN_IS_TERMINATOR(sn))
2585 1944 : snpc += SN_DELTA(sn);
2586 1962 : updateLine();
2587 1962 : }
2588 :
2589 5939568 : void popFront() {
2590 5939568 : BytecodeRange::popFront();
2591 5939568 : if (!empty())
2592 5937606 : updateLine();
2593 5939568 : }
2594 :
2595 5939568 : size_t frontLineNumber() const { return lineno; }
2596 :
2597 : private:
2598 5939568 : void updateLine() {
2599 : /*
2600 : * Determine the current line number by reading all source notes up to
2601 : * and including the current offset.
2602 : */
2603 12179376 : while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) {
2604 300240 : SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
2605 300240 : if (type == SRC_SETLINE)
2606 1476 : lineno = size_t(js_GetSrcNoteOffset(sn, 0));
2607 298764 : else if (type == SRC_NEWLINE)
2608 6066 : lineno++;
2609 :
2610 300240 : sn = SN_NEXT(sn);
2611 300240 : snpc += SN_DELTA(sn);
2612 : }
2613 5939568 : }
2614 :
2615 : size_t lineno;
2616 : jssrcnote *sn;
2617 : jsbytecode *snpc;
2618 : };
2619 :
2620 : static const size_t NoEdges = -1;
2621 : static const size_t MultipleEdges = -2;
2622 :
2623 : /*
2624 : * FlowGraphSummary::populate(cx, script) computes a summary of script's
2625 : * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}.
2626 : *
2627 : * jumpData[offset] is:
2628 : * - NoEdges if offset isn't the offset of an instruction, or if the
2629 : * instruction is apparently unreachable;
2630 : * - MultipleEdges if you can arrive at that instruction from
2631 : * instructions on multiple different lines OR it's the first
2632 : * instruction of the script;
2633 : * - otherwise, the (unique) line number of all instructions that can
2634 : * precede the instruction at offset.
2635 : *
2636 : * The generated graph does not contain edges for JSOP_RETSUB, which appears at
2637 : * the end of finally blocks. The algorithm that uses this information works
2638 : * anyway, because in non-exception cases, JSOP_RETSUB always returns to a
2639 : * !FlowsIntoNext instruction (JSOP_GOTO/GOTOX or JSOP_RETRVAL) which generates
2640 : * an edge if needed.
2641 : */
2642 981 : class FlowGraphSummary : public Vector<size_t> {
2643 : public:
2644 : typedef Vector<size_t> Base;
2645 981 : FlowGraphSummary(JSContext *cx) : Base(cx) {}
2646 :
2647 2970153 : void addEdge(size_t sourceLine, size_t targetOffset) {
2648 2970153 : FlowGraphSummary &self = *this;
2649 2970153 : if (self[targetOffset] == NoEdges)
2650 2968398 : self[targetOffset] = sourceLine;
2651 1755 : else if (self[targetOffset] != sourceLine)
2652 1305 : self[targetOffset] = MultipleEdges;
2653 2970153 : }
2654 :
2655 : void addEdgeFromAnywhere(size_t targetOffset) {
2656 : (*this)[targetOffset] = MultipleEdges;
2657 : }
2658 :
2659 981 : bool populate(JSContext *cx, JSScript *script) {
2660 981 : if (!growBy(script->length))
2661 0 : return false;
2662 981 : FlowGraphSummary &self = *this;
2663 981 : self[0] = MultipleEdges;
2664 8910576 : for (size_t i = 1; i < script->length; i++)
2665 8909595 : self[i] = NoEdges;
2666 :
2667 981 : size_t prevLine = script->lineno;
2668 981 : JSOp prevOp = JSOP_NOP;
2669 2970765 : for (BytecodeRangeWithLineNumbers r(script); !r.empty(); r.popFront()) {
2670 2969784 : size_t lineno = r.frontLineNumber();
2671 2969784 : JSOp op = r.frontOpcode();
2672 :
2673 2969784 : if (FlowsIntoNext(prevOp))
2674 2968749 : addEdge(prevLine, r.frontOffset());
2675 :
2676 2969784 : if (js_CodeSpec[op].type() == JOF_JUMP) {
2677 1062 : addEdge(lineno, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
2678 2968722 : } else if (op == JSOP_TABLESWITCH || op == JSOP_LOOKUPSWITCH) {
2679 99 : jsbytecode *pc = r.frontPC();
2680 99 : size_t offset = r.frontOffset();
2681 99 : ptrdiff_t step = JUMP_OFFSET_LEN;
2682 99 : size_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
2683 99 : pc += step;
2684 99 : addEdge(lineno, defaultOffset);
2685 :
2686 : int ncases;
2687 99 : if (op == JSOP_TABLESWITCH) {
2688 54 : int32_t low = GET_JUMP_OFFSET(pc);
2689 54 : pc += JUMP_OFFSET_LEN;
2690 54 : ncases = GET_JUMP_OFFSET(pc) - low + 1;
2691 54 : pc += JUMP_OFFSET_LEN;
2692 : } else {
2693 45 : ncases = GET_UINT16(pc);
2694 45 : pc += UINT16_LEN;
2695 45 : JS_ASSERT(ncases > 0);
2696 : }
2697 :
2698 342 : for (int i = 0; i < ncases; i++) {
2699 243 : if (op == JSOP_LOOKUPSWITCH)
2700 108 : pc += UINT32_INDEX_LEN;
2701 243 : size_t target = offset + GET_JUMP_OFFSET(pc);
2702 243 : addEdge(lineno, target);
2703 243 : pc += step;
2704 : }
2705 : }
2706 :
2707 2969784 : prevOp = op;
2708 2969784 : prevLine = lineno;
2709 : }
2710 :
2711 981 : return true;
2712 : }
2713 : };
2714 :
2715 : static JSBool
2716 54 : DebuggerScript_getAllOffsets(JSContext *cx, unsigned argc, Value *vp)
2717 : {
2718 54 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
2719 :
2720 : /*
2721 : * First pass: determine which offsets in this script are jump targets and
2722 : * which line numbers jump to them.
2723 : */
2724 108 : FlowGraphSummary flowData(cx);
2725 54 : if (!flowData.populate(cx, script))
2726 0 : return false;
2727 :
2728 : /* Second pass: build the result array. */
2729 54 : JSObject *result = NewDenseEmptyArray(cx);
2730 54 : if (!result)
2731 0 : return false;
2732 1458 : for (BytecodeRangeWithLineNumbers r(script); !r.empty(); r.popFront()) {
2733 1404 : size_t offset = r.frontOffset();
2734 1404 : size_t lineno = r.frontLineNumber();
2735 :
2736 : /* Make a note, if the current instruction is an entry point for the current line. */
2737 1404 : if (flowData[offset] != NoEdges && flowData[offset] != lineno) {
2738 : /* Get the offsets array for this line. */
2739 : JSObject *offsets;
2740 : Value offsetsv;
2741 360 : if (!result->arrayGetOwnDataElement(cx, lineno, &offsetsv))
2742 0 : return false;
2743 :
2744 : jsid id;
2745 360 : if (offsetsv.isObject()) {
2746 54 : offsets = &offsetsv.toObject();
2747 : } else {
2748 306 : JS_ASSERT(offsetsv.isMagic(JS_ARRAY_HOLE));
2749 :
2750 : /*
2751 : * Create an empty offsets array for this line.
2752 : * Store it in the result array.
2753 : */
2754 306 : offsets = NewDenseEmptyArray(cx);
2755 918 : if (!offsets ||
2756 306 : !ValueToId(cx, NumberValue(lineno), &id) ||
2757 306 : !result->defineGeneric(cx, id, ObjectValue(*offsets)))
2758 : {
2759 0 : return false;
2760 : }
2761 : }
2762 :
2763 : /* Append the current offset to the offsets array. */
2764 360 : if (!js_NewbornArrayPush(cx, offsets, NumberValue(offset)))
2765 0 : return false;
2766 : }
2767 : }
2768 :
2769 54 : args.rval().setObject(*result);
2770 54 : return true;
2771 : }
2772 :
2773 : static JSBool
2774 927 : DebuggerScript_getLineOffsets(JSContext *cx, unsigned argc, Value *vp)
2775 : {
2776 927 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script);
2777 927 : REQUIRE_ARGC("Debugger.Script.getLineOffsets", 1);
2778 :
2779 : /* Parse lineno argument. */
2780 : size_t lineno;
2781 927 : bool ok = false;
2782 927 : if (args[0].isNumber()) {
2783 927 : double d = args[0].toNumber();
2784 927 : lineno = size_t(d);
2785 927 : ok = (lineno == d);
2786 : }
2787 927 : if (!ok) {
2788 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_LINE);
2789 0 : return false;
2790 : }
2791 :
2792 : /*
2793 : * First pass: determine which offsets in this script are jump targets and
2794 : * which line numbers jump to them.
2795 : */
2796 1854 : FlowGraphSummary flowData(cx);
2797 927 : if (!flowData.populate(cx, script))
2798 0 : return false;
2799 :
2800 : /* Second pass: build the result array. */
2801 927 : JSObject *result = NewDenseEmptyArray(cx);
2802 927 : if (!result)
2803 0 : return false;
2804 2969307 : for (BytecodeRangeWithLineNumbers r(script); !r.empty(); r.popFront()) {
2805 2968380 : size_t offset = r.frontOffset();
2806 :
2807 : /* If the op at offset is an entry point, append offset to result. */
2808 2983428 : if (r.frontLineNumber() == lineno &&
2809 7641 : flowData[offset] != NoEdges &&
2810 7407 : flowData[offset] != lineno)
2811 : {
2812 999 : if (!js_NewbornArrayPush(cx, result, NumberValue(offset)))
2813 0 : return false;
2814 : }
2815 : }
2816 :
2817 927 : args.rval().setObject(*result);
2818 927 : return true;
2819 : }
2820 :
2821 : static JSBool
2822 1296 : DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp)
2823 : {
2824 1296 : REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2);
2825 1296 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
2826 1296 : Debugger *dbg = Debugger::fromChildJSObject(obj);
2827 :
2828 1296 : GlobalObject *scriptGlobal = script->getGlobalObjectOrNull();
2829 1296 : if (!dbg->observesGlobal(ScriptGlobal(cx, script, scriptGlobal))) {
2830 18 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_DEBUGGING);
2831 18 : return false;
2832 : }
2833 :
2834 : size_t offset;
2835 1278 : if (!ScriptOffset(cx, script, args[0], &offset))
2836 9 : return false;
2837 :
2838 1269 : JSObject *handler = NonNullObject(cx, args[1]);
2839 1269 : if (!handler)
2840 0 : return false;
2841 :
2842 1269 : jsbytecode *pc = script->code + offset;
2843 1269 : BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc, scriptGlobal);
2844 1269 : if (!site)
2845 0 : return false;
2846 1269 : site->inc(cx->runtime->defaultFreeOp());
2847 1269 : if (cx->runtime->new_<Breakpoint>(dbg, site, handler)) {
2848 1269 : args.rval().setUndefined();
2849 1269 : return true;
2850 : }
2851 0 : site->dec(cx->runtime->defaultFreeOp());
2852 0 : site->destroyIfEmpty(cx->runtime->defaultFreeOp());
2853 0 : return false;
2854 : }
2855 :
2856 : static JSBool
2857 180 : DebuggerScript_getBreakpoints(JSContext *cx, unsigned argc, Value *vp)
2858 : {
2859 180 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
2860 180 : Debugger *dbg = Debugger::fromChildJSObject(obj);
2861 :
2862 : jsbytecode *pc;
2863 180 : if (argc > 0) {
2864 : size_t offset;
2865 27 : if (!ScriptOffset(cx, script, args[0], &offset))
2866 0 : return false;
2867 27 : pc = script->code + offset;
2868 : } else {
2869 153 : pc = NULL;
2870 : }
2871 :
2872 180 : JSObject *arr = NewDenseEmptyArray(cx);
2873 180 : if (!arr)
2874 0 : return false;
2875 :
2876 3600 : for (unsigned i = 0; i < script->length; i++) {
2877 3420 : BreakpointSite *site = script->getBreakpointSite(script->code + i);
2878 3420 : if (site && (!pc || site->pc == pc)) {
2879 540 : for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
2880 540 : if (bp->debugger == dbg &&
2881 162 : !js_NewbornArrayPush(cx, arr, ObjectValue(*bp->getHandler())))
2882 : {
2883 0 : return false;
2884 : }
2885 : }
2886 : }
2887 : }
2888 180 : args.rval().setObject(*arr);
2889 180 : return true;
2890 : }
2891 :
2892 : static JSBool
2893 81 : DebuggerScript_clearBreakpoint(JSContext *cx, unsigned argc, Value *vp)
2894 : {
2895 81 : REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1);
2896 81 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
2897 81 : Debugger *dbg = Debugger::fromChildJSObject(obj);
2898 :
2899 81 : JSObject *handler = NonNullObject(cx, args[0]);
2900 81 : if (!handler)
2901 0 : return false;
2902 :
2903 81 : script->clearBreakpointsIn(cx, dbg, handler);
2904 81 : args.rval().setUndefined();
2905 81 : return true;
2906 : }
2907 :
2908 : static JSBool
2909 27 : DebuggerScript_clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp)
2910 : {
2911 27 : THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script);
2912 27 : Debugger *dbg = Debugger::fromChildJSObject(obj);
2913 27 : script->clearBreakpointsIn(cx, dbg, NULL);
2914 27 : args.rval().setUndefined();
2915 27 : return true;
2916 : }
2917 :
2918 : static JSBool
2919 18 : DebuggerScript_construct(JSContext *cx, unsigned argc, Value *vp)
2920 : {
2921 18 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Script");
2922 18 : return false;
2923 : }
2924 :
2925 : static JSPropertySpec DebuggerScript_properties[] = {
2926 : JS_PSG("url", DebuggerScript_getUrl, 0),
2927 : JS_PSG("startLine", DebuggerScript_getStartLine, 0),
2928 : JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
2929 : JS_PS_END
2930 : };
2931 :
2932 : static JSFunctionSpec DebuggerScript_methods[] = {
2933 : JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0),
2934 : JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0),
2935 : JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0),
2936 : JS_FN("getOffsetLine", DebuggerScript_getOffsetLine, 0, 0),
2937 : JS_FN("setBreakpoint", DebuggerScript_setBreakpoint, 2, 0),
2938 : JS_FN("getBreakpoints", DebuggerScript_getBreakpoints, 1, 0),
2939 : JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0),
2940 : JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0),
2941 : JS_FS_END
2942 : };
2943 :
2944 :
2945 : /*** Debugger.Frame ******************************************************************************/
2946 :
2947 : Class DebuggerFrame_class = {
2948 : "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
2949 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
2950 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
2951 : };
2952 :
2953 : static JSObject *
2954 28259 : CheckThisFrame(JSContext *cx, const CallArgs &args, const char *fnname, bool checkLive)
2955 : {
2956 28259 : if (!args.thisv().isObject()) {
2957 0 : ReportObjectRequired(cx);
2958 0 : return NULL;
2959 : }
2960 28259 : JSObject *thisobj = &args.thisv().toObject();
2961 28259 : if (thisobj->getClass() != &DebuggerFrame_class) {
2962 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
2963 18 : "Debugger.Frame", fnname, thisobj->getClass()->name);
2964 18 : return NULL;
2965 : }
2966 :
2967 : /*
2968 : * Forbid Debugger.Frame.prototype, which is of class DebuggerFrame_class
2969 : * but isn't really a working Debugger.Frame object. The prototype object
2970 : * is distinguished by having a NULL private value. Also, forbid popped
2971 : * frames.
2972 : */
2973 28241 : if (!thisobj->getPrivate()) {
2974 1647 : if (thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).isUndefined()) {
2975 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
2976 18 : "Debugger.Frame", fnname, "prototype object");
2977 18 : return NULL;
2978 : }
2979 1629 : if (checkLive) {
2980 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE,
2981 153 : "Debugger.Frame", fnname, "stack frame");
2982 153 : return NULL;
2983 : }
2984 : }
2985 28070 : return thisobj;
2986 : }
2987 :
2988 : #if DEBUG
2989 : static bool
2990 23561 : StackContains(JSContext *cx, StackFrame *fp)
2991 : {
2992 95955 : for (AllFramesIter i(cx->stack.space()); !i.done(); ++i) {
2993 95955 : if (fp == i.fp())
2994 23561 : return true;
2995 : }
2996 0 : return false;
2997 : }
2998 : #endif
2999 :
3000 : #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, fp) \
3001 : CallArgs args = CallArgsFromVp(argc, vp); \
3002 : JSObject *thisobj = CheckThisFrame(cx, args, fnname, true); \
3003 : if (!thisobj) \
3004 : return false; \
3005 : StackFrame *fp = (StackFrame *) thisobj->getPrivate(); \
3006 : JS_ASSERT(StackContains(cx, fp))
3007 :
3008 : #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, fp, dbg) \
3009 : THIS_FRAME(cx, argc, vp, fnname, args, thisobj, fp); \
3010 : Debugger *dbg = Debugger::fromChildJSObject(thisobj)
3011 :
3012 : static JSBool
3013 2151 : DebuggerFrame_getType(JSContext *cx, unsigned argc, Value *vp)
3014 : {
3015 2151 : THIS_FRAME(cx, argc, vp, "get type", args, thisobj, fp);
3016 :
3017 : /*
3018 : * Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the
3019 : * order of checks here is significant.
3020 : */
3021 4284 : args.rval().setString(fp->isEvalFrame()
3022 : ? cx->runtime->atomState.evalAtom
3023 1458 : : fp->isGlobalFrame()
3024 : ? cx->runtime->atomState.globalAtom
3025 5742 : : cx->runtime->atomState.callAtom);
3026 2142 : return true;
3027 : }
3028 :
3029 : static Env *
3030 1287 : Frame_GetEnv(JSContext *cx, StackFrame *fp)
3031 : {
3032 1287 : assertSameCompartment(cx, fp);
3033 1287 : if (fp->isNonEvalFunctionFrame() && !fp->hasCallObj() && !CallObject::createForFunction(cx, fp))
3034 0 : return NULL;
3035 1287 : return GetScopeChain(cx, fp);
3036 : }
3037 :
3038 : static JSBool
3039 1296 : DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
3040 : {
3041 1296 : THIS_FRAME_OWNER(cx, argc, vp, "get environment", args, thisobj, fp, dbg);
3042 :
3043 : Env *env;
3044 : {
3045 2574 : AutoCompartment ac(cx, &fp->scopeChain());
3046 1287 : if (!ac.enter())
3047 0 : return false;
3048 1287 : env = Frame_GetEnv(cx, fp);
3049 1287 : if (!env)
3050 0 : return false;
3051 : }
3052 :
3053 1287 : return dbg->wrapEnvironment(cx, env, &args.rval());
3054 : }
3055 :
3056 : static JSBool
3057 1404 : DebuggerFrame_getCallee(JSContext *cx, unsigned argc, Value *vp)
3058 : {
3059 1404 : THIS_FRAME(cx, argc, vp, "get callee", args, thisobj, fp);
3060 1395 : Value calleev = (fp->isFunctionFrame() && !fp->isEvalFrame()) ? fp->calleev() : NullValue();
3061 1395 : if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &calleev))
3062 0 : return false;
3063 1395 : args.rval() = calleev;
3064 1395 : return true;
3065 : }
3066 :
3067 : static JSBool
3068 108 : DebuggerFrame_getGenerator(JSContext *cx, unsigned argc, Value *vp)
3069 : {
3070 108 : THIS_FRAME(cx, argc, vp, "get generator", args, thisobj, fp);
3071 99 : args.rval().setBoolean(fp->isGeneratorFrame());
3072 99 : return true;
3073 : }
3074 :
3075 : static JSBool
3076 342 : DebuggerFrame_getConstructing(JSContext *cx, unsigned argc, Value *vp)
3077 : {
3078 342 : THIS_FRAME(cx, argc, vp, "get constructing", args, thisobj, fp);
3079 333 : args.rval().setBoolean(fp->isFunctionFrame() && fp->isConstructing());
3080 333 : return true;
3081 : }
3082 :
3083 : static JSBool
3084 387 : DebuggerFrame_getThis(JSContext *cx, unsigned argc, Value *vp)
3085 : {
3086 387 : THIS_FRAME(cx, argc, vp, "get this", args, thisobj, fp);
3087 : Value thisv;
3088 : {
3089 756 : AutoCompartment ac(cx, &fp->scopeChain());
3090 378 : if (!ac.enter())
3091 0 : return false;
3092 378 : if (!ComputeThis(cx, fp))
3093 0 : return false;
3094 756 : thisv = fp->thisValue();
3095 : }
3096 378 : if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &thisv))
3097 0 : return false;
3098 378 : args.rval() = thisv;
3099 378 : return true;
3100 : }
3101 :
3102 : static JSBool
3103 4860 : DebuggerFrame_getOlder(JSContext *cx, unsigned argc, Value *vp)
3104 : {
3105 4860 : THIS_FRAME(cx, argc, vp, "get this", args, thisobj, thisfp);
3106 4842 : Debugger *dbg = Debugger::fromChildJSObject(thisobj);
3107 5517 : for (StackFrame *fp = thisfp->prev(); fp; fp = fp->prev()) {
3108 5022 : if (dbg->observesFrame(fp))
3109 4347 : return dbg->getScriptFrame(cx, fp, vp);
3110 : }
3111 495 : args.rval().setNull();
3112 495 : return true;
3113 : }
3114 :
3115 : Class DebuggerArguments_class = {
3116 : "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT),
3117 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
3118 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
3119 : };
3120 :
3121 : /* The getter used for each element of frame.arguments. See DebuggerFrame_getArguments. */
3122 : static JSBool
3123 3127 : DebuggerArguments_getArg(JSContext *cx, unsigned argc, Value *vp)
3124 : {
3125 3127 : CallArgs args = CallArgsFromVp(argc, vp);
3126 3127 : int32_t i = args.callee().toFunction()->getExtendedSlot(0).toInt32();
3127 :
3128 : /* Check that the this value is an Arguments object. */
3129 3127 : if (!args.thisv().isObject()) {
3130 0 : ReportObjectRequired(cx);
3131 0 : return false;
3132 : }
3133 3127 : JSObject *argsobj = &args.thisv().toObject();
3134 3127 : if (argsobj->getClass() != &DebuggerArguments_class) {
3135 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
3136 9 : "Arguments", "getArgument", argsobj->getClass()->name);
3137 9 : return false;
3138 : }
3139 :
3140 : /*
3141 : * Put the Debugger.Frame into the this-value slot, then use THIS_FRAME
3142 : * to check that it is still live and get the fp.
3143 : */
3144 3118 : args.thisv() = argsobj->getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME);
3145 3118 : THIS_FRAME(cx, argc, vp, "get argument", ca2, thisobj, fp);
3146 :
3147 : /*
3148 : * Since getters can be extracted and applied to other objects,
3149 : * there is no guarantee this object has an ith argument.
3150 : */
3151 3109 : JS_ASSERT(i >= 0);
3152 : Value arg;
3153 3109 : if (unsigned(i) < fp->numActualArgs())
3154 3100 : arg = fp->canonicalActualArg(i);
3155 : else
3156 9 : arg.setUndefined();
3157 :
3158 3109 : if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg))
3159 0 : return false;
3160 3109 : args.rval() = arg;
3161 3109 : return true;
3162 : }
3163 :
3164 : static JSBool
3165 2677 : DebuggerFrame_getArguments(JSContext *cx, unsigned argc, Value *vp)
3166 : {
3167 2677 : THIS_FRAME(cx, argc, vp, "get arguments", args, thisobj, fp);
3168 2668 : Value argumentsv = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS);
3169 2668 : if (!argumentsv.isUndefined()) {
3170 940 : JS_ASSERT(argumentsv.isObjectOrNull());
3171 940 : args.rval() = argumentsv;
3172 940 : return true;
3173 : }
3174 :
3175 : JSObject *argsobj;
3176 1728 : if (fp->hasArgs()) {
3177 : /* Create an arguments object. */
3178 3456 : RootedVar<GlobalObject*> global(cx);
3179 1728 : global = &args.callee().global();
3180 1728 : JSObject *proto = global->getOrCreateArrayPrototype(cx);
3181 1728 : if (!proto)
3182 0 : return false;
3183 1728 : argsobj = NewObjectWithGivenProto(cx, &DebuggerArguments_class, proto, global);
3184 1728 : if (!argsobj)
3185 0 : return false;
3186 1728 : SetReservedSlot(argsobj, JSSLOT_DEBUGARGUMENTS_FRAME, ObjectValue(*thisobj));
3187 :
3188 1728 : JS_ASSERT(fp->numActualArgs() <= 0x7fffffff);
3189 1728 : int32_t fargc = int32_t(fp->numActualArgs());
3190 3456 : if (!DefineNativeProperty(cx, argsobj, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom),
3191 : Int32Value(fargc), NULL, NULL,
3192 3456 : JSPROP_PERMANENT | JSPROP_READONLY, 0, 0))
3193 : {
3194 0 : return false;
3195 : }
3196 :
3197 4140 : for (int32_t i = 0; i < fargc; i++) {
3198 : JSFunction *getobj =
3199 : js_NewFunction(cx, NULL, DebuggerArguments_getArg, 0, 0, global, NULL,
3200 2412 : JSFunction::ExtendedFinalizeKind);
3201 4824 : if (!getobj ||
3202 : !DefineNativeProperty(cx, argsobj, INT_TO_JSID(i), UndefinedValue(),
3203 : JS_DATA_TO_FUNC_PTR(PropertyOp, getobj), NULL,
3204 2412 : JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER, 0, 0))
3205 : {
3206 0 : return false;
3207 : }
3208 2412 : getobj->setExtendedSlot(0, Int32Value(i));
3209 : }
3210 : } else {
3211 0 : argsobj = NULL;
3212 : }
3213 1728 : args.rval() = ObjectOrNullValue(argsobj);
3214 1728 : thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, args.rval());
3215 1728 : return true;
3216 : }
3217 :
3218 : static JSBool
3219 2592 : DebuggerFrame_getScript(JSContext *cx, unsigned argc, Value *vp)
3220 : {
3221 2592 : THIS_FRAME(cx, argc, vp, "get script", args, thisobj, fp);
3222 2592 : Debugger *debug = Debugger::fromChildJSObject(thisobj);
3223 :
3224 2592 : JSObject *scriptObject = NULL;
3225 2592 : if (fp->isFunctionFrame() && !fp->isEvalFrame()) {
3226 1476 : JSFunction *callee = fp->callee().toFunction();
3227 1476 : if (callee->isInterpreted()) {
3228 1476 : scriptObject = debug->wrapScript(cx, callee->script());
3229 1476 : if (!scriptObject)
3230 0 : return false;
3231 : }
3232 1116 : } else if (fp->isScriptFrame()) {
3233 : /*
3234 : * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script
3235 : * frames.
3236 : */
3237 1116 : JSScript *script = fp->script();
3238 1116 : scriptObject = debug->wrapScript(cx, script);
3239 1116 : if (!scriptObject)
3240 0 : return false;
3241 : }
3242 2592 : args.rval().setObjectOrNull(scriptObject);
3243 2592 : return true;
3244 : }
3245 :
3246 : static JSBool
3247 1125 : DebuggerFrame_getOffset(JSContext *cx, unsigned argc, Value *vp)
3248 : {
3249 1125 : THIS_FRAME(cx, argc, vp, "get offset", args, thisobj, fp);
3250 1116 : if (fp->isScriptFrame()) {
3251 1116 : JSScript *script = fp->script();
3252 1116 : jsbytecode *pc = fp->pcQuadratic(cx);
3253 1116 : JS_ASSERT(script->code <= pc);
3254 1116 : JS_ASSERT(pc < script->code + script->length);
3255 1116 : size_t offset = pc - script->code;
3256 1116 : args.rval().setNumber(double(offset));
3257 : } else {
3258 0 : args.rval().setUndefined();
3259 : }
3260 1116 : return true;
3261 : }
3262 :
3263 : static JSBool
3264 4509 : DebuggerFrame_getLive(JSContext *cx, unsigned argc, Value *vp)
3265 : {
3266 4509 : CallArgs args = CallArgsFromVp(argc, vp);
3267 4509 : JSObject *thisobj = CheckThisFrame(cx, args, "get live", false);
3268 4509 : if (!thisobj)
3269 0 : return false;
3270 4509 : StackFrame *fp = (StackFrame *) thisobj->getPrivate();
3271 4509 : args.rval().setBoolean(!!fp);
3272 4509 : return true;
3273 : }
3274 :
3275 : static bool
3276 1620 : IsValidHook(const Value &v)
3277 : {
3278 1620 : return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
3279 : }
3280 :
3281 : static JSBool
3282 9 : DebuggerFrame_getOnStep(JSContext *cx, unsigned argc, Value *vp)
3283 : {
3284 9 : THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, fp);
3285 : (void) fp; // Silence GCC warning
3286 9 : Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
3287 9 : JS_ASSERT(IsValidHook(handler));
3288 9 : args.rval() = handler;
3289 9 : return true;
3290 : }
3291 :
3292 : static JSBool
3293 495 : DebuggerFrame_setOnStep(JSContext *cx, unsigned argc, Value *vp)
3294 : {
3295 495 : REQUIRE_ARGC("Debugger.Frame.set onStep", 1);
3296 495 : THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, fp);
3297 495 : if (!fp->isScriptFrame()) {
3298 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_SCRIPT_FRAME);
3299 0 : return false;
3300 : }
3301 495 : if (!IsValidHook(args[0])) {
3302 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
3303 0 : return false;
3304 : }
3305 :
3306 495 : Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
3307 495 : int delta = !args[0].isUndefined() - !prior.isUndefined();
3308 495 : if (delta != 0) {
3309 : /* Try to adjust this frame's script single-step mode count. */
3310 990 : AutoCompartment ac(cx, &fp->scopeChain());
3311 495 : if (!ac.enter())
3312 0 : return false;
3313 495 : if (!fp->script()->changeStepModeCount(cx, delta))
3314 0 : return false;
3315 : }
3316 :
3317 : /* Now that the step mode switch has succeeded, we can install the handler. */
3318 495 : thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]);
3319 495 : args.rval().setUndefined();
3320 495 : return true;
3321 : }
3322 :
3323 : static JSBool
3324 27 : DebuggerFrame_getOnPop(JSContext *cx, unsigned argc, Value *vp)
3325 : {
3326 27 : THIS_FRAME(cx, argc, vp, "get onPop", args, thisobj, fp);
3327 : (void) fp; // Silence GCC warning
3328 9 : Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER);
3329 9 : JS_ASSERT(IsValidHook(handler));
3330 9 : args.rval() = handler;
3331 9 : return true;
3332 : }
3333 :
3334 : static JSBool
3335 1161 : DebuggerFrame_setOnPop(JSContext *cx, unsigned argc, Value *vp)
3336 : {
3337 1161 : REQUIRE_ARGC("Debugger.Frame.set onPop", 1);
3338 1161 : THIS_FRAME(cx, argc, vp, "set onPop", args, thisobj, fp);
3339 1107 : if (!fp->isScriptFrame()) {
3340 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_SCRIPT_FRAME);
3341 0 : return false;
3342 : }
3343 1107 : if (!IsValidHook(args[0])) {
3344 54 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
3345 54 : return false;
3346 : }
3347 :
3348 1053 : thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]);
3349 1053 : args.rval().setUndefined();
3350 1053 : return true;
3351 : }
3352 :
3353 : namespace js {
3354 :
3355 : JSBool
3356 2952 : EvaluateInEnv(JSContext *cx, Env *env, StackFrame *fp, const jschar *chars,
3357 : unsigned length, const char *filename, unsigned lineno, Value *rval)
3358 : {
3359 2952 : assertSameCompartment(cx, env, fp);
3360 :
3361 2952 : if (fp) {
3362 : /* Execute assumes an already-computed 'this" value. */
3363 2952 : if (!ComputeThis(cx, fp))
3364 0 : return false;
3365 : }
3366 :
3367 : /*
3368 : * NB: This function breaks the assumption that the compiler can see all
3369 : * calls and properly compute a static level. In order to get around this,
3370 : * we use a static level that will cause us not to attempt to optimize
3371 : * variable references made by this frame.
3372 : */
3373 2952 : JSPrincipals *prin = fp->scopeChain().principals(cx);
3374 : JSScript *script = frontend::CompileScript(cx, env, fp, prin, prin,
3375 : TCF_COMPILE_N_GO | TCF_NEED_SCRIPT_GLOBAL,
3376 : chars, length, filename, lineno,
3377 : cx->findVersion(), NULL,
3378 2952 : UpvarCookie::UPVAR_LEVEL_LIMIT);
3379 :
3380 2952 : if (!script)
3381 9 : return false;
3382 :
3383 2943 : return ExecuteKernel(cx, script, *env, fp->thisValue(), EXECUTE_DEBUG, fp, rval);
3384 : }
3385 :
3386 : }
3387 :
3388 : enum EvalBindingsMode { WithoutBindings, WithBindings };
3389 :
3390 : static JSBool
3391 1998 : DebuggerFrameEval(JSContext *cx, unsigned argc, Value *vp, EvalBindingsMode mode)
3392 : {
3393 1998 : if (mode == WithBindings)
3394 333 : REQUIRE_ARGC("Debugger.Frame.evalWithBindings", 2);
3395 : else
3396 1665 : REQUIRE_ARGC("Debugger.Frame.eval", 1);
3397 1998 : THIS_FRAME(cx, argc, vp, mode == WithBindings ? "evalWithBindings" : "eval",
3398 1980 : args, thisobj, fp);
3399 1980 : Debugger *dbg = Debugger::fromChildJSObject(thisobj);
3400 :
3401 : /* Check the first argument, the eval code string. */
3402 1980 : if (!args[0].isString()) {
3403 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPECTED_TYPE,
3404 0 : "Debugger.Frame.eval", "string", InformalValueTypeName(args[0]));
3405 0 : return false;
3406 : }
3407 1980 : JSLinearString *linearStr = args[0].toString()->ensureLinear(cx);
3408 1980 : if (!linearStr)
3409 0 : return false;
3410 :
3411 : /*
3412 : * Gather keys and values of bindings, if any. This must be done in the
3413 : * debugger compartment, since that is where any exceptions must be
3414 : * thrown.
3415 : */
3416 3960 : AutoIdVector keys(cx);
3417 3960 : AutoValueVector values(cx);
3418 1980 : if (mode == WithBindings) {
3419 333 : JSObject *bindingsobj = NonNullObject(cx, args[1]);
3420 999 : if (!bindingsobj ||
3421 333 : !GetPropertyNames(cx, bindingsobj, JSITER_OWNONLY, &keys) ||
3422 333 : !values.growBy(keys.length()))
3423 : {
3424 0 : return false;
3425 : }
3426 693 : for (size_t i = 0; i < keys.length(); i++) {
3427 360 : Value *valp = &values[i];
3428 720 : if (!bindingsobj->getGeneric(cx, bindingsobj, keys[i], valp) ||
3429 360 : !dbg->unwrapDebuggeeValue(cx, valp))
3430 : {
3431 0 : return false;
3432 : }
3433 : }
3434 : }
3435 :
3436 3960 : AutoCompartment ac(cx, &fp->scopeChain());
3437 1980 : if (!ac.enter())
3438 0 : return false;
3439 :
3440 1980 : Env *env = JS_GetFrameScopeChain(cx, Jsvalify(fp));
3441 1980 : if (!env)
3442 0 : return false;
3443 :
3444 : /* If evalWithBindings, create the inner environment. */
3445 1980 : if (mode == WithBindings) {
3446 : /* TODO - This should probably be a Call object, like ES5 strict eval. */
3447 333 : env = NewObjectWithGivenProto(cx, &ObjectClass, NULL, env);
3448 333 : if (!env)
3449 0 : return false;
3450 693 : for (size_t i = 0; i < keys.length(); i++) {
3451 720 : if (!cx->compartment->wrap(cx, &values[i]) ||
3452 360 : !DefineNativeProperty(cx, env, keys[i], values[i], NULL, NULL, 0, 0, 0))
3453 : {
3454 0 : return false;
3455 : }
3456 : }
3457 : }
3458 :
3459 : /* Run the code and produce the completion value. */
3460 : Value rval;
3461 3960 : JS::Anchor<JSString *> anchor(linearStr);
3462 : bool ok = EvaluateInEnv(cx, env, fp, linearStr->chars(), linearStr->length(),
3463 1980 : "debugger eval code", 1, &rval);
3464 1980 : return dbg->receiveCompletionValue(ac, ok, rval, vp);
3465 : }
3466 :
3467 : static JSBool
3468 1665 : DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
3469 : {
3470 1665 : return DebuggerFrameEval(cx, argc, vp, WithoutBindings);
3471 : }
3472 :
3473 : static JSBool
3474 333 : DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp)
3475 : {
3476 333 : return DebuggerFrameEval(cx, argc, vp, WithBindings);
3477 : }
3478 :
3479 : static JSBool
3480 0 : DebuggerFrame_construct(JSContext *cx, unsigned argc, Value *vp)
3481 : {
3482 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Frame");
3483 0 : return false;
3484 : }
3485 :
3486 : static JSPropertySpec DebuggerFrame_properties[] = {
3487 : JS_PSG("arguments", DebuggerFrame_getArguments, 0),
3488 : JS_PSG("callee", DebuggerFrame_getCallee, 0),
3489 : JS_PSG("constructing", DebuggerFrame_getConstructing, 0),
3490 : JS_PSG("environment", DebuggerFrame_getEnvironment, 0),
3491 : JS_PSG("generator", DebuggerFrame_getGenerator, 0),
3492 : JS_PSG("live", DebuggerFrame_getLive, 0),
3493 : JS_PSG("offset", DebuggerFrame_getOffset, 0),
3494 : JS_PSG("older", DebuggerFrame_getOlder, 0),
3495 : JS_PSG("script", DebuggerFrame_getScript, 0),
3496 : JS_PSG("this", DebuggerFrame_getThis, 0),
3497 : JS_PSG("type", DebuggerFrame_getType, 0),
3498 : JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
3499 : JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0),
3500 : JS_PS_END
3501 : };
3502 :
3503 : static JSFunctionSpec DebuggerFrame_methods[] = {
3504 : JS_FN("eval", DebuggerFrame_eval, 1, 0),
3505 : JS_FN("evalWithBindings", DebuggerFrame_evalWithBindings, 1, 0),
3506 : JS_FS_END
3507 : };
3508 :
3509 :
3510 : /*** Debugger.Object *****************************************************************************/
3511 :
3512 : static void
3513 47340 : DebuggerObject_trace(JSTracer *trc, JSObject *obj)
3514 : {
3515 : /*
3516 : * There is a barrier on private pointers, so the Unbarriered marking
3517 : * is okay.
3518 : */
3519 47340 : if (JSObject *referent = (JSObject *) obj->getPrivate()) {
3520 2507 : MarkCrossCompartmentObjectUnbarriered(trc, &referent, "Debugger.Object referent");
3521 2507 : obj->setPrivateUnbarriered(referent);
3522 : }
3523 47340 : }
3524 :
3525 : Class DebuggerObject_class = {
3526 : "Object",
3527 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
3528 : JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
3529 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
3530 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
3531 : NULL, /* checkAccess */
3532 : NULL, /* call */
3533 : NULL, /* construct */
3534 : NULL, /* hasInstance */
3535 : DebuggerObject_trace
3536 : };
3537 :
3538 : static JSObject *
3539 7317 : DebuggerObject_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
3540 : {
3541 7317 : if (!args.thisv().isObject()) {
3542 0 : ReportObjectRequired(cx);
3543 0 : return NULL;
3544 : }
3545 7317 : JSObject *thisobj = &args.thisv().toObject();
3546 7317 : if (thisobj->getClass() != &DebuggerObject_class) {
3547 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
3548 0 : "Debugger.Object", fnname, thisobj->getClass()->name);
3549 0 : return NULL;
3550 : }
3551 :
3552 : /*
3553 : * Forbid Debugger.Object.prototype, which is of class DebuggerObject_class
3554 : * but isn't a real working Debugger.Object. The prototype object is
3555 : * distinguished by having no referent.
3556 : */
3557 7317 : if (!thisobj->getPrivate()) {
3558 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
3559 0 : "Debugger.Object", fnname, "prototype object");
3560 0 : return NULL;
3561 : }
3562 7317 : return thisobj;
3563 : }
3564 :
3565 : #define THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj) \
3566 : CallArgs args = CallArgsFromVp(argc, vp); \
3567 : JSObject *obj = DebuggerObject_checkThis(cx, args, fnname); \
3568 : if (!obj) \
3569 : return false; \
3570 : obj = (JSObject *) obj->getPrivate(); \
3571 : JS_ASSERT(obj)
3572 :
3573 : #define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj) \
3574 : CallArgs args = CallArgsFromVp(argc, vp); \
3575 : JSObject *obj = DebuggerObject_checkThis(cx, args, fnname); \
3576 : if (!obj) \
3577 : return false; \
3578 : Debugger *dbg = Debugger::fromChildJSObject(obj); \
3579 : obj = (JSObject *) obj->getPrivate(); \
3580 : JS_ASSERT(obj)
3581 :
3582 : static JSBool
3583 0 : DebuggerObject_construct(JSContext *cx, unsigned argc, Value *vp)
3584 : {
3585 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Object");
3586 0 : return false;
3587 : }
3588 :
3589 : static JSBool
3590 225 : DebuggerObject_getProto(JSContext *cx, unsigned argc, Value *vp)
3591 : {
3592 225 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get proto", args, dbg, refobj);
3593 225 : Value protov = ObjectOrNullValue(refobj->getProto());
3594 225 : if (!dbg->wrapDebuggeeValue(cx, &protov))
3595 0 : return false;
3596 225 : args.rval() = protov;
3597 225 : return true;
3598 : }
3599 :
3600 : static JSBool
3601 441 : DebuggerObject_getClass(JSContext *cx, unsigned argc, Value *vp)
3602 : {
3603 441 : THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get class", args, refobj);
3604 441 : const char *s = refobj->getClass()->name;
3605 441 : JSAtom *str = js_Atomize(cx, s, strlen(s));
3606 441 : if (!str)
3607 0 : return false;
3608 441 : args.rval().setString(str);
3609 441 : return true;
3610 : }
3611 :
3612 : static JSBool
3613 45 : DebuggerObject_getCallable(JSContext *cx, unsigned argc, Value *vp)
3614 : {
3615 45 : THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get callable", args, refobj);
3616 45 : args.rval().setBoolean(refobj->isCallable());
3617 45 : return true;
3618 : }
3619 :
3620 : static JSBool
3621 1188 : DebuggerObject_getName(JSContext *cx, unsigned argc, Value *vp)
3622 : {
3623 1188 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get name", args, dbg, obj);
3624 1188 : if (!obj->isFunction()) {
3625 45 : args.rval().setUndefined();
3626 45 : return true;
3627 : }
3628 :
3629 1143 : JSString *name = obj->toFunction()->atom;
3630 1143 : if (!name) {
3631 18 : args.rval().setUndefined();
3632 18 : return true;
3633 : }
3634 :
3635 1125 : Value namev = StringValue(name);
3636 1125 : if (!dbg->wrapDebuggeeValue(cx, &namev))
3637 0 : return false;
3638 1125 : args.rval() = namev;
3639 1125 : return true;
3640 : }
3641 :
3642 : static JSBool
3643 54 : DebuggerObject_getParameterNames(JSContext *cx, unsigned argc, Value *vp)
3644 : {
3645 54 : THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get parameterNames", args, obj);
3646 54 : if (!obj->isFunction()) {
3647 9 : args.rval().setUndefined();
3648 9 : return true;
3649 : }
3650 :
3651 45 : const JSFunction *fun = obj->toFunction();
3652 45 : JSObject *result = NewDenseAllocatedArray(cx, fun->nargs, NULL);
3653 45 : if (!result)
3654 0 : return false;
3655 45 : result->ensureDenseArrayInitializedLength(cx, 0, fun->nargs);
3656 :
3657 45 : if (fun->isInterpreted()) {
3658 36 : JS_ASSERT(fun->nargs == fun->script()->bindings.countArgs());
3659 :
3660 36 : if (fun->nargs > 0) {
3661 54 : Vector<JSAtom *> names(cx);
3662 27 : if (!fun->script()->bindings.getLocalNameArray(cx, &names))
3663 0 : return false;
3664 :
3665 297 : for (size_t i = 0; i < fun->nargs; i++) {
3666 270 : JSAtom *name = names[i];
3667 270 : result->setDenseArrayElement(i, name ? StringValue(name) : UndefinedValue());
3668 : }
3669 : }
3670 : } else {
3671 27 : for (size_t i = 0; i < fun->nargs; i++)
3672 18 : result->setDenseArrayElement(i, UndefinedValue());
3673 : }
3674 :
3675 45 : args.rval().setObject(*result);
3676 45 : return true;
3677 : }
3678 :
3679 : static JSBool
3680 2304 : DebuggerObject_getScript(JSContext *cx, unsigned argc, Value *vp)
3681 : {
3682 2304 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get script", args, dbg, obj);
3683 :
3684 2304 : if (!obj->isFunction()) {
3685 9 : args.rval().setUndefined();
3686 9 : return true;
3687 : }
3688 :
3689 2295 : JSFunction *fun = obj->toFunction();
3690 2295 : if (!fun->isInterpreted()) {
3691 9 : args.rval().setUndefined();
3692 9 : return true;
3693 : }
3694 :
3695 2286 : JSObject *scriptObject = dbg->wrapScript(cx, fun->script());
3696 2286 : if (!scriptObject)
3697 0 : return false;
3698 :
3699 2286 : args.rval().setObject(*scriptObject);
3700 2286 : return true;
3701 : }
3702 :
3703 : static JSBool
3704 99 : DebuggerObject_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
3705 : {
3706 99 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj);
3707 :
3708 : /* Don't bother switching compartments just to check obj's type and get its env. */
3709 99 : if (!obj->isFunction() || !obj->toFunction()->isInterpreted()) {
3710 27 : args.rval().setUndefined();
3711 27 : return true;
3712 : }
3713 :
3714 72 : Env *env = obj->toFunction()->environment();
3715 72 : return dbg->wrapEnvironment(cx, env, &args.rval());
3716 : }
3717 :
3718 : static JSBool
3719 837 : DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
3720 : {
3721 837 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyDescriptor", args, dbg, obj);
3722 :
3723 : jsid id;
3724 837 : if (!ValueToId(cx, argc >= 1 ? args[0] : UndefinedValue(), &id))
3725 0 : return false;
3726 :
3727 : /* Bug: This can cause the debuggee to run! */
3728 1674 : AutoPropertyDescriptorRooter desc(cx);
3729 : {
3730 1674 : AutoCompartment ac(cx, obj);
3731 837 : if (!ac.enter() || !cx->compartment->wrapId(cx, &id))
3732 0 : return false;
3733 :
3734 1674 : ErrorCopier ec(ac, dbg->toJSObject());
3735 837 : if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
3736 0 : return false;
3737 : }
3738 :
3739 837 : if (desc.obj) {
3740 : /* Rewrap the debuggee values in desc for the debugger. */
3741 765 : if (!dbg->wrapDebuggeeValue(cx, &desc.value))
3742 0 : return false;
3743 765 : if (desc.attrs & JSPROP_GETTER) {
3744 27 : Value get = ObjectOrNullValue(CastAsObject(desc.getter));
3745 27 : if (!dbg->wrapDebuggeeValue(cx, &get))
3746 0 : return false;
3747 27 : desc.getter = CastAsPropertyOp(get.toObjectOrNull());
3748 : }
3749 765 : if (desc.attrs & JSPROP_SETTER) {
3750 9 : Value set = ObjectOrNullValue(CastAsObject(desc.setter));
3751 9 : if (!dbg->wrapDebuggeeValue(cx, &set))
3752 0 : return false;
3753 9 : desc.setter = CastAsStrictPropertyOp(set.toObjectOrNull());
3754 : }
3755 : }
3756 :
3757 837 : return NewPropertyDescriptorObject(cx, &desc, &args.rval());
3758 : }
3759 :
3760 : static JSBool
3761 135 : DebuggerObject_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp)
3762 : {
3763 135 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyNames", args, dbg, obj);
3764 :
3765 270 : AutoIdVector keys(cx);
3766 : {
3767 270 : AutoCompartment ac(cx, obj);
3768 135 : if (!ac.enter())
3769 0 : return false;
3770 :
3771 270 : ErrorCopier ec(ac, dbg->toJSObject());
3772 135 : if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys))
3773 0 : return false;
3774 : }
3775 :
3776 270 : AutoValueVector vals(cx);
3777 135 : if (!vals.resize(keys.length()))
3778 0 : return false;
3779 :
3780 378 : for (size_t i = 0, len = keys.length(); i < len; i++) {
3781 243 : jsid id = keys[i];
3782 243 : if (JSID_IS_INT(id)) {
3783 27 : JSString *str = js_IntToString(cx, JSID_TO_INT(id));
3784 27 : if (!str)
3785 0 : return false;
3786 27 : vals[i].setString(str);
3787 216 : } else if (JSID_IS_ATOM(id)) {
3788 216 : vals[i].setString(JSID_TO_STRING(id));
3789 216 : if (!cx->compartment->wrap(cx, &vals[i]))
3790 0 : return false;
3791 : } else {
3792 0 : vals[i].setObject(*JSID_TO_OBJECT(id));
3793 0 : if (!dbg->wrapDebuggeeValue(cx, &vals[i]))
3794 0 : return false;
3795 : }
3796 : }
3797 :
3798 135 : JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin());
3799 135 : if (!aobj)
3800 0 : return false;
3801 135 : args.rval().setObject(*aobj);
3802 135 : return true;
3803 : }
3804 :
3805 : static bool
3806 270 : CheckArgCompartment(JSContext *cx, JSObject *obj, const Value &v,
3807 : const char *methodname, const char *propname)
3808 : {
3809 270 : if (v.isObject() && v.toObject().compartment() != obj->compartment()) {
3810 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_COMPARTMENT_MISMATCH,
3811 9 : methodname, propname);
3812 9 : return false;
3813 : }
3814 261 : return true;
3815 : }
3816 :
3817 : /*
3818 : * Convert Debugger.Objects in desc to debuggee values.
3819 : * Reject non-callable getters and setters.
3820 : */
3821 : static bool
3822 297 : UnwrapPropDesc(JSContext *cx, Debugger *dbg, JSObject *obj, PropDesc *desc)
3823 : {
3824 522 : return (!desc->hasValue || (dbg->unwrapDebuggeeValue(cx, &desc->value) &&
3825 : CheckArgCompartment(cx, obj, desc->value, "defineProperty",
3826 216 : "value"))) &&
3827 324 : (!desc->hasGet || (dbg->unwrapDebuggeeValue(cx, &desc->get) &&
3828 36 : CheckArgCompartment(cx, obj, desc->get, "defineProperty", "get") &&
3829 36 : desc->checkGetter(cx))) &&
3830 270 : (!desc->hasSet || (dbg->unwrapDebuggeeValue(cx, &desc->set) &&
3831 18 : CheckArgCompartment(cx, obj, desc->set, "defineProperty", "set") &&
3832 1422 : desc->checkSetter(cx)));
3833 : }
3834 :
3835 : /*
3836 : * Rewrap *idp and the fields of *desc for the current compartment. Also:
3837 : * defining a property on a proxy requires the pd field to contain a descriptor
3838 : * object, so reconstitute desc->pd if needed.
3839 : */
3840 : static bool
3841 252 : WrapIdAndPropDesc(JSContext *cx, JSObject *obj, jsid *idp, PropDesc *desc)
3842 : {
3843 252 : JSCompartment *comp = cx->compartment;
3844 252 : return comp->wrapId(cx, idp) &&
3845 252 : comp->wrap(cx, &desc->value) &&
3846 252 : comp->wrap(cx, &desc->get) &&
3847 252 : comp->wrap(cx, &desc->set) &&
3848 1008 : (!IsProxy(obj) || desc->makeObject(cx));
3849 : }
3850 :
3851 : static JSBool
3852 189 : DebuggerObject_defineProperty(JSContext *cx, unsigned argc, Value *vp)
3853 : {
3854 189 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperty", args, dbg, obj);
3855 189 : REQUIRE_ARGC("Debugger.Object.defineProperty", 2);
3856 :
3857 : jsid id;
3858 180 : if (!ValueToId(cx, args[0], &id))
3859 0 : return JS_FALSE;
3860 :
3861 180 : const Value &descval = args[1];
3862 360 : AutoPropDescArrayRooter descs(cx);
3863 180 : PropDesc *desc = descs.append();
3864 180 : if (!desc || !desc->initialize(cx, descval, false))
3865 0 : return false;
3866 :
3867 180 : desc->pd.setUndefined();
3868 180 : if (!UnwrapPropDesc(cx, dbg, obj, desc))
3869 45 : return false;
3870 :
3871 : {
3872 270 : AutoCompartment ac(cx, obj);
3873 135 : if (!ac.enter() || !WrapIdAndPropDesc(cx, obj, &id, desc))
3874 0 : return false;
3875 :
3876 270 : ErrorCopier ec(ac, dbg->toJSObject());
3877 : bool dummy;
3878 135 : if (!DefineProperty(cx, obj, id, *desc, true, &dummy))
3879 9 : return false;
3880 : }
3881 :
3882 126 : args.rval().setUndefined();
3883 126 : return true;
3884 : }
3885 :
3886 : static JSBool
3887 90 : DebuggerObject_defineProperties(JSContext *cx, unsigned argc, Value *vp)
3888 : {
3889 90 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperties", args, dbg, obj);
3890 90 : REQUIRE_ARGC("Debugger.Object.defineProperties", 1);
3891 90 : JSObject *props = ToObject(cx, &args[0]);
3892 90 : if (!props)
3893 0 : return false;
3894 :
3895 180 : AutoIdVector ids(cx);
3896 180 : AutoPropDescArrayRooter descs(cx);
3897 90 : if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs))
3898 0 : return false;
3899 90 : size_t n = ids.length();
3900 :
3901 207 : for (size_t i = 0; i < n; i++) {
3902 117 : if (!UnwrapPropDesc(cx, dbg, obj, &descs[i]))
3903 0 : return false;
3904 : }
3905 :
3906 : {
3907 180 : AutoCompartment ac(cx, obj);
3908 90 : if (!ac.enter())
3909 0 : return false;
3910 207 : for (size_t i = 0; i < n; i++) {
3911 117 : if (!WrapIdAndPropDesc(cx, obj, &ids[i], &descs[i]))
3912 0 : return false;
3913 : }
3914 :
3915 180 : ErrorCopier ec(ac, dbg->toJSObject());
3916 198 : for (size_t i = 0; i < n; i++) {
3917 : bool dummy;
3918 117 : if (!DefineProperty(cx, obj, ids[i], descs[i], true, &dummy))
3919 9 : return false;
3920 : }
3921 : }
3922 :
3923 81 : args.rval().setUndefined();
3924 81 : return true;
3925 : }
3926 :
3927 : /*
3928 : * This does a non-strict delete, as a matter of API design. The case where the
3929 : * property is non-configurable isn't necessarily exceptional here.
3930 : */
3931 : static JSBool
3932 45 : DebuggerObject_deleteProperty(JSContext *cx, unsigned argc, Value *vp)
3933 : {
3934 45 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "deleteProperty", args, dbg, obj);
3935 45 : Value nameArg = argc > 0 ? args[0] : UndefinedValue();
3936 :
3937 90 : AutoCompartment ac(cx, obj);
3938 45 : if (!ac.enter() || !cx->compartment->wrap(cx, &nameArg))
3939 0 : return false;
3940 :
3941 90 : ErrorCopier ec(ac, dbg->toJSObject());
3942 45 : return obj->deleteByValue(cx, nameArg, &args.rval(), false);
3943 : }
3944 :
3945 : enum SealHelperOp { Seal, Freeze, PreventExtensions };
3946 :
3947 : static JSBool
3948 207 : DebuggerObject_sealHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op, const char *name)
3949 : {
3950 207 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj);
3951 :
3952 414 : AutoCompartment ac(cx, obj);
3953 207 : if (!ac.enter())
3954 0 : return false;
3955 :
3956 414 : ErrorCopier ec(ac, dbg->toJSObject());
3957 : bool ok;
3958 207 : if (op == Seal) {
3959 63 : ok = obj->seal(cx);
3960 144 : } else if (op == Freeze) {
3961 63 : ok = obj->freeze(cx);
3962 : } else {
3963 81 : JS_ASSERT(op == PreventExtensions);
3964 81 : if (!obj->isExtensible()) {
3965 9 : args.rval().setUndefined();
3966 9 : return true;
3967 : }
3968 144 : AutoIdVector props(cx);
3969 72 : ok = obj->preventExtensions(cx, &props);
3970 : }
3971 198 : if (!ok)
3972 0 : return false;
3973 198 : args.rval().setUndefined();
3974 198 : return true;
3975 : }
3976 :
3977 : static JSBool
3978 63 : DebuggerObject_seal(JSContext *cx, unsigned argc, Value *vp)
3979 : {
3980 63 : return DebuggerObject_sealHelper(cx, argc, vp, Seal, "seal");
3981 : }
3982 :
3983 : static JSBool
3984 63 : DebuggerObject_freeze(JSContext *cx, unsigned argc, Value *vp)
3985 : {
3986 63 : return DebuggerObject_sealHelper(cx, argc, vp, Freeze, "freeze");
3987 : }
3988 :
3989 : static JSBool
3990 81 : DebuggerObject_preventExtensions(JSContext *cx, unsigned argc, Value *vp)
3991 : {
3992 81 : return DebuggerObject_sealHelper(cx, argc, vp, PreventExtensions, "preventExtensions");
3993 : }
3994 :
3995 : static JSBool
3996 774 : DebuggerObject_isSealedHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op,
3997 : const char *name)
3998 : {
3999 774 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj);
4000 :
4001 1548 : AutoCompartment ac(cx, obj);
4002 774 : if (!ac.enter())
4003 0 : return false;
4004 :
4005 1548 : ErrorCopier ec(ac, dbg->toJSObject());
4006 : bool r;
4007 774 : if (op == Seal) {
4008 252 : if (!obj->isSealed(cx, &r))
4009 0 : return false;
4010 522 : } else if (op == Freeze) {
4011 252 : if (!obj->isFrozen(cx, &r))
4012 0 : return false;
4013 : } else {
4014 270 : r = obj->isExtensible();
4015 : }
4016 774 : args.rval().setBoolean(r);
4017 774 : return true;
4018 : }
4019 :
4020 : static JSBool
4021 252 : DebuggerObject_isSealed(JSContext *cx, unsigned argc, Value *vp)
4022 : {
4023 252 : return DebuggerObject_isSealedHelper(cx, argc, vp, Seal, "isSealed");
4024 : }
4025 :
4026 : static JSBool
4027 252 : DebuggerObject_isFrozen(JSContext *cx, unsigned argc, Value *vp)
4028 : {
4029 252 : return DebuggerObject_isSealedHelper(cx, argc, vp, Freeze, "isFrozen");
4030 : }
4031 :
4032 : static JSBool
4033 270 : DebuggerObject_isExtensible(JSContext *cx, unsigned argc, Value *vp)
4034 : {
4035 270 : return DebuggerObject_isSealedHelper(cx, argc, vp, PreventExtensions, "isExtensible");
4036 : }
4037 :
4038 : enum ApplyOrCallMode { ApplyMode, CallMode };
4039 :
4040 : static JSBool
4041 576 : ApplyOrCall(JSContext *cx, unsigned argc, Value *vp, ApplyOrCallMode mode)
4042 : {
4043 576 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "apply", args, dbg, obj);
4044 :
4045 : /*
4046 : * Any JS exceptions thrown must be in the debugger compartment, so do
4047 : * sanity checks and fallible conversions before entering the debuggee.
4048 : */
4049 576 : Value calleev = ObjectValue(*obj);
4050 576 : if (!obj->isCallable()) {
4051 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
4052 0 : "Debugger.Object", "apply", obj->getClass()->name);
4053 0 : return false;
4054 : }
4055 :
4056 : /*
4057 : * Unwrap Debugger.Objects. This happens in the debugger's compartment since
4058 : * that is where any exceptions must be reported.
4059 : */
4060 576 : Value thisv = argc > 0 ? args[0] : UndefinedValue();
4061 576 : if (!dbg->unwrapDebuggeeValue(cx, &thisv))
4062 18 : return false;
4063 558 : unsigned callArgc = 0;
4064 558 : Value *callArgv = NULL;
4065 1116 : AutoValueVector argv(cx);
4066 558 : if (mode == ApplyMode) {
4067 270 : if (argc >= 2 && !args[1].isNullOrUndefined()) {
4068 243 : if (!args[1].isObject()) {
4069 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_APPLY_ARGS,
4070 18 : js_apply_str);
4071 18 : return false;
4072 : }
4073 225 : JSObject *argsobj = &args[1].toObject();
4074 225 : if (!js_GetLengthProperty(cx, argsobj, &callArgc))
4075 0 : return false;
4076 225 : callArgc = unsigned(JS_MIN(callArgc, StackSpace::ARGS_LENGTH_MAX));
4077 225 : if (!argv.growBy(callArgc) || !GetElements(cx, argsobj, callArgc, argv.begin()))
4078 0 : return false;
4079 225 : callArgv = argv.begin();
4080 : }
4081 : } else {
4082 288 : callArgc = argc > 0 ? unsigned(JS_MIN(argc - 1, StackSpace::ARGS_LENGTH_MAX)) : 0;
4083 288 : callArgv = args.array() + 1;
4084 : }
4085 1206 : for (unsigned i = 0; i < callArgc; i++) {
4086 684 : if (!dbg->unwrapDebuggeeValue(cx, &callArgv[i]))
4087 18 : return false;
4088 : }
4089 :
4090 : /*
4091 : * Enter the debuggee compartment and rewrap all input value for that compartment.
4092 : * (Rewrapping always takes place in the destination compartment.)
4093 : */
4094 1044 : AutoCompartment ac(cx, obj);
4095 522 : if (!ac.enter() || !cx->compartment->wrap(cx, &calleev) || !cx->compartment->wrap(cx, &thisv))
4096 0 : return false;
4097 1188 : for (unsigned i = 0; i < callArgc; i++) {
4098 666 : if (!cx->compartment->wrap(cx, &callArgv[i]))
4099 0 : return false;
4100 : }
4101 :
4102 : /*
4103 : * Call the function. Use receiveCompletionValue to return to the debugger
4104 : * compartment and populate args.rval().
4105 : */
4106 : Value rval;
4107 522 : bool ok = Invoke(cx, thisv, calleev, callArgc, callArgv, &rval);
4108 522 : return dbg->receiveCompletionValue(ac, ok, rval, &args.rval());
4109 : }
4110 :
4111 : static JSBool
4112 279 : DebuggerObject_apply(JSContext *cx, unsigned argc, Value *vp)
4113 : {
4114 279 : return ApplyOrCall(cx, argc, vp, ApplyMode);
4115 : }
4116 :
4117 : static JSBool
4118 297 : DebuggerObject_call(JSContext *cx, unsigned argc, Value *vp)
4119 : {
4120 297 : return ApplyOrCall(cx, argc, vp, CallMode);
4121 : }
4122 :
4123 : static JSBool
4124 108 : DebuggerObject_makeDebuggeeValue(JSContext *cx, unsigned argc, Value *vp)
4125 : {
4126 108 : REQUIRE_ARGC("Debugger.Object.prototype.makeDebuggeeValue", 1);
4127 108 : THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "makeDebuggeeValue", args, dbg, referent);
4128 :
4129 : /* Non-objects are already debuggee values. */
4130 108 : if (args[0].isObject()) {
4131 : // Enter this Debugger.Object's referent's compartment, and wrap the
4132 : // argument as appropriate for references from there.
4133 : {
4134 90 : AutoCompartment ac(cx, referent);
4135 90 : if (!ac.enter() ||
4136 45 : !cx->compartment->wrap(cx, &args[0]))
4137 0 : return false;
4138 : }
4139 :
4140 : // Back in the debugger's compartment, produce a new Debugger.Object
4141 : // instance referring to the wrapped argument.
4142 45 : if (!dbg->wrapDebuggeeValue(cx, &args[0]))
4143 0 : return false;
4144 : }
4145 :
4146 108 : args.rval() = args[0];
4147 108 : return true;
4148 : }
4149 :
4150 : static JSPropertySpec DebuggerObject_properties[] = {
4151 : JS_PSG("proto", DebuggerObject_getProto, 0),
4152 : JS_PSG("class", DebuggerObject_getClass, 0),
4153 : JS_PSG("callable", DebuggerObject_getCallable, 0),
4154 : JS_PSG("name", DebuggerObject_getName, 0),
4155 : JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0),
4156 : JS_PSG("script", DebuggerObject_getScript, 0),
4157 : JS_PSG("environment", DebuggerObject_getEnvironment, 0),
4158 : JS_PS_END
4159 : };
4160 :
4161 : static JSFunctionSpec DebuggerObject_methods[] = {
4162 : JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0),
4163 : JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0),
4164 : JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0),
4165 : JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0),
4166 : JS_FN("deleteProperty", DebuggerObject_deleteProperty, 1, 0),
4167 : JS_FN("seal", DebuggerObject_seal, 0, 0),
4168 : JS_FN("freeze", DebuggerObject_freeze, 0, 0),
4169 : JS_FN("preventExtensions", DebuggerObject_preventExtensions, 0, 0),
4170 : JS_FN("isSealed", DebuggerObject_isSealed, 0, 0),
4171 : JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0),
4172 : JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0),
4173 : JS_FN("apply", DebuggerObject_apply, 0, 0),
4174 : JS_FN("call", DebuggerObject_call, 0, 0),
4175 : JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0),
4176 : JS_FS_END
4177 : };
4178 :
4179 :
4180 : /*** Debugger.Environment ************************************************************************/
4181 :
4182 : static void
4183 44923 : DebuggerEnv_trace(JSTracer *trc, JSObject *obj)
4184 : {
4185 : /*
4186 : * There is a barrier on private pointers, so the Unbarriered marking
4187 : * is okay.
4188 : */
4189 44923 : if (Env *referent = (JSObject *) obj->getPrivate()) {
4190 90 : MarkCrossCompartmentObjectUnbarriered(trc, &referent, "Debugger.Environment referent");
4191 90 : obj->setPrivateUnbarriered(referent);
4192 : }
4193 44923 : }
4194 :
4195 : Class DebuggerEnv_class = {
4196 : "Environment",
4197 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
4198 : JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT),
4199 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
4200 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
4201 : NULL, /* checkAccess */
4202 : NULL, /* call */
4203 : NULL, /* construct */
4204 : NULL, /* hasInstance */
4205 : DebuggerEnv_trace
4206 : };
4207 :
4208 : static JSObject *
4209 15264 : DebuggerEnv_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
4210 : {
4211 15264 : if (!args.thisv().isObject()) {
4212 0 : ReportObjectRequired(cx);
4213 0 : return NULL;
4214 : }
4215 15264 : JSObject *thisobj = &args.thisv().toObject();
4216 15264 : if (thisobj->getClass() != &DebuggerEnv_class) {
4217 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
4218 0 : "Debugger.Environment", fnname, thisobj->getClass()->name);
4219 0 : return NULL;
4220 : }
4221 :
4222 : /*
4223 : * Forbid Debugger.Environment.prototype, which is of class DebuggerEnv_class
4224 : * but isn't a real working Debugger.Environment. The prototype object is
4225 : * distinguished by having no referent.
4226 : */
4227 15264 : if (!thisobj->getPrivate()) {
4228 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
4229 0 : "Debugger.Environment", fnname, "prototype object");
4230 0 : return NULL;
4231 : }
4232 15264 : return thisobj;
4233 : }
4234 :
4235 : #define THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env) \
4236 : CallArgs args = CallArgsFromVp(argc, vp); \
4237 : JSObject *envobj = DebuggerEnv_checkThis(cx, args, fnname); \
4238 : if (!envobj) \
4239 : return false; \
4240 : Env *env = static_cast<Env *>(envobj->getPrivate()); \
4241 : JS_ASSERT(env)
4242 :
4243 : #define THIS_DEBUGENV_OWNER(cx, argc, vp, fnname, args, envobj, env, dbg) \
4244 : THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env); \
4245 : Debugger *dbg = Debugger::fromChildJSObject(envobj)
4246 :
4247 : static JSBool
4248 0 : DebuggerEnv_construct(JSContext *cx, unsigned argc, Value *vp)
4249 : {
4250 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Environment");
4251 0 : return false;
4252 : }
4253 :
4254 : static JSBool
4255 432 : DebuggerEnv_getType(JSContext *cx, unsigned argc, Value *vp)
4256 : {
4257 432 : THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env);
4258 :
4259 : /* Don't bother switching compartments just to check env's class. */
4260 : const char *s;
4261 432 : if (env->isCall() || env->isBlock() || env->isDeclEnv())
4262 351 : s = "declarative";
4263 : else
4264 81 : s = "object";
4265 :
4266 432 : JSAtom *str = js_Atomize(cx, s, strlen(s), InternAtom, NormalEncoding);
4267 432 : if (!str)
4268 0 : return false;
4269 432 : args.rval().setString(str);
4270 432 : return true;
4271 : }
4272 :
4273 : static JSBool
4274 6903 : DebuggerEnv_getParent(JSContext *cx, unsigned argc, Value *vp)
4275 : {
4276 6903 : THIS_DEBUGENV_OWNER(cx, argc, vp, "get parent", args, envobj, env, dbg);
4277 :
4278 : /* Don't bother switching compartments just to get env's parent. */
4279 6903 : Env *parent = env->enclosingScope();
4280 6903 : return dbg->wrapEnvironment(cx, parent, &args.rval());
4281 : }
4282 :
4283 : static JSBool
4284 63 : DebuggerEnv_getObject(JSContext *cx, unsigned argc, Value *vp)
4285 : {
4286 63 : THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
4287 :
4288 : /*
4289 : * Don't bother switching compartments just to check env's class and
4290 : * possibly get its proto.
4291 : */
4292 63 : if (env->isCall() || env->isBlock() || env->isDeclEnv()) {
4293 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NO_SCOPE_OBJECT);
4294 0 : return false;
4295 : }
4296 63 : JSObject *obj = env->isWith() ? env->getProto() : env;
4297 :
4298 63 : Value rval = ObjectValue(*obj);
4299 63 : if (!dbg->wrapDebuggeeValue(cx, &rval))
4300 0 : return false;
4301 63 : args.rval() = rval;
4302 63 : return true;
4303 : }
4304 :
4305 : static JSBool
4306 7047 : DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp)
4307 : {
4308 7047 : THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
4309 :
4310 14094 : AutoIdVector keys(cx);
4311 : {
4312 14094 : AutoCompartment ac(cx, env);
4313 7047 : if (!ac.enter())
4314 0 : return false;
4315 :
4316 14094 : ErrorCopier ec(ac, dbg->toJSObject());
4317 7047 : if (!GetPropertyNames(cx, env, JSITER_HIDDEN, &keys))
4318 0 : return false;
4319 : }
4320 :
4321 7047 : JSObject *arr = NewDenseEmptyArray(cx);
4322 7047 : if (!arr)
4323 0 : return false;
4324 462213 : for (size_t i = 0, len = keys.length(); i < len; i++) {
4325 455166 : jsid id = keys[i];
4326 455166 : if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) {
4327 455130 : if (!cx->compartment->wrapId(cx, &id))
4328 0 : return false;
4329 455130 : if (!js_NewbornArrayPush(cx, arr, StringValue(JSID_TO_STRING(id))))
4330 0 : return false;
4331 : }
4332 : }
4333 7047 : args.rval().setObject(*arr);
4334 7047 : return true;
4335 : }
4336 :
4337 : static JSBool
4338 837 : DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp)
4339 : {
4340 837 : REQUIRE_ARGC("Debugger.Environment.find", 1);
4341 819 : THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
4342 :
4343 : jsid id;
4344 819 : if (!ValueToIdentifier(cx, args[0], &id))
4345 108 : return false;
4346 :
4347 : {
4348 1422 : AutoCompartment ac(cx, env);
4349 711 : if (!ac.enter() || !cx->compartment->wrapId(cx, &id))
4350 0 : return false;
4351 :
4352 : /* This can trigger resolve hooks. */
4353 1422 : ErrorCopier ec(ac, dbg->toJSObject());
4354 711 : JSProperty *prop = NULL;
4355 : JSObject *pobj;
4356 7560 : for (; env && !prop; env = env->enclosingScope()) {
4357 7488 : if (!env->lookupGeneric(cx, id, &pobj, &prop))
4358 0 : return false;
4359 7488 : if (prop)
4360 639 : break;
4361 : }
4362 : }
4363 :
4364 711 : return dbg->wrapEnvironment(cx, env, &args.rval());
4365 : }
4366 :
4367 : static JSPropertySpec DebuggerEnv_properties[] = {
4368 : JS_PSG("type", DebuggerEnv_getType, 0),
4369 : JS_PSG("object", DebuggerEnv_getObject, 0),
4370 : JS_PSG("parent", DebuggerEnv_getParent, 0),
4371 : JS_PS_END
4372 : };
4373 :
4374 : static JSFunctionSpec DebuggerEnv_methods[] = {
4375 : JS_FN("names", DebuggerEnv_names, 0, 0),
4376 : JS_FN("find", DebuggerEnv_find, 1, 0),
4377 : JS_FS_END
4378 : };
4379 :
4380 :
4381 :
4382 : /*** Glue ****************************************************************************************/
4383 :
4384 : extern JS_PUBLIC_API(JSBool)
4385 23330 : JS_DefineDebuggerObject(JSContext *cx, JSObject *obj)
4386 : {
4387 46660 : RootObject objRoot(cx, &obj);
4388 :
4389 : RootedVarObject
4390 46660 : objProto(cx),
4391 46660 : debugCtor(cx),
4392 46660 : debugProto(cx),
4393 46660 : frameProto(cx),
4394 46660 : scriptProto(cx),
4395 46660 : objectProto(cx);
4396 :
4397 23330 : objProto = obj->asGlobal().getOrCreateObjectPrototype(cx);
4398 23330 : if (!objProto)
4399 0 : return false;
4400 :
4401 :
4402 : debugProto = js_InitClass(cx, objRoot,
4403 : objProto, &Debugger::jsclass, Debugger::construct,
4404 : 1, Debugger::properties, Debugger::methods, NULL, NULL,
4405 23330 : debugCtor.address());
4406 23330 : if (!debugProto)
4407 0 : return false;
4408 :
4409 : frameProto = js_InitClass(cx, debugCtor, objProto, &DebuggerFrame_class,
4410 : DebuggerFrame_construct, 0,
4411 : DebuggerFrame_properties, DebuggerFrame_methods,
4412 23330 : NULL, NULL);
4413 23330 : if (!frameProto)
4414 0 : return false;
4415 :
4416 : scriptProto = js_InitClass(cx, debugCtor, objProto, &DebuggerScript_class,
4417 : DebuggerScript_construct, 0,
4418 : DebuggerScript_properties, DebuggerScript_methods,
4419 23330 : NULL, NULL);
4420 23330 : if (!scriptProto)
4421 0 : return false;
4422 :
4423 : objectProto = js_InitClass(cx, debugCtor, objProto, &DebuggerObject_class,
4424 : DebuggerObject_construct, 0,
4425 : DebuggerObject_properties, DebuggerObject_methods,
4426 23330 : NULL, NULL);
4427 23330 : if (!objectProto)
4428 0 : return false;
4429 :
4430 : JSObject *envProto = js_InitClass(cx, debugCtor, objProto, &DebuggerEnv_class,
4431 : DebuggerEnv_construct, 0,
4432 : DebuggerEnv_properties, DebuggerEnv_methods,
4433 23330 : NULL, NULL);
4434 23330 : if (!envProto)
4435 0 : return false;
4436 :
4437 23330 : debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
4438 23330 : debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
4439 23330 : debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto));
4440 23330 : debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto));
4441 23330 : return true;
4442 : }
|