summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/Frame.h
blob: 44ee39d9db8f13cab3a77cc38157a670c6386f6c (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
/* -*- 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_Frame_h
#define debugger_Frame_h

#include "mozilla/Maybe.h"   // for Maybe
#include "mozilla/Range.h"   // for Range
#include "mozilla/Result.h"  // for Result

#include <stddef.h>  // for size_t

#include "NamespaceImports.h"   // for Value, MutableHandleValue, HandleObject
#include "debugger/DebugAPI.h"  // for ResumeMode
#include "debugger/Debugger.h"  // for ResumeMode, Handler, Debugger
#include "gc/Barrier.h"         // for HeapPtr
#include "vm/FrameIter.h"       // for FrameIter
#include "vm/JSObject.h"        // for JSObject
#include "vm/NativeObject.h"    // for NativeObject
#include "vm/Stack.h"           // for AbstractFramePtr

struct JS_PUBLIC_API JSContext;

namespace js {

class AbstractGeneratorObject;
class GlobalObject;

/*
 * An OnStepHandler represents a handler function that is called when a small
 * amount of progress is made in a frame.
 */
struct OnStepHandler : Handler {
  /*
   * If we have made a small amount of progress in a frame, this method is
   * called with the frame as argument. If succesful, this method should
   * return true, with `resumeMode` and `vp` set to a resumption value
   * specifiying how execution should continue.
   */
  virtual bool onStep(JSContext* cx, Handle<DebuggerFrame*> frame,
                      ResumeMode& resumeMode, MutableHandleValue vp) = 0;
};

class ScriptedOnStepHandler final : public OnStepHandler {
 public:
  explicit ScriptedOnStepHandler(JSObject* object);
  virtual JSObject* object() const override;
  virtual void hold(JSObject* owner) override;
  virtual void drop(JS::GCContext* gcx, JSObject* owner) override;
  virtual void trace(JSTracer* tracer) override;
  virtual size_t allocSize() const override;
  virtual bool onStep(JSContext* cx, Handle<DebuggerFrame*> frame,
                      ResumeMode& resumeMode, MutableHandleValue vp) override;

 private:
  const HeapPtr<JSObject*> object_;
};

/*
 * An OnPopHandler represents a handler function that is called just before a
 * frame is popped.
 */
struct OnPopHandler : Handler {
  /*
   * The given `frame` is about to be popped; `completion` explains why.
   *
   * When this method returns true, it must set `resumeMode` and `vp` to a
   * resumption value specifying how execution should continue.
   *
   * When this method returns false, it should set an exception on `cx`.
   */
  virtual bool onPop(JSContext* cx, Handle<DebuggerFrame*> frame,
                     const Completion& completion, ResumeMode& resumeMode,
                     MutableHandleValue vp) = 0;
};

class ScriptedOnPopHandler final : public OnPopHandler {
 public:
  explicit ScriptedOnPopHandler(JSObject* object);
  virtual JSObject* object() const override;
  virtual void hold(JSObject* owner) override;
  virtual void drop(JS::GCContext* gcx, JSObject* owner) override;
  virtual void trace(JSTracer* tracer) override;
  virtual size_t allocSize() const override;
  virtual bool onPop(JSContext* cx, Handle<DebuggerFrame*> frame,
                     const Completion& completion, ResumeMode& resumeMode,
                     MutableHandleValue vp) override;

 private:
  const HeapPtr<JSObject*> object_;
};

enum class DebuggerFrameType { Eval, Global, Call, Module, WasmCall };

enum class DebuggerFrameImplementation { Interpreter, Baseline, Ion, Wasm };

class DebuggerArguments : public NativeObject {
 public:
  static const JSClass class_;

  static DebuggerArguments* create(JSContext* cx, HandleObject proto,
                                   Handle<DebuggerFrame*> frame);

 private:
  enum { FRAME_SLOT };

  static const unsigned RESERVED_SLOTS = 1;
};

class DebuggerFrame : public NativeObject {
  friend class DebuggerArguments;
  friend class ScriptedOnStepHandler;
  friend class ScriptedOnPopHandler;

 public:
  static const JSClass class_;

  enum {
    FRAME_ITER_SLOT = 0,
    OWNER_SLOT,
    ARGUMENTS_SLOT,
    ONSTEP_HANDLER_SLOT,
    ONPOP_HANDLER_SLOT,

    // If this is a frame for a generator call, and the generator object has
    // been created (which doesn't happen until after default argument
    // evaluation and destructuring), then this is a PrivateValue pointing to a
    // GeneratorInfo struct that points to the call's AbstractGeneratorObject.
    // This allows us to implement Debugger.Frame methods even while the call is
    // suspended, and we have no FrameIter::Data.
    //
    // While Debugger::generatorFrames maps an AbstractGeneratorObject to its
    // Debugger.Frame, this link represents the reverse relation, from a
    // Debugger.Frame to its generator object. This slot is set if and only if
    // there is a corresponding entry in generatorFrames.
    GENERATOR_INFO_SLOT,

    RESERVED_SLOTS,
  };

  void trace(JSTracer* trc);

  static NativeObject* initClass(JSContext* cx, Handle<GlobalObject*> global,
                                 HandleObject dbgCtor);
  static DebuggerFrame* create(JSContext* cx, HandleObject proto,
                               Handle<NativeObject*> debugger,
                               const FrameIter* maybeIter,
                               Handle<AbstractGeneratorObject*> maybeGenerator);

  [[nodiscard]] static bool getArguments(
      JSContext* cx, Handle<DebuggerFrame*> frame,
      MutableHandle<DebuggerArguments*> result);
  [[nodiscard]] static bool getCallee(JSContext* cx,
                                      Handle<DebuggerFrame*> frame,
                                      MutableHandle<DebuggerObject*> result);
  [[nodiscard]] static bool getIsConstructing(JSContext* cx,
                                              Handle<DebuggerFrame*> frame,
                                              bool& result);
  [[nodiscard]] static bool getEnvironment(
      JSContext* cx, Handle<DebuggerFrame*> frame,
      MutableHandle<DebuggerEnvironment*> result);
  [[nodiscard]] static bool getOffset(JSContext* cx,
                                      Handle<DebuggerFrame*> frame,
                                      size_t& result);
  [[nodiscard]] static bool getOlder(JSContext* cx,
                                     Handle<DebuggerFrame*> frame,
                                     MutableHandle<DebuggerFrame*> result);
  [[nodiscard]] static bool getAsyncPromise(
      JSContext* cx, Handle<DebuggerFrame*> frame,
      MutableHandle<DebuggerObject*> result);
  [[nodiscard]] static bool getOlderSavedFrame(
      JSContext* cx, Handle<DebuggerFrame*> frame,
      MutableHandle<SavedFrame*> result);
  [[nodiscard]] static bool getThis(JSContext* cx, Handle<DebuggerFrame*> frame,
                                    MutableHandleValue result);
  static DebuggerFrameType getType(Handle<DebuggerFrame*> frame);
  static DebuggerFrameImplementation getImplementation(
      Handle<DebuggerFrame*> frame);
  [[nodiscard]] static bool setOnStepHandler(JSContext* cx,
                                             Handle<DebuggerFrame*> frame,
                                             UniquePtr<OnStepHandler> handler);

  [[nodiscard]] static JS::Result<Completion> eval(
      JSContext* cx, Handle<DebuggerFrame*> frame,
      mozilla::Range<const char16_t> chars, HandleObject bindings,
      const EvalOptions& options);

  [[nodiscard]] static DebuggerFrame* check(JSContext* cx, HandleValue thisv);

  bool isOnStack() const;

  bool isSuspended() const;

  OnStepHandler* onStepHandler() const;
  OnPopHandler* onPopHandler() const;
  void setOnPopHandler(JSContext* cx, OnPopHandler* handler);

  inline bool hasGeneratorInfo() const;

  // If hasGeneratorInfo(), return an direct cross-compartment reference to this
  // Debugger.Frame's generator object.
  AbstractGeneratorObject& unwrappedGenerator() const;

#ifdef DEBUG
  JSScript* generatorScript() const;
#endif

  /*
   * Associate the generator object genObj with this Debugger.Frame. This
   * association allows the Debugger.Frame to track the generator's execution
   * across suspensions and resumptions, and to implement some methods even
   * while the generator is suspended.
   *
   * The context `cx` must be in the Debugger.Frame's realm, and `genObj` must
   * be in a debuggee realm.
   *
   * Technically, the generator activation need not actually be on the stack
   * right now; it's okay to call this method on a Debugger.Frame that has no
   * ScriptFrameIter::Data at present. However, this function has no way to
   * verify that genObj really is the generator associated with the call for
   * which this Debugger.Frame was originally created, so it's best to make the
   * association while the call is on the stack, and the relationships are easy
   * to discern.
   */
  [[nodiscard]] static bool setGeneratorInfo(
      JSContext* cx, Handle<DebuggerFrame*> frame,
      Handle<AbstractGeneratorObject*> genObj);

  /*
   * Undo the effects of a prior call to setGenerator.
   *
   * If provided, owner must be the Debugger to which this Debugger.Frame
   * belongs; remove this frame's entry from its generatorFrames map, and clean
   * up its cross-compartment wrapper table entry. The owner must be passed
   * unless this method is being called from the Debugger.Frame's finalizer. (In
   * that case, the owner is not reliably available, and is not actually
   * necessary.)
   *
   * If maybeGeneratorFramesEnum is non-null, use it to remove this frame's
   * entry from the Debugger's generatorFrames weak map. In this case, this
   * function will not otherwise disturb generatorFrames. Passing the enum
   * allows this function to be used while iterating over generatorFrames.
   */
  void clearGeneratorInfo(JS::GCContext* gcx);

  /*
   * Called after a generator/async frame is resumed, before exposing this
   * Debugger.Frame object to any hooks.
   */
  bool resume(const FrameIter& iter);

  bool hasAnyHooks() const;

  Debugger* owner() const;

 private:
  static const JSClassOps classOps_;

  static const JSPropertySpec properties_[];
  static const JSFunctionSpec methods_[];

  static void finalize(JS::GCContext* gcx, JSObject* obj);

  static AbstractFramePtr getReferent(Handle<DebuggerFrame*> frame);
  [[nodiscard]] static bool requireScriptReferent(JSContext* cx,
                                                  Handle<DebuggerFrame*> frame);

  [[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);

  struct CallData;

  [[nodiscard]] bool incrementStepperCounter(JSContext* cx,
                                             AbstractFramePtr referent);
  [[nodiscard]] bool incrementStepperCounter(JSContext* cx,
                                             HandleScript script);
  void decrementStepperCounter(JS::GCContext* gcx, JSScript* script);
  void decrementStepperCounter(JS::GCContext* gcx, AbstractFramePtr referent);

  FrameIter::Data* frameIterData() const;
  void setFrameIterData(FrameIter::Data*);
  void freeFrameIterData(JS::GCContext* gcx);

 public:
  FrameIter getFrameIter(JSContext* cx);

  void terminate(JS::GCContext* gcx, AbstractFramePtr frame);
  void onGeneratorClosed(JS::GCContext* gcx);
  void suspend(JS::GCContext* gcx);

  [[nodiscard]] bool replaceFrameIterData(JSContext* cx, const FrameIter&);

  class GeneratorInfo;
  inline GeneratorInfo* generatorInfo() const;
};

} /* namespace js */

#endif /* debugger_Frame_h */