summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/DebugAPI.h
blob: c821dd412fec127cdac09d2138904799eaf3dedf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef debugger_DebugAPI_h
#define debugger_DebugAPI_h

#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/Realm.h"

namespace js {

// This file contains the API which SpiderMonkey should use to interact with any
// active Debuggers.

class AbstractGeneratorObject;
class DebugScriptMap;
class PromiseObject;

namespace gc {
class AutoSuppressGC;
}  // namespace gc

/**
 * DebugAPI::onNativeCall allows the debugger to call callbacks just before
 * some native functions are to be executed. It also allows the hooks
 * themselves to affect the result of the call. This enum represents the
 * various affects that DebugAPI::onNativeCall may perform.
 */
enum class NativeResumeMode {
  /**
   * If the debugger hook did not return a value to manipulate the result of
   * the native call, execution can continue unchanged.
   *
   * Continue indicates that the native function should execute normally.
   */
  Continue,

  /**
   * If the debugger hook returned an explicit return value that is meant to
   * take the place of the native call's result, execution of the native
   * function needs to be skipped in favor of the explicit result.
   *
   * Override indicates that the native function should be skipped and that
   * the debugger has already stored the return value into the CallArgs.
   */
  Override,

  /**
   * If the debugger hook returns an explicit termination or an explicit
   * thrown exception, execution of the native function needs to be skipped
   * in favor of handling the error condition.
   *
   * Abort indicates that the native function should be skipped and that
   * execution should be terminated. The debugger may or may not have set a
   * pending exception.
   */
  Abort,
};

class DebugScript;
class DebuggerVector;

class DebugAPI {
 public:
  friend class Debugger;

  /*** Methods for interaction with the GC. ***********************************/

  /*
   * Trace (inferred) owning edges from stack frames to Debugger.Frames, as part
   * of root marking.
   *
   * Even if a Debugger.Frame for a live stack frame is entirely unreachable
   * from JS, if it has onStep or onPop hooks set, then collecting it would have
   * observable side effects - namely, the hooks would fail to run. The effect
   * is the same as if the stack frame held an owning edge to its
   * Debugger.Frame.
   *
   * Debugger.Frames must also be retained if the Debugger to which they belong
   * is reachable, even if they have no hooks set, but we handle that elsewhere;
   * this function is only concerned with the inferred roots from stack frames
   * to Debugger.Frames that have hooks set.
   */
  static void traceFramesWithLiveHooks(JSTracer* tracer);

  /*
   * Trace (inferred) owning edges from generator objects to Debugger.Frames.
   *
   * Even if a Debugger.Frame for a live suspended generator object is entirely
   * unreachable from JS, if it has onStep or onPop hooks set, then collecting
   * it would have observable side effects - namely, the hooks would fail to run
   * if the generator is resumed. The effect is the same as if the generator
   * object held an owning edge to its Debugger.Frame.
   */
  static inline void traceGeneratorFrame(JSTracer* tracer,
                                         AbstractGeneratorObject* generator);

  // Trace cross compartment edges in all debuggers relevant to the current GC.
  static void traceCrossCompartmentEdges(JSTracer* tracer);

  // Trace all debugger-owned GC things unconditionally, during a moving GC.
  static void traceAllForMovingGC(JSTracer* trc);

  // Trace the debug script map.  Called as part of tracing a zone's roots.
  static void traceDebugScriptMap(JSTracer* trc, DebugScriptMap* map);

  static void traceFromRealm(JSTracer* trc, Realm* realm);

  // The garbage collector calls this after everything has been marked, but
  // before anything has been finalized. We use this to clear Debugger /
  // debuggee edges at a point where the parties concerned are all still
  // initialized. This does not update edges to moved GC things which is handled
  // via the other trace methods.
  static void sweepAll(JS::GCContext* gcx);

  // Add sweep group edges due to the presence of any debuggers.
  [[nodiscard]] static bool findSweepGroupEdges(JSRuntime* rt);

  // Remove the debugging information associated with a script.
  static void removeDebugScript(JS::GCContext* gcx, JSScript* script);

