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