js/src/vm/Debugger.h

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

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

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

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 * vim: set ts=8 sts=4 et sw=4 tw=99:
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #ifndef vm_Debugger_h
michael@0 8 #define vm_Debugger_h
michael@0 9
michael@0 10 #include "mozilla/LinkedList.h"
michael@0 11
michael@0 12 #include "jsclist.h"
michael@0 13 #include "jscntxt.h"
michael@0 14 #include "jscompartment.h"
michael@0 15 #include "jsweakmap.h"
michael@0 16
michael@0 17 #include "gc/Barrier.h"
michael@0 18 #include "js/HashTable.h"
michael@0 19 #include "vm/GlobalObject.h"
michael@0 20
michael@0 21 namespace js {
michael@0 22
michael@0 23 class Breakpoint;
michael@0 24
michael@0 25 /*
michael@0 26 * A weakmap that supports the keys being in different compartments to the
michael@0 27 * values, although all values must be in the same compartment.
michael@0 28 *
michael@0 29 * The Key and Value classes must support the compartment() method.
michael@0 30 *
michael@0 31 * The purpose of this is to allow the garbage collector to easily find edges
michael@0 32 * from debugee object compartments to debugger compartments when calculating
michael@0 33 * the compartment groups. Note that these edges are the inverse of the edges
michael@0 34 * stored in the cross compartment map.
michael@0 35 *
michael@0 36 * The current implementation results in all debuggee object compartments being
michael@0 37 * swept in the same group as the debugger. This is a conservative approach,
michael@0 38 * and compartments may be unnecessarily grouped, however it results in a
michael@0 39 * simpler and faster implementation.
michael@0 40 *
michael@0 41 * If InvisibleKeysOk is true, then the map can have keys in invisible-to-
michael@0 42 * debugger compartments. If it is false, we assert that such entries are never
michael@0 43 * created.
michael@0 44 */
michael@0 45 template <class Key, class Value, bool InvisibleKeysOk=false>
michael@0 46 class DebuggerWeakMap : private WeakMap<Key, Value, DefaultHasher<Key> >
michael@0 47 {
michael@0 48 private:
michael@0 49 typedef HashMap<JS::Zone *,
michael@0 50 uintptr_t,
michael@0 51 DefaultHasher<JS::Zone *>,
michael@0 52 RuntimeAllocPolicy> CountMap;
michael@0 53
michael@0 54 CountMap zoneCounts;
michael@0 55
michael@0 56 public:
michael@0 57 typedef WeakMap<Key, Value, DefaultHasher<Key> > Base;
michael@0 58 explicit DebuggerWeakMap(JSContext *cx)
michael@0 59 : Base(cx), zoneCounts(cx->runtime()) { }
michael@0 60
michael@0 61 public:
michael@0 62 /* Expose those parts of HashMap public interface that are used by Debugger methods. */
michael@0 63
michael@0 64 typedef typename Base::Entry Entry;
michael@0 65 typedef typename Base::Ptr Ptr;
michael@0 66 typedef typename Base::AddPtr AddPtr;
michael@0 67 typedef typename Base::Range Range;
michael@0 68 typedef typename Base::Enum Enum;
michael@0 69 typedef typename Base::Lookup Lookup;
michael@0 70
michael@0 71 /* Expose WeakMap public interface */
michael@0 72
michael@0 73 using Base::clearWithoutCallingDestructors;
michael@0 74 using Base::lookupForAdd;
michael@0 75 using Base::all;
michael@0 76 using Base::trace;
michael@0 77
michael@0 78 bool init(uint32_t len = 16) {
michael@0 79 return Base::init(len) && zoneCounts.init();
michael@0 80 }
michael@0 81
michael@0 82 template<typename KeyInput, typename ValueInput>
michael@0 83 bool relookupOrAdd(AddPtr &p, const KeyInput &k, const ValueInput &v) {
michael@0 84 JS_ASSERT(v->compartment() == Base::compartment);
michael@0 85 JS_ASSERT(!k->compartment()->options_.mergeable());
michael@0 86 JS_ASSERT_IF(!InvisibleKeysOk, !k->compartment()->options_.invisibleToDebugger());
michael@0 87 JS_ASSERT(!Base::has(k));
michael@0 88 if (!incZoneCount(k->zone()))
michael@0 89 return false;
michael@0 90 bool ok = Base::relookupOrAdd(p, k, v);
michael@0 91 if (!ok)
michael@0 92 decZoneCount(k->zone());
michael@0 93 return ok;
michael@0 94 }
michael@0 95
michael@0 96 void remove(const Lookup &l) {
michael@0 97 JS_ASSERT(Base::has(l));
michael@0 98 Base::remove(l);
michael@0 99 decZoneCount(l->zone());
michael@0 100 }
michael@0 101
michael@0 102 public:
michael@0 103 void markKeys(JSTracer *tracer) {
michael@0 104 for (Enum e(*static_cast<Base *>(this)); !e.empty(); e.popFront()) {
michael@0 105 Key key = e.front().key();
michael@0 106 gc::Mark(tracer, &key, "Debugger WeakMap key");
michael@0 107 if (key != e.front().key())
michael@0 108 e.rekeyFront(key);
michael@0 109 key.unsafeSet(nullptr);
michael@0 110 }
michael@0 111 }
michael@0 112
michael@0 113 bool hasKeyInZone(JS::Zone *zone) {
michael@0 114 CountMap::Ptr p = zoneCounts.lookup(zone);
michael@0 115 JS_ASSERT_IF(p, p->value() > 0);
michael@0 116 return p;
michael@0 117 }
michael@0 118
michael@0 119 private:
michael@0 120 /* Override sweep method to also update our edge cache. */
michael@0 121 void sweep() {
michael@0 122 for (Enum e(*static_cast<Base *>(this)); !e.empty(); e.popFront()) {
michael@0 123 Key k(e.front().key());
michael@0 124 if (gc::IsAboutToBeFinalized(&k)) {
michael@0 125 e.removeFront();
michael@0 126 decZoneCount(k->zone());
michael@0 127 }
michael@0 128 }
michael@0 129 Base::assertEntriesNotAboutToBeFinalized();
michael@0 130 }
michael@0 131
michael@0 132 bool incZoneCount(JS::Zone *zone) {
michael@0 133 CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0);
michael@0 134 if (!p)
michael@0 135 return false;
michael@0 136 ++p->value();
michael@0 137 return true;
michael@0 138 }
michael@0 139
michael@0 140 void decZoneCount(JS::Zone *zone) {
michael@0 141 CountMap::Ptr p = zoneCounts.lookup(zone);
michael@0 142 JS_ASSERT(p);
michael@0 143 JS_ASSERT(p->value() > 0);
michael@0 144 --p->value();
michael@0 145 if (p->value() == 0)
michael@0 146 zoneCounts.remove(zone);
michael@0 147 }
michael@0 148 };
michael@0 149
michael@0 150 /*
michael@0 151 * Env is the type of what ES5 calls "lexical environments" (runtime
michael@0 152 * activations of lexical scopes). This is currently just JSObject, and is
michael@0 153 * implemented by Call, Block, With, and DeclEnv objects, among others--but
michael@0 154 * environments and objects are really two different concepts.
michael@0 155 */
michael@0 156 typedef JSObject Env;
michael@0 157
michael@0 158 class Debugger : private mozilla::LinkedListElement<Debugger>
michael@0 159 {
michael@0 160 friend class Breakpoint;
michael@0 161 friend class mozilla::LinkedListElement<Debugger>;
michael@0 162 friend bool (::JS_DefineDebuggerObject)(JSContext *cx, JS::HandleObject obj);
michael@0 163
michael@0 164 public:
michael@0 165 enum Hook {
michael@0 166 OnDebuggerStatement,
michael@0 167 OnExceptionUnwind,
michael@0 168 OnNewScript,
michael@0 169 OnEnterFrame,
michael@0 170 OnNewGlobalObject,
michael@0 171 HookCount
michael@0 172 };
michael@0 173 enum {
michael@0 174 JSSLOT_DEBUG_PROTO_START,
michael@0 175 JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START,
michael@0 176 JSSLOT_DEBUG_ENV_PROTO,
michael@0 177 JSSLOT_DEBUG_OBJECT_PROTO,
michael@0 178 JSSLOT_DEBUG_SCRIPT_PROTO,
michael@0 179 JSSLOT_DEBUG_SOURCE_PROTO,
michael@0 180 JSSLOT_DEBUG_MEMORY_PROTO,
michael@0 181 JSSLOT_DEBUG_PROTO_STOP,
michael@0 182 JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP,
michael@0 183 JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
michael@0 184 JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP,
michael@0 185 JSSLOT_DEBUG_COUNT
michael@0 186 };
michael@0 187 private:
michael@0 188 HeapPtrObject object; /* The Debugger object. Strong reference. */
michael@0 189 GlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */
michael@0 190 js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
michael@0 191 bool enabled;
michael@0 192 JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */
michael@0 193
michael@0 194 /*
michael@0 195 * If this Debugger is enabled, and has a onNewGlobalObject handler, then
michael@0 196 * this link is inserted into the circular list headed by
michael@0 197 * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a
michael@0 198 * singleton cycle.
michael@0 199 */
michael@0 200 JSCList onNewGlobalObjectWatchersLink;
michael@0 201
michael@0 202 /*
michael@0 203 * Map from stack frames that are currently on the stack to Debugger.Frame
michael@0 204 * instances.
michael@0 205 *
michael@0 206 * The keys are always live stack frames. We drop them from this map as
michael@0 207 * soon as they leave the stack (see slowPathOnLeaveFrame) and in
michael@0 208 * removeDebuggee.
michael@0 209 *
michael@0 210 * We don't trace the keys of this map (the frames are on the stack and
michael@0 211 * thus necessarily live), but we do trace the values. It's like a WeakMap
michael@0 212 * that way, but since stack frames are not gc-things, the implementation
michael@0 213 * has to be different.
michael@0 214 */
michael@0 215 typedef HashMap<AbstractFramePtr,
michael@0 216 RelocatablePtrObject,
michael@0 217 DefaultHasher<AbstractFramePtr>,
michael@0 218 RuntimeAllocPolicy> FrameMap;
michael@0 219 FrameMap frames;
michael@0 220
michael@0 221 /* An ephemeral map from JSScript* to Debugger.Script instances. */
michael@0 222 typedef DebuggerWeakMap<EncapsulatedPtrScript, RelocatablePtrObject> ScriptWeakMap;
michael@0 223 ScriptWeakMap scripts;
michael@0 224
michael@0 225 /* The map from debuggee source script objects to their Debugger.Source instances. */
michael@0 226 typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject, true> SourceWeakMap;
michael@0 227 SourceWeakMap sources;
michael@0 228
michael@0 229 /* The map from debuggee objects to their Debugger.Object instances. */
michael@0 230 typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap;
michael@0 231 ObjectWeakMap objects;
michael@0 232
michael@0 233 /* The map from debuggee Envs to Debugger.Environment instances. */
michael@0 234 ObjectWeakMap environments;
michael@0 235
michael@0 236 class FrameRange;
michael@0 237 class ScriptQuery;
michael@0 238
michael@0 239 bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj);
michael@0 240 bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj,
michael@0 241 AutoDebugModeInvalidation &invalidate);
michael@0 242 void cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
michael@0 243 AutoDebugModeInvalidation &invalidate,
michael@0 244 GlobalObjectSet::Enum *compartmentEnum,
michael@0 245 GlobalObjectSet::Enum *debugEnu);
michael@0 246 bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
michael@0 247 GlobalObjectSet::Enum *compartmentEnum,
michael@0 248 GlobalObjectSet::Enum *debugEnum);
michael@0 249 bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
michael@0 250 AutoDebugModeInvalidation &invalidate,
michael@0 251 GlobalObjectSet::Enum *compartmentEnum,
michael@0 252 GlobalObjectSet::Enum *debugEnum);
michael@0 253 void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
michael@0 254 GlobalObjectSet::Enum *compartmentEnum,
michael@0 255 GlobalObjectSet::Enum *debugEnum);
michael@0 256 void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
michael@0 257 AutoDebugModeInvalidation &invalidate,
michael@0 258 GlobalObjectSet::Enum *compartmentEnum,
michael@0 259 GlobalObjectSet::Enum *debugEnum);
michael@0 260
michael@0 261 /*
michael@0 262 * Cope with an error or exception in a debugger hook.
michael@0 263 *
michael@0 264 * If callHook is true, then call the uncaughtExceptionHook, if any. If, in
michael@0 265 * addition, vp is given, then parse the value returned by
michael@0 266 * uncaughtExceptionHook as a resumption value.
michael@0 267 *
michael@0 268 * If there is no uncaughtExceptionHook, or if it fails, report and clear
michael@0 269 * the pending exception on ac.context and return JSTRAP_ERROR.
michael@0 270 *
michael@0 271 * This always calls ac.leave(); ac is a parameter because this method must
michael@0 272 * do some things in the debugger compartment and some things in the
michael@0 273 * debuggee compartment.
michael@0 274 */
michael@0 275 JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment> &ac, bool callHook);
michael@0 276 JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment> &ac, MutableHandleValue vp, bool callHook);
michael@0 277
michael@0 278 JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment> &ac,
michael@0 279 MutableHandleValue *vp, bool callHook);
michael@0 280
michael@0 281 /*
michael@0 282 * Handle the result of a hook that is expected to return a resumption
michael@0 283 * value <https://wiki.mozilla.org/Debugger#Resumption_Values>. This is called
michael@0 284 * when we return from a debugging hook to debuggee code. The interpreter wants
michael@0 285 * a (JSTrapStatus, Value) pair telling it how to proceed.
michael@0 286 *
michael@0 287 * Precondition: ac is entered. We are in the debugger compartment.
michael@0 288 *
michael@0 289 * Postcondition: This called ac.leave(). See handleUncaughtException.
michael@0 290 *
michael@0 291 * If ok is false, the hook failed. If an exception is pending in
michael@0 292 * ac.context(), return handleUncaughtException(ac, vp, callhook).
michael@0 293 * Otherwise just return JSTRAP_ERROR.
michael@0 294 *
michael@0 295 * If ok is true, there must be no exception pending in ac.context(). rv may be:
michael@0 296 * undefined - Return JSTRAP_CONTINUE to continue execution normally.
michael@0 297 * {return: value} or {throw: value} - Call unwrapDebuggeeValue to
michael@0 298 * unwrap value. Store the result in *vp and return JSTRAP_RETURN
michael@0 299 * or JSTRAP_THROW. The interpreter will force the current frame to
michael@0 300 * return or throw an exception.
michael@0 301 * null - Return JSTRAP_ERROR to terminate the debuggee with an
michael@0 302 * uncatchable error.
michael@0 303 * anything else - Make a new TypeError the pending exception and
michael@0 304 * return handleUncaughtException(ac, vp, callHook).
michael@0 305 */
michael@0 306 JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment> &ac, bool ok, const Value &rv,
michael@0 307 MutableHandleValue vp, bool callHook = true);
michael@0 308
michael@0 309 GlobalObject *unwrapDebuggeeArgument(JSContext *cx, const Value &v);
michael@0 310
michael@0 311 static void traceObject(JSTracer *trc, JSObject *obj);
michael@0 312 void trace(JSTracer *trc);
michael@0 313 static void finalize(FreeOp *fop, JSObject *obj);
michael@0 314 void markKeysInCompartment(JSTracer *tracer);
michael@0 315
michael@0 316 static const Class jsclass;
michael@0 317
michael@0 318 static Debugger *fromThisValue(JSContext *cx, const CallArgs &ca, const char *fnname);
michael@0 319 static bool getEnabled(JSContext *cx, unsigned argc, Value *vp);
michael@0 320 static bool setEnabled(JSContext *cx, unsigned argc, Value *vp);
michael@0 321 static bool getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which);
michael@0 322 static bool setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which);
michael@0 323 static bool getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp);
michael@0 324 static bool setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp);
michael@0 325 static bool getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp);
michael@0 326 static bool setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp);
michael@0 327 static bool getOnNewScript(JSContext *cx, unsigned argc, Value *vp);
michael@0 328 static bool setOnNewScript(JSContext *cx, unsigned argc, Value *vp);
michael@0 329 static bool getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp);
michael@0 330 static bool setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp);
michael@0 331 static bool getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp);
michael@0 332 static bool setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp);
michael@0 333 static bool getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp);
michael@0 334 static bool setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp);
michael@0 335 static bool getMemory(JSContext *cx, unsigned argc, Value *vp);
michael@0 336 static bool addDebuggee(JSContext *cx, unsigned argc, Value *vp);
michael@0 337 static bool addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp);
michael@0 338 static bool removeDebuggee(JSContext *cx, unsigned argc, Value *vp);
michael@0 339 static bool removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp);
michael@0 340 static bool hasDebuggee(JSContext *cx, unsigned argc, Value *vp);
michael@0 341 static bool getDebuggees(JSContext *cx, unsigned argc, Value *vp);
michael@0 342 static bool getNewestFrame(JSContext *cx, unsigned argc, Value *vp);
michael@0 343 static bool clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp);
michael@0 344 static bool findScripts(JSContext *cx, unsigned argc, Value *vp);
michael@0 345 static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp);
michael@0 346 static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp);
michael@0 347 static bool construct(JSContext *cx, unsigned argc, Value *vp);
michael@0 348 static const JSPropertySpec properties[];
michael@0 349 static const JSFunctionSpec methods[];
michael@0 350
michael@0 351 JSObject *getHook(Hook hook) const;
michael@0 352 bool hasAnyLiveHooks() const;
michael@0 353
michael@0 354 static JSTrapStatus slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame,
michael@0 355 MutableHandleValue vp);
michael@0 356 static bool slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok);
michael@0 357 static void slowPathOnNewScript(JSContext *cx, HandleScript script,
michael@0 358 GlobalObject *compileAndGoGlobal);
michael@0 359 static void slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global);
michael@0 360 static JSTrapStatus dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which);
michael@0 361
michael@0 362 JSTrapStatus fireDebuggerStatement(JSContext *cx, MutableHandleValue vp);
michael@0 363 JSTrapStatus fireExceptionUnwind(JSContext *cx, MutableHandleValue vp);
michael@0 364 JSTrapStatus fireEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp);
michael@0 365 JSTrapStatus fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, MutableHandleValue vp);
michael@0 366
michael@0 367 /*
michael@0 368 * Allocate and initialize a Debugger.Script instance whose referent is
michael@0 369 * |script|.
michael@0 370 */
michael@0 371 JSObject *newDebuggerScript(JSContext *cx, HandleScript script);
michael@0 372
michael@0 373 /*
michael@0 374 * Allocate and initialize a Debugger.Source instance whose referent is
michael@0 375 * |source|.
michael@0 376 */
michael@0 377 JSObject *newDebuggerSource(JSContext *cx, js::HandleScriptSource source);
michael@0 378
michael@0 379 /*
michael@0 380 * Receive a "new script" event from the engine. A new script was compiled
michael@0 381 * or deserialized.
michael@0 382 */
michael@0 383 void fireNewScript(JSContext *cx, HandleScript script);
michael@0 384
michael@0 385 /*
michael@0 386 * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
michael@0 387 * its data if we need to make a new Debugger.Frame.
michael@0 388 */
michael@0 389 bool getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame,
michael@0 390 const ScriptFrameIter *maybeIter, MutableHandleValue vp);
michael@0 391
michael@0 392 inline Breakpoint *firstBreakpoint() const;
michael@0 393
michael@0 394 static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link);
michael@0 395
michael@0 396 static bool replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to,
michael@0 397 ScriptFrameIter &iter);
michael@0 398
michael@0 399 public:
michael@0 400 Debugger(JSContext *cx, JSObject *dbg);
michael@0 401 ~Debugger();
michael@0 402
michael@0 403 bool init(JSContext *cx);
michael@0 404 inline const js::HeapPtrObject &toJSObject() const;
michael@0 405 inline js::HeapPtrObject &toJSObjectRef();
michael@0 406 static inline Debugger *fromJSObject(JSObject *obj);
michael@0 407 static Debugger *fromChildJSObject(JSObject *obj);
michael@0 408
michael@0 409 /*********************************** Methods for interaction with the GC. */
michael@0 410
michael@0 411 /*
michael@0 412 * A Debugger object is live if:
michael@0 413 * * the Debugger JSObject is live (Debugger::trace handles this case); OR
michael@0 414 * * it is in the middle of dispatching an event (the event dispatching
michael@0 415 * code roots it in this case); OR
michael@0 416 * * it is enabled, and it is debugging at least one live compartment,
michael@0 417 * and at least one of the following is true:
michael@0 418 * - it has a debugger hook installed
michael@0 419 * - it has a breakpoint set on a live script
michael@0 420 * - it has a watchpoint set on a live object.
michael@0 421 *
michael@0 422 * Debugger::markAllIteratively handles the last case. If it finds any
michael@0 423 * Debugger objects that are definitely live but not yet marked, it marks
michael@0 424 * them and returns true. If not, it returns false.
michael@0 425 */
michael@0 426 static void markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer);
michael@0 427 static bool markAllIteratively(GCMarker *trc);
michael@0 428 static void markAll(JSTracer *trc);
michael@0 429 static void sweepAll(FreeOp *fop);
michael@0 430 static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global,
michael@0 431 GlobalObjectSet::Enum *compartmentEnum);
michael@0 432 static void findCompartmentEdges(JS::Zone *v, gc::ComponentFinder<JS::Zone> &finder);
michael@0 433
michael@0 434 static inline JSTrapStatus onEnterFrame(JSContext *cx, AbstractFramePtr frame,
michael@0 435 MutableHandleValue vp);
michael@0 436 static inline bool onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok);
michael@0 437 static inline JSTrapStatus onDebuggerStatement(JSContext *cx, MutableHandleValue vp);
michael@0 438 static inline JSTrapStatus onExceptionUnwind(JSContext *cx, MutableHandleValue vp);
michael@0 439 static inline void onNewScript(JSContext *cx, HandleScript script,
michael@0 440 GlobalObject *compileAndGoGlobal);
michael@0 441 static inline void onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global);
michael@0 442 static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp);
michael@0 443 static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp);
michael@0 444 static bool handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to);
michael@0 445 static bool handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to);
michael@0 446
michael@0 447 /************************************* Functions for use by Debugger.cpp. */
michael@0 448
michael@0 449 inline bool observesEnterFrame() const;
michael@0 450 inline bool observesNewScript() const;
michael@0 451 inline bool observesNewGlobalObject() const;
michael@0 452 inline bool observesGlobal(GlobalObject *global) const;
michael@0 453 bool observesFrame(AbstractFramePtr frame) const;
michael@0 454 bool observesFrame(const ScriptFrameIter &iter) const;
michael@0 455 bool observesScript(JSScript *script) const;
michael@0 456
michael@0 457 /*
michael@0 458 * If env is nullptr, call vp->setNull() and return true. Otherwise, find
michael@0 459 * or create a Debugger.Environment object for the given Env. On success,
michael@0 460 * store the Environment object in *vp and return true.
michael@0 461 */
michael@0 462 bool wrapEnvironment(JSContext *cx, Handle<Env*> env, MutableHandleValue vp);
michael@0 463
michael@0 464 /*
michael@0 465 * Like cx->compartment()->wrap(cx, vp), but for the debugger compartment.
michael@0 466 *
michael@0 467 * Preconditions: *vp is a value from a debuggee compartment; cx is in the
michael@0 468 * debugger's compartment.
michael@0 469 *
michael@0 470 * If *vp is an object, this produces a (new or existing) Debugger.Object
michael@0 471 * wrapper for it. Otherwise this is the same as JSCompartment::wrap.
michael@0 472 *
michael@0 473 * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object
michael@0 474 * of the form { optimizedOut: true }.
michael@0 475 *
michael@0 476 * If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing
michael@0 477 * arguments, this produces a plain object of the form { missingArguments:
michael@0 478 * true }.
michael@0 479 */
michael@0 480 bool wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp);
michael@0 481
michael@0 482 /*
michael@0 483 * Unwrap a Debug.Object, without rewrapping it for any particular debuggee
michael@0 484 * compartment.
michael@0 485 *
michael@0 486 * Preconditions: cx is in the debugger compartment. *vp is a value in that
michael@0 487 * compartment. (*vp should be a "debuggee value", meaning it is the
michael@0 488 * debugger's reflection of a value in the debuggee.)
michael@0 489 *
michael@0 490 * If *vp is a Debugger.Object, store the referent in *vp. Otherwise, if *vp
michael@0 491 * is an object, throw a TypeError, because it is not a debuggee
michael@0 492 * value. Otherwise *vp is a primitive, so leave it alone.
michael@0 493 *
michael@0 494 * When passing values from the debuggee to the debugger:
michael@0 495 * enter debugger compartment;
michael@0 496 * call wrapDebuggeeValue; // compartment- and debugger-wrapping
michael@0 497 *
michael@0 498 * When passing values from the debugger to the debuggee:
michael@0 499 * call unwrapDebuggeeValue; // debugger-unwrapping
michael@0 500 * enter debuggee compartment;
michael@0 501 * call cx->compartment()->wrap; // compartment-rewrapping
michael@0 502 *
michael@0 503 * (Extreme nerd sidebar: Unwrapping happens in two steps because there are
michael@0 504 * two different kinds of symmetry at work: regardless of which direction
michael@0 505 * we're going, we want any exceptions to be created and thrown in the
michael@0 506 * debugger compartment--mirror symmetry. But compartment wrapping always
michael@0 507 * happens in the target compartment--rotational symmetry.)
michael@0 508 */
michael@0 509 bool unwrapDebuggeeValue(JSContext *cx, MutableHandleValue vp);
michael@0 510
michael@0 511 /*
michael@0 512 * Store the Debugger.Frame object for frame in *vp.
michael@0 513 *
michael@0 514 * Use this if you have already access to a frame pointer without having
michael@0 515 * to incur the cost of walking the stack.
michael@0 516 */
michael@0 517 bool getScriptFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) {
michael@0 518 return getScriptFrameWithIter(cx, frame, nullptr, vp);
michael@0 519 }
michael@0 520
michael@0 521 /*
michael@0 522 * Store the Debugger.Frame object for iter in *vp. Eagerly copies a
michael@0 523 * ScriptFrameIter::Data.
michael@0 524 *
michael@0 525 * Use this if you had to make a ScriptFrameIter to get the required
michael@0 526 * frame, in which case the cost of walking the stack has already been
michael@0 527 * paid.
michael@0 528 */
michael@0 529 bool getScriptFrame(JSContext *cx, const ScriptFrameIter &iter, MutableHandleValue vp) {
michael@0 530 return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);
michael@0 531 }
michael@0 532
michael@0 533 /*
michael@0 534 * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a
michael@0 535 * standard SpiderMonkey call state: a boolean success value |ok|, a return
michael@0 536 * value |rv|, and a context |cx| that may or may not have an exception set.
michael@0 537 * If an exception was pending on |cx|, it is cleared (and |ok| is asserted
michael@0 538 * to be false).
michael@0 539 */
michael@0 540 static void resultToCompletion(JSContext *cx, bool ok, const Value &rv,
michael@0 541 JSTrapStatus *status, MutableHandleValue value);
michael@0 542
michael@0 543 /*
michael@0 544 * Set |*result| to a JavaScript completion value corresponding to |status|
michael@0 545 * and |value|. |value| should be the return value or exception value, not
michael@0 546 * wrapped as a debuggee value. |cx| must be in the debugger compartment.
michael@0 547 */
michael@0 548 bool newCompletionValue(JSContext *cx, JSTrapStatus status, Value value,
michael@0 549 MutableHandleValue result);
michael@0 550
michael@0 551 /*
michael@0 552 * Precondition: we are in the debuggee compartment (ac is entered) and ok
michael@0 553 * is true if the operation in the debuggee compartment succeeded, false on
michael@0 554 * error or exception.
michael@0 555 *
michael@0 556 * Postcondition: we are in the debugger compartment, having called
michael@0 557 * ac.leave() even if an error occurred.
michael@0 558 *
michael@0 559 * On success, a completion value is in vp and ac.context does not have a
michael@0 560 * pending exception. (This ordinarily returns true even if the ok argument
michael@0 561 * is false.)
michael@0 562 */
michael@0 563 bool receiveCompletionValue(mozilla::Maybe<AutoCompartment> &ac, bool ok,
michael@0 564 HandleValue val,
michael@0 565 MutableHandleValue vp);
michael@0 566
michael@0 567 /*
michael@0 568 * Return the Debugger.Script object for |script|, or create a new one if
michael@0 569 * needed. The context |cx| must be in the debugger compartment; |script|
michael@0 570 * must be a script in a debuggee compartment.
michael@0 571 */
michael@0 572 JSObject *wrapScript(JSContext *cx, HandleScript script);
michael@0 573
michael@0 574 /*
michael@0 575 * Return the Debugger.Source object for |source|, or create a new one if
michael@0 576 * needed. The context |cx| must be in the debugger compartment; |source|
michael@0 577 * must be a script source object in a debuggee compartment.
michael@0 578 */
michael@0 579 JSObject *wrapSource(JSContext *cx, js::HandleScriptSource source);
michael@0 580
michael@0 581 private:
michael@0 582 Debugger(const Debugger &) MOZ_DELETE;
michael@0 583 Debugger & operator=(const Debugger &) MOZ_DELETE;
michael@0 584 };
michael@0 585
michael@0 586 class BreakpointSite {
michael@0 587 friend class Breakpoint;
michael@0 588 friend struct ::JSCompartment;
michael@0 589 friend class ::JSScript;
michael@0 590 friend class Debugger;
michael@0 591
michael@0 592 public:
michael@0 593 JSScript *script;
michael@0 594 jsbytecode * const pc;
michael@0 595
michael@0 596 private:
michael@0 597 JSCList breakpoints; /* cyclic list of all js::Breakpoints at this instruction */
michael@0 598 size_t enabledCount; /* number of breakpoints in the list that are enabled */
michael@0 599 JSTrapHandler trapHandler; /* trap state */
michael@0 600 HeapValue trapClosure;
michael@0 601
michael@0 602 void recompile(FreeOp *fop);
michael@0 603
michael@0 604 public:
michael@0 605 BreakpointSite(JSScript *script, jsbytecode *pc);
michael@0 606 Breakpoint *firstBreakpoint() const;
michael@0 607 bool hasBreakpoint(Breakpoint *bp);
michael@0 608 bool hasTrap() const { return !!trapHandler; }
michael@0 609
michael@0 610 void inc(FreeOp *fop);
michael@0 611 void dec(FreeOp *fop);
michael@0 612 void setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure);
michael@0 613 void clearTrap(FreeOp *fop, JSTrapHandler *handlerp = nullptr, Value *closurep = nullptr);
michael@0 614 void destroyIfEmpty(FreeOp *fop);
michael@0 615 };
michael@0 616
michael@0 617 /*
michael@0 618 * Each Breakpoint is a member of two linked lists: its debugger's list and its
michael@0 619 * site's list.
michael@0 620 *
michael@0 621 * GC rules:
michael@0 622 * - script is live, breakpoint exists, and debugger is enabled
michael@0 623 * ==> debugger is live
michael@0 624 * - script is live, breakpoint exists, and debugger is live
michael@0 625 * ==> retain the breakpoint and the handler object is live
michael@0 626 *
michael@0 627 * Debugger::markAllIteratively implements these two rules. It uses
michael@0 628 * Debugger::hasAnyLiveHooks to check for rule 1.
michael@0 629 *
michael@0 630 * Nothing else causes a breakpoint to be retained, so if its script or
michael@0 631 * debugger is collected, the breakpoint is destroyed during GC sweep phase,
michael@0 632 * even if the debugger compartment isn't being GC'd. This is implemented in
michael@0 633 * JSCompartment::sweepBreakpoints.
michael@0 634 */
michael@0 635 class Breakpoint {
michael@0 636 friend struct ::JSCompartment;
michael@0 637 friend class Debugger;
michael@0 638
michael@0 639 public:
michael@0 640 Debugger * const debugger;
michael@0 641 BreakpointSite * const site;
michael@0 642 private:
michael@0 643 /* |handler| is marked unconditionally during minor GC. */
michael@0 644 js::EncapsulatedPtrObject handler;
michael@0 645 JSCList debuggerLinks;
michael@0 646 JSCList siteLinks;
michael@0 647
michael@0 648 public:
michael@0 649 static Breakpoint *fromDebuggerLinks(JSCList *links);
michael@0 650 static Breakpoint *fromSiteLinks(JSCList *links);
michael@0 651 Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler);
michael@0 652 void destroy(FreeOp *fop);
michael@0 653 Breakpoint *nextInDebugger();
michael@0 654 Breakpoint *nextInSite();
michael@0 655 const EncapsulatedPtrObject &getHandler() const { return handler; }
michael@0 656 EncapsulatedPtrObject &getHandlerRef() { return handler; }
michael@0 657 };
michael@0 658
michael@0 659 Breakpoint *
michael@0 660 Debugger::firstBreakpoint() const
michael@0 661 {
michael@0 662 if (JS_CLIST_IS_EMPTY(&breakpoints))
michael@0 663 return nullptr;
michael@0 664 return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints));
michael@0 665 }
michael@0 666
michael@0 667 Debugger *
michael@0 668 Debugger::fromOnNewGlobalObjectWatchersLink(JSCList *link) {
michael@0 669 char *p = reinterpret_cast<char *>(link);
michael@0 670 return reinterpret_cast<Debugger *>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink));
michael@0 671 }
michael@0 672
michael@0 673 const js::HeapPtrObject &
michael@0 674 Debugger::toJSObject() const
michael@0 675 {
michael@0 676 JS_ASSERT(object);
michael@0 677 return object;
michael@0 678 }
michael@0 679
michael@0 680 js::HeapPtrObject &
michael@0 681 Debugger::toJSObjectRef()
michael@0 682 {
michael@0 683 JS_ASSERT(object);
michael@0 684 return object;
michael@0 685 }
michael@0 686
michael@0 687 bool
michael@0 688 Debugger::observesEnterFrame() const
michael@0 689 {
michael@0 690 return enabled && getHook(OnEnterFrame);
michael@0 691 }
michael@0 692
michael@0 693 bool
michael@0 694 Debugger::observesNewScript() const
michael@0 695 {
michael@0 696 return enabled && getHook(OnNewScript);
michael@0 697 }
michael@0 698
michael@0 699 bool
michael@0 700 Debugger::observesNewGlobalObject() const
michael@0 701 {
michael@0 702 return enabled && getHook(OnNewGlobalObject);
michael@0 703 }
michael@0 704
michael@0 705 bool
michael@0 706 Debugger::observesGlobal(GlobalObject *global) const
michael@0 707 {
michael@0 708 return debuggees.has(global);
michael@0 709 }
michael@0 710
michael@0 711 JSTrapStatus
michael@0 712 Debugger::onEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp)
michael@0 713 {
michael@0 714 if (cx->compartment()->getDebuggees().empty())
michael@0 715 return JSTRAP_CONTINUE;
michael@0 716 return slowPathOnEnterFrame(cx, frame, vp);
michael@0 717 }
michael@0 718
michael@0 719 JSTrapStatus
michael@0 720 Debugger::onDebuggerStatement(JSContext *cx, MutableHandleValue vp)
michael@0 721 {
michael@0 722 return cx->compartment()->getDebuggees().empty()
michael@0 723 ? JSTRAP_CONTINUE
michael@0 724 : dispatchHook(cx, vp, OnDebuggerStatement);
michael@0 725 }
michael@0 726
michael@0 727 JSTrapStatus
michael@0 728 Debugger::onExceptionUnwind(JSContext *cx, MutableHandleValue vp)
michael@0 729 {
michael@0 730 return cx->compartment()->getDebuggees().empty()
michael@0 731 ? JSTRAP_CONTINUE
michael@0 732 : dispatchHook(cx, vp, OnExceptionUnwind);
michael@0 733 }
michael@0 734
michael@0 735 void
michael@0 736 Debugger::onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal)
michael@0 737 {
michael@0 738 JS_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal);
michael@0 739 JS_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal == &script->uninlinedGlobal());
michael@0 740 // We early return in slowPathOnNewScript for self-hosted scripts, so we can
michael@0 741 // ignore those in our assertion here.
michael@0 742 JS_ASSERT_IF(!script->compartment()->options().invisibleToDebugger() &&
michael@0 743 !script->selfHosted(),
michael@0 744 script->compartment()->firedOnNewGlobalObject);
michael@0 745 JS_ASSERT_IF(!script->compileAndGo(), !compileAndGoGlobal);
michael@0 746 if (!script->compartment()->getDebuggees().empty())
michael@0 747 slowPathOnNewScript(cx, script, compileAndGoGlobal);
michael@0 748 }
michael@0 749
michael@0 750 void
michael@0 751 Debugger::onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global)
michael@0 752 {
michael@0 753 JS_ASSERT(!global->compartment()->firedOnNewGlobalObject);
michael@0 754 #ifdef DEBUG
michael@0 755 global->compartment()->firedOnNewGlobalObject = true;
michael@0 756 #endif
michael@0 757 if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers))
michael@0 758 Debugger::slowPathOnNewGlobalObject(cx, global);
michael@0 759 }
michael@0 760
michael@0 761 extern bool
michael@0 762 EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
michael@0 763 ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno,
michael@0 764 MutableHandleValue rval);
michael@0 765
michael@0 766 }
michael@0 767
michael@0 768 #endif /* vm_Debugger_h */

mercurial