  // Delete a Zone's debug script map. Called when a zone is destroyed.
  static void deleteDebugScriptMap(DebugScriptMap* map);

  // Validate the debugging information in a script after a moving GC>
#ifdef JSGC_HASH_TABLE_CHECKS
  static void checkDebugScriptAfterMovingGC(DebugScript* ds);
#endif

#ifdef DEBUG
  static bool edgeIsInDebuggerWeakmap(JSRuntime* rt, JSObject* src,
                                      JS::GCCellPtr dst);
#endif

  /*** Methods for querying script breakpoint state. **************************/

  // Query information about whether any debuggers are observing a script.
  static inline bool stepModeEnabled(JSScript* script);
  static inline bool hasBreakpointsAt(JSScript* script, jsbytecode* pc);
  static inline bool hasAnyBreakpointsOrStepMode(JSScript* script);

  /*** Methods for interacting with the JITs. *********************************/

  // Update Debugger frames when an interpreter frame is replaced with a
  // baseline frame.
  [[nodiscard]] static bool handleBaselineOsr(JSContext* cx,
                                              InterpreterFrame* from,
                                              jit::BaselineFrame* to);

  // Update Debugger frames when an Ion frame bails out and is replaced with a
  // baseline frame.
  [[nodiscard]] static bool handleIonBailout(JSContext* cx,
                                             jit::RematerializedFrame* from,
                                             jit::BaselineFrame* to);

  // Detach any Debugger frames from an Ion frame after an error occurred while
  // it bailed out.
  static void handleUnrecoverableIonBailoutError(
      JSContext* cx, jit::RematerializedFrame* frame);

  // When doing on-stack-replacement of a debuggee interpreter frame with a
  // baseline frame, ensure that the resulting frame can be observed by the
  // debugger.
  [[nodiscard]] static bool ensureExecutionObservabilityOfOsrFrame(
      JSContext* cx, AbstractFramePtr osrSourceFrame);

  // Describes a set of scripts or frames whose execution observability can
  // change due to debugger activity.
  class ExecutionObservableSet {
   public:
    using ZoneRange = HashSet<Zone*>::Range;

    virtual Zone* singleZone() const { return nullptr; }
    virtual JSScript* singleScriptForZoneInvalidation() const {
      return nullptr;
    }
    virtual const HashSet<Zone*>* zones() const { return nullptr; }

    virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
    virtual bool shouldMarkAsDebuggee(FrameIter& iter) const = 0;
  };

  // This enum is converted to and compare with bool values; NotObserving
  // must be 0 and Observing must be 1.
  enum IsObserving { NotObserving = 0, Observing = 1 };

  /*** Methods for calling installed debugger handlers. ***********************/

  // Called when a new script becomes accessible to debuggers.
  static void onNewScript(JSContext* cx, HandleScript script);

  // Called when a new wasm instance becomes accessible to debuggers.
  static inline void onNewWasmInstance(
      JSContext* cx, Handle<WasmInstanceObject*> wasmInstance);

  /*
   * Announce to the debugger that the context has entered a new JavaScript
   * frame, |frame|. Call whatever hooks have been registered to observe new
   * frames.
   */
  [[nodiscard]] static inline bool onEnterFrame(JSContext* cx,
                                                AbstractFramePtr frame);

  /*
   * Like onEnterFrame, but for resuming execution of a generator or async
   * function. `frame` is a new baseline or interpreter frame, but abstractly
   * it can be identified with a particular generator frame that was
   * suspended earlier.
   *
   * There is no separate user-visible Debugger.onResumeFrame hook; this
   * fires .onEnterFrame (again, since we're re-entering the frame).
   *
   * Unfortunately, the interpreter and the baseline JIT arrange for this to
   * be called in different ways. The interpreter calls it from JSOp::Resume,
   * immediately after pushing the resumed frame; the JIT calls it from
   * JSOp::AfterYield, just after the generator resumes. The difference
   * should not be user-visible.
   */
  [[nodiscard]] static inline bool onResumeFrame(JSContext* cx,
                                                 AbstractFramePtr frame);

  static inline NativeResumeMode onNativeCall(JSContext* cx,
                                              const CallArgs& args,
                                              CallReason reason);

