summaryrefslogtreecommitdiffstats
path: root/js/src/vm/FrameIter.h
blob: c3561e0247b029fe02ff4344c473653e929bea02 (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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
/* -*- 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 vm_FrameIter_h
#define vm_FrameIter_h

#include "mozilla/Assertions.h"  // MOZ_ASSERT
#include "mozilla/Attributes.h"  // MOZ_IMPLICIT, MOZ_RAII
#include "mozilla/MaybeOneOf.h"  // mozilla::MaybeOneOf

#include <stddef.h>  // size_t
#include <stdint.h>  // uint8_t, uint32_t, uintptr_t

#include "jstypes.h"  // JS_PUBLIC_API

#include "jit/JSJitFrameIter.h"  // js::jit::{InlineFrameIterator,JSJitFrameIter}
#include "js/RootingAPI.h"       // JS::Handle, JS::Rooted
#include "js/TypeDecls.h"  // jsbytecode, JSContext, JSAtom, JSFunction, JSObject, JSScript
#include "js/Value.h"       // JS::Value
#include "vm/Activation.h"  // js::InterpreterActivation
#include "vm/Stack.h"       // js::{AbstractFramePtr,MaybeCheckAliasing}
#include "wasm/WasmFrameIter.h"  // js::wasm::{ExitReason,RegisterState,WasmFrameIter}

struct JSPrincipals;

namespace JS {

class JS_PUBLIC_API Compartment;
class JS_PUBLIC_API Realm;

}  // namespace JS

namespace js {

class ArgumentsObject;
class CallObject;

namespace jit {
class CommonFrameLayout;
class JitActivation;
}  // namespace jit

namespace wasm {
class Instance;
}  // namespace wasm

// Iterates over the frames of a single InterpreterActivation.
class InterpreterFrameIterator {
  InterpreterActivation* activation_;
  InterpreterFrame* fp_;
  jsbytecode* pc_;
  JS::Value* sp_;

 public:
  explicit InterpreterFrameIterator(InterpreterActivation* activation)
      : activation_(activation), fp_(nullptr), pc_(nullptr), sp_(nullptr) {
    if (activation) {
      fp_ = activation->current();
      pc_ = activation->regs().pc;
      sp_ = activation->regs().sp;
    }
  }

  InterpreterFrame* frame() const {
    MOZ_ASSERT(!done());
    return fp_;
  }
  jsbytecode* pc() const {
    MOZ_ASSERT(!done());
    return pc_;
  }
  JS::Value* sp() const {
    MOZ_ASSERT(!done());
    return sp_;
  }

  InterpreterFrameIterator& operator++();

  bool done() const { return fp_ == nullptr; }
};

// A JitFrameIter can iterate over all kind of frames emitted by our code
// generators, be they composed of JS jit frames or wasm frames, interleaved or
// not, in any order.
//
// In the following class:
// - code generated for JS is referred to as JSJit.
// - code generated for wasm is referred to as Wasm.
// Also, Jit refers to any one of them.
//
// JitFrameIter uses JSJitFrameIter to iterate over JSJit code or a
// WasmFrameIter to iterate over wasm code; only one of them is active at the
// time. When a sub-iterator is done, the JitFrameIter knows how to stop, move
// onto the next activation or move onto another kind of Jit code.
//
// For ease of use, there is also OnlyJSJitFrameIter, which skips all the
// non-JSJit frames.
//
// Note it is allowed to get a handle to the internal frame iterator via
// asJSJit() and asWasm(), but the user has to be careful not to have those be
// used after JitFrameIter leaves the scope or the operator++ is called.
//
// In particular, this can handle the transition from wasm to jit and from jit
// to wasm, since these can be interleaved in the same JitActivation.
class JitFrameIter {
 protected:
  jit::JitActivation* act_ = nullptr;
  mozilla::MaybeOneOf<jit::JSJitFrameIter, wasm::WasmFrameIter> iter_ = {};
  bool mustUnwindActivation_ = false;

  void settle();

 public:
  JitFrameIter() = default;

  explicit JitFrameIter(jit::JitActivation* activation,
                        bool mustUnwindActivation = false);

  explicit JitFrameIter(const JitFrameIter& another);
  JitFrameIter& operator=(const JitFrameIter& another);

  bool isSome() const { return !iter_.empty(); }
  void reset() {
    MOZ_ASSERT(isSome());
    iter_.destroy();
  }

  bool isJSJit() const {
    return isSome() && iter_.constructed<jit::JSJitFrameIter>();
  }
  jit::JSJitFrameIter& asJSJit() { return iter_.ref<jit::JSJitFrameIter>(); }
  const jit::JSJitFrameIter& asJSJit() const {
    return iter_.ref<jit::JSJitFrameIter>();
  }

  bool isWasm() const {
    return isSome() && iter_.constructed<wasm::WasmFrameIter>();
  }
  wasm::WasmFrameIter& asWasm() { return iter_.ref<wasm::WasmFrameIter>(); }
  const wasm::WasmFrameIter& asWasm() const {
    return iter_.ref<wasm::WasmFrameIter>();
  }

  // Operations common to all frame iterators.
  const jit::JitActivation* activation() const { return act_; }
  bool done() const;
  void operator++();

  JS::Realm* realm() const;

  // Returns the address of the next instruction that will execute in this
  // frame, once control returns to this frame.
  uint8_t* resumePCinCurrentFrame() const;

  // Operations which have an effect only on JIT frames.
  void skipNonScriptedJSFrames();

  // Returns true iff this is a JIT frame with a self-hosted script. Note: be
  // careful, JitFrameIter does not consider functions inlined by Ion.
  bool isSelfHostedIgnoringInlining() const;
};

// A JitFrameIter that skips all the non-JSJit frames, skipping interleaved
// frames of any another kind.

class OnlyJSJitFrameIter : public JitFrameIter {
  void settle() {
    while (!done() && !isJSJit()) {
      JitFrameIter::operator++();
    }
  }

 public:
  explicit OnlyJSJitFrameIter(jit::JitActivation* act);
  explicit OnlyJSJitFrameIter(const ActivationIterator& cx);

  void operator++() {
    JitFrameIter::operator++();
    settle();
  }

  const jit::JSJitFrameIter& frame() const { return asJSJit(); }
};

class ScriptSource;

// A FrameIter walks over a context's stack of JS script activations,
// abstracting over whether the JS scripts were running in the interpreter or
// different modes of compiled code.
//
// FrameIter is parameterized by what it includes in the stack iteration:
//  - When provided, the optional JSPrincipal argument will cause FrameIter to
//    only show frames in globals whose JSPrincipals are subsumed (via
//    JSSecurityCallbacks::subsume) by the given JSPrincipal.
//
// Additionally, there are derived FrameIter types that automatically skip
// certain frames:
//  - ScriptFrameIter only shows frames that have an associated JSScript
//    (currently everything other than wasm stack frames). When !hasScript(),
//    clients must stick to the portion of the
//    interface marked below.
//  - NonBuiltinScriptFrameIter additionally filters out builtin (self-hosted)
//    scripts.
class FrameIter {
 public:
  enum DebuggerEvalOption {
    FOLLOW_DEBUGGER_EVAL_PREV_LINK,
    IGNORE_DEBUGGER_EVAL_PREV_LINK
  };

  enum State {
    DONE,    // when there are no more frames nor activations to unwind.
    INTERP,  // interpreter activation on the stack
    JIT      // jit or wasm activations on the stack
  };

  // Unlike ScriptFrameIter itself, ScriptFrameIter::Data can be allocated on
  // the heap, so this structure should not contain any GC things.
  struct Data {
    JSContext* cx_;
    DebuggerEvalOption debuggerEvalOption_;
    JSPrincipals* principals_;

    State state_;

    jsbytecode* pc_;

    InterpreterFrameIterator interpFrames_;
    ActivationIterator activations_;

    JitFrameIter jitFrames_;
    unsigned ionInlineFrameNo_;

    Data(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
         JSPrincipals* principals);
    Data(const Data& other);
  };

  explicit FrameIter(JSContext* cx,
                     DebuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK);
  FrameIter(JSContext* cx, DebuggerEvalOption, JSPrincipals*);
  FrameIter(const FrameIter& iter);
  MOZ_IMPLICIT FrameIter(const Data& data);
  MOZ_IMPLICIT FrameIter(AbstractFramePtr frame);

  bool done() const { return data_.state_ == DONE; }

  // -------------------------------------------------------
  // The following functions can only be called when !done()
  // -------------------------------------------------------

  FrameIter& operator++();

  JS::Realm* realm() const;
  JS::Compartment* compartment() const;
  Activation* activation() const { return data_.activations_.activation(); }

  bool isInterp() const {
    MOZ_ASSERT(!done());
    return data_.state_ == INTERP;
  }
  bool isJSJit() const {
    MOZ_ASSERT(!done());
    return data_.state_ == JIT && data_.jitFrames_.isJSJit();
  }
  bool isWasm() const {
    MOZ_ASSERT(!done());
    return data_.state_ == JIT && data_.jitFrames_.isWasm();
  }

  inline bool isIon() const;
  inline bool isBaseline() const;
  inline bool isPhysicalJitFrame() const;

  bool isEvalFrame() const;
  bool isModuleFrame() const;
  bool isFunctionFrame() const;
  bool hasArgs() const { return isFunctionFrame(); }

  ScriptSource* scriptSource() const;
  const char* filename() const;
  const char16_t* displayURL() const;
  unsigned computeLine(uint32_t* column = nullptr) const;
  JSAtom* maybeFunctionDisplayAtom() const;
  bool mutedErrors() const;

  bool hasScript() const { return !isWasm(); }

  // -----------------------------------------------------------
  //  The following functions can only be called when isWasm()
  // -----------------------------------------------------------

  inline bool wasmDebugEnabled() const;
  inline wasm::Instance* wasmInstance() const;
  inline uint32_t wasmFuncIndex() const;
  inline unsigned wasmBytecodeOffset() const;
  void wasmUpdateBytecodeOffset();

  // -----------------------------------------------------------
  // The following functions can only be called when hasScript()
  // -----------------------------------------------------------

  inline JSScript* script() const;

  bool isConstructing() const;
  jsbytecode* pc() const {
    MOZ_ASSERT(!done());
    return data_.pc_;
  }
  void updatePcQuadratic();

  // The function |calleeTemplate()| returns either the function from which
  // the current |callee| was cloned or the |callee| if it can be read. As
  // long as we do not have to investigate the environment chain or build a
  // new frame, we should prefer to use |calleeTemplate| instead of
  // |callee|, as requesting the |callee| might cause the invalidation of
  // the frame. (see js::Lambda)
  JSFunction* calleeTemplate() const;
  JSFunction* callee(JSContext* cx) const;

  JSFunction* maybeCallee(JSContext* cx) const {
    return isFunctionFrame() ? callee(cx) : nullptr;
  }

  bool matchCallee(JSContext* cx, JS::Handle<JSFunction*> fun) const;

  unsigned numActualArgs() const;
  unsigned numFormalArgs() const;
  JS::Value unaliasedActual(unsigned i,
                            MaybeCheckAliasing = CHECK_ALIASING) const;
  template <class Op>
  inline void unaliasedForEachActual(JSContext* cx, Op op);

  JSObject* environmentChain(JSContext* cx) const;
  bool hasInitialEnvironment(JSContext* cx) const;
  CallObject& callObj(JSContext* cx) const;

  bool hasArgsObj() const;
  ArgumentsObject& argsObj() const;

  // Get the original |this| value passed to this function. May not be the
  // actual this-binding (for instance, derived class constructors will
  // change their this-value later and non-strict functions will box
  // primitives).
  JS::Value thisArgument(JSContext* cx) const;

  JS::Value returnValue() const;
  void setReturnValue(const JS::Value& v);

  // These are only valid for the top frame.
  size_t numFrameSlots() const;
  JS::Value frameSlotValue(size_t index) const;

  // Ensures that we have rematerialized the top frame and its associated
  // inline frames. Can only be called when isIon().
  bool ensureHasRematerializedFrame(JSContext* cx);

  // True when isInterp() or isBaseline(). True when isIon() if it
  // has a rematerialized frame. False otherwise.
  bool hasUsableAbstractFramePtr() const;

  // -----------------------------------------------------------
  // The following functions can only be called when isInterp(),
  // isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can
  // only be called when hasUsableAbstractFramePtr().
  // -----------------------------------------------------------

  AbstractFramePtr abstractFramePtr() const;
  Data* copyData() const;

  // This can only be called when isInterp():
  inline InterpreterFrame* interpFrame() const;

  // This can only be called when isPhysicalJitFrame():
  inline jit::CommonFrameLayout* physicalJitFrame() const;

  // This is used to provide a raw interface for debugging.
  void* rawFramePtr() const;

  bool inPrologue() const;

 private:
  Data data_;
  jit::InlineFrameIterator ionInlineFrames_;

  const jit::JSJitFrameIter& jsJitFrame() const {
    return data_.jitFrames_.asJSJit();
  }
  const wasm::WasmFrameIter& wasmFrame() const {
    return data_.jitFrames_.asWasm();
  }

  jit::JSJitFrameIter& jsJitFrame() { return data_.jitFrames_.asJSJit(); }
  wasm::WasmFrameIter& wasmFrame() { return data_.jitFrames_.asWasm(); }

  bool isIonScripted() const {
    return isJSJit() && jsJitFrame().isIonScripted();
  }

  bool principalsSubsumeFrame() const;

  void popActivation();
  void popInterpreterFrame();
  void nextJitFrame();
  void popJitFrame();
  void settleOnActivation();
};

class ScriptFrameIter : public FrameIter {
  void settle() {
    while (!done() && !hasScript()) {
      FrameIter::operator++();
    }
  }

 public:
  explicit ScriptFrameIter(
      JSContext* cx,
      DebuggerEvalOption debuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK)
      : FrameIter(cx, debuggerEvalOption) {
    settle();
  }

  ScriptFrameIter& operator++() {
    FrameIter::operator++();
    settle();
    return *this;
  }
};

#ifdef DEBUG
bool SelfHostedFramesVisible();
#else
static inline bool SelfHostedFramesVisible() { return false; }
#endif

/* A filtering of the FrameIter to only stop at non-self-hosted scripts. */
class NonBuiltinFrameIter : public FrameIter {
  void settle();

 public:
  explicit NonBuiltinFrameIter(
      JSContext* cx, FrameIter::DebuggerEvalOption debuggerEvalOption =
                         FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK)
      : FrameIter(cx, debuggerEvalOption) {
    settle();
  }

  NonBuiltinFrameIter(JSContext* cx,
                      FrameIter::DebuggerEvalOption debuggerEvalOption,
                      JSPrincipals* principals)
      : FrameIter(cx, debuggerEvalOption, principals) {
    settle();
  }

  NonBuiltinFrameIter(JSContext* cx, JSPrincipals* principals)
      : FrameIter(cx, FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, principals) {
    settle();
  }

  NonBuiltinFrameIter& operator++() {
    FrameIter::operator++();
    settle();
    return *this;
  }
};

// A filtering of the ScriptFrameIter to only stop at non-self-hosted scripts.
class NonBuiltinScriptFrameIter : public ScriptFrameIter {
  void settle();

 public:
  explicit NonBuiltinScriptFrameIter(
      JSContext* cx, ScriptFrameIter::DebuggerEvalOption debuggerEvalOption =
                         ScriptFrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK)
      : ScriptFrameIter(cx, debuggerEvalOption) {
    settle();
  }

  NonBuiltinScriptFrameIter& operator++() {
    ScriptFrameIter::operator++();
    settle();
    return *this;
  }
};

/*
 * Blindly iterate over all frames in the current thread's stack. These frames
 * can be from different contexts and compartments, so beware.
 */
class AllFramesIter : public FrameIter {
 public:
  explicit AllFramesIter(JSContext* cx)
      : FrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK) {}
};