  /*
   * Announce to the debugger a |debugger;| statement on has been
   * encountered on the youngest JS frame on |cx|. Call whatever hooks have
   * been registered to observe this.
   *
   * Note that this method is called for all |debugger;| statements,
   * regardless of the frame's debuggee-ness.
   */
  [[nodiscard]] static inline bool onDebuggerStatement(JSContext* cx,
                                                       AbstractFramePtr frame);

  /*
   * Announce to the debugger that an exception has been thrown and propagated
   * to |frame|. Call whatever hooks have been registered to observe this.
   */
  [[nodiscard]] static inline bool onExceptionUnwind(JSContext* cx,
                                                     AbstractFramePtr frame);

  /*
   * Announce to the debugger that the thread has exited a JavaScript frame,
   * |frame|. If |ok| is true, the frame is returning normally; if |ok| is
   * false, the frame is throwing an exception or terminating.
   *
   * Change cx's current exception and |frame|'s return value to reflect the
   * changes in behavior the hooks request, if any. Return the new error/success
   * value.
   *
   * This function may be called twice for the same outgoing frame; only the
   * first call has any effect. (Permitting double calls simplifies some
   * cases where an onPop handler's resumption value changes a return to a
   * throw, or vice versa: we can redirect to a complete copy of the
   * alternative path, containing its own call to onLeaveFrame.)
   */
  [[nodiscard]] static inline bool onLeaveFrame(JSContext* cx,
                                                AbstractFramePtr frame,
                                                const jsbytecode* pc, bool ok);

  // Call any breakpoint handlers for the current scripted location.
  [[nodiscard]] static bool onTrap(JSContext* cx);

  // Call any stepping handlers for the current scripted location.
  [[nodiscard]] static bool onSingleStep(JSContext* cx);

  // Notify any Debugger instances observing this promise's global that a new
  // promise was allocated.
  static inline void onNewPromise(JSContext* cx,
                                  Handle<PromiseObject*> promise);

  // Notify any Debugger instances observing this promise's global that the
  // promise has settled (ie, it has either been fulfilled or rejected). Note
  // that this is *not* equivalent to the promise resolution (ie, the promise's
  // fate getting locked in) because you can resolve a promise with another
  // pending promise, in which case neither promise has settled yet.
  //
  // This should never be called on the same promise more than once, because a
  // promise can only make the transition from unsettled to settled once.
  static inline void onPromiseSettled(JSContext* cx,
                                      Handle<PromiseObject*> promise);

  // Notify any Debugger instances that a new global object has been created.
  static inline void onNewGlobalObject(JSContext* cx,
                                       Handle<GlobalObject*> global);

  /*** Methods for querying installed debugger handlers. **********************/

  // Whether any debugger is observing execution in a global.
  static bool debuggerObservesAllExecution(GlobalObject* global);

  // Whether any debugger is observing JS execution coverage in a global.
  static bool debuggerObservesCoverage(GlobalObject* global);

  // Whether any Debugger is observing asm.js execution in a global.
  static bool debuggerObservesAsmJS(GlobalObject* global);

  // Whether any Debugger is observing WebAssembly execution in a global.
  static bool debuggerObservesWasm(GlobalObject* global);

  // Whether any Debugger is observing native function call.
  static bool debuggerObservesNativeCall(GlobalObject* global);

  /*
   * Return true if the given global is being observed by at least one
   * Debugger that is tracking allocations.
   */
  static bool isObservedByDebuggerTrackingAllocations(
      const GlobalObject& debuggee);

  // If any debuggers are tracking allocations for a global, return the
  // probability that a given allocation should be tracked. Nothing otherwise.
  static mozilla::Maybe<double> allocationSamplingProbability(
      GlobalObject* global);

  // Whether any debugger is observing exception unwinds in a realm.
  static bool hasExceptionUnwindHook(GlobalObject* global);

  // Whether any debugger is observing debugger statements in a realm.
  static bool hasDebuggerStatementHook(GlobalObject* global);

  /*** Assorted methods for interacting with the runtime. *********************/