/* Iterates over all script frame in the current thread's stack.
 * See also AllFramesIter and ScriptFrameIter.
 */
class AllScriptFramesIter : public ScriptFrameIter {
 public:
  explicit AllScriptFramesIter(JSContext* cx)
      : ScriptFrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK) {}
};

/* Popular inline definitions. */

inline JSScript* FrameIter::script() const {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(hasScript());
  if (data_.state_ == INTERP) {
    return interpFrame()->script();
  }
  if (jsJitFrame().isIonJS()) {
    return ionInlineFrames_.script();
  }
  return jsJitFrame().script();
}

inline bool FrameIter::wasmDebugEnabled() const {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(isWasm());
  return wasmFrame().debugEnabled();
}

inline wasm::Instance* FrameIter::wasmInstance() const {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(isWasm());
  return wasmFrame().instance();
}

inline unsigned FrameIter::wasmBytecodeOffset() const {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(isWasm());
  return wasmFrame().lineOrBytecode();
}

inline uint32_t FrameIter::wasmFuncIndex() const {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(isWasm());
  return wasmFrame().funcIndex();
}

inline bool FrameIter::isIon() const {
  return isJSJit() && jsJitFrame().isIonJS();
}

inline bool FrameIter::isBaseline() const {
  return isJSJit() && jsJitFrame().isBaselineJS();
}

inline InterpreterFrame* FrameIter::interpFrame() const {
  MOZ_ASSERT(data_.state_ == INTERP);
  return data_.interpFrames_.frame();
}

inline bool FrameIter::isPhysicalJitFrame() const {
  if (!isJSJit()) {
    return false;
  }

  auto& jitFrame = jsJitFrame();

  if (jitFrame.isBaselineJS()) {
    return true;
  }

  if (jitFrame.isIonScripted()) {
    // Only the bottom of a group of inlined Ion frames is a physical frame.
    return ionInlineFrames_.frameNo() == 0;
  }

  return false;
}

inline jit::CommonFrameLayout* FrameIter::physicalJitFrame() const {
  MOZ_ASSERT(isPhysicalJitFrame());
  return jsJitFrame().current();
}

}  // namespace js

#endif  // vm_FrameIter_h