  // Checks if the current compartment is allowed to execute code.
  [[nodiscard]] static inline bool checkNoExecute(JSContext* cx,
                                                  HandleScript script);

  /*
   * Announce to the debugger that a generator object has been created,
   * via JSOp::Generator.
   *
   * This does not fire user hooks, but it's needed for debugger bookkeeping.
   */
  [[nodiscard]] static inline bool onNewGenerator(
      JSContext* cx, AbstractFramePtr frame,
      Handle<AbstractGeneratorObject*> genObj);

  // If necessary, record an object that was just allocated for any observing
  // debuggers.
  [[nodiscard]] static inline bool onLogAllocationSite(
      JSContext* cx, JSObject* obj, Handle<SavedFrame*> frame,
      mozilla::TimeStamp when);

  // Announce to the debugger that a global object is being collected by the
  // specified major GC.
  static inline void notifyParticipatesInGC(GlobalObject* global,
                                            uint64_t majorGCNumber);

 private:
  static bool stepModeEnabledSlow(JSScript* script);
  static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc);
  static void slowPathOnNewGlobalObject(JSContext* cx,
                                        Handle<GlobalObject*> global);
  static void slowPathNotifyParticipatesInGC(uint64_t majorGCNumber,
                                             JS::Realm::DebuggerVector& dbgs,
                                             const JS::AutoRequireNoGC& nogc);
  [[nodiscard]] static bool slowPathOnLogAllocationSite(
      JSContext* cx, HandleObject obj, Handle<SavedFrame*> frame,
      mozilla::TimeStamp when, JS::Realm::DebuggerVector& dbgs,
      const gc::AutoSuppressGC& nogc);
  [[nodiscard]] static bool slowPathOnLeaveFrame(JSContext* cx,
                                                 AbstractFramePtr frame,
                                                 const jsbytecode* pc, bool ok);
  [[nodiscard]] static bool slowPathOnNewGenerator(
      JSContext* cx, AbstractFramePtr frame,
      Handle<AbstractGeneratorObject*> genObj);
  [[nodiscard]] static bool slowPathCheckNoExecute(JSContext* cx,
                                                   HandleScript script);
  [[nodiscard]] static bool slowPathOnEnterFrame(JSContext* cx,
                                                 AbstractFramePtr frame);
  [[nodiscard]] static bool slowPathOnResumeFrame(JSContext* cx,
                                                  AbstractFramePtr frame);
  static NativeResumeMode slowPathOnNativeCall(JSContext* cx,
                                               const CallArgs& args,
                                               CallReason reason);
  [[nodiscard]] static bool slowPathOnDebuggerStatement(JSContext* cx,
                                                        AbstractFramePtr frame);
  [[nodiscard]] static bool slowPathOnExceptionUnwind(JSContext* cx,
                                                      AbstractFramePtr frame);
  static void slowPathOnNewWasmInstance(
      JSContext* cx, Handle<WasmInstanceObject*> wasmInstance);
  static void slowPathOnNewPromise(JSContext* cx,
                                   Handle<PromiseObject*> promise);
  static void slowPathOnPromiseSettled(JSContext* cx,
                                       Handle<PromiseObject*> promise);
  static bool inFrameMaps(AbstractFramePtr frame);
  static void slowPathTraceGeneratorFrame(JSTracer* tracer,
                                          AbstractGeneratorObject* generator);
};

// Suppresses all debuggee NX checks, i.e., allow all execution. Used to allow
// certain whitelisted operations to execute code.
//
// WARNING
// WARNING Do not use this unless you know what you are doing!
// WARNING
class AutoSuppressDebuggeeNoExecuteChecks {
  EnterDebuggeeNoExecute** stack_;
  EnterDebuggeeNoExecute* prev_;

 public:
  explicit AutoSuppressDebuggeeNoExecuteChecks(JSContext* cx) {
    stack_ = &cx->noExecuteDebuggerTop.ref();
    prev_ = *stack_;
    *stack_ = nullptr;
  }

  ~AutoSuppressDebuggeeNoExecuteChecks() {
    MOZ_ASSERT(!*stack_);
    *stack_ = prev_;
  }
};

} /* namespace js */

#endif /* debugger_DebugAPI_h */