summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/NoExecute.h
blob: 6c9fa3374dfa4fdd2da01752cac8a7f6773c20ee (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
/* -*- 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_NoExecute_h
#define debugger_NoExecute_h

#include "mozilla/Assertions.h"  // for AssertionConditionType, MOZ_ASSERT
#include "mozilla/Attributes.h"  // for MOZ_RAII

#include "NamespaceImports.h"  // for HandleScript
#include "js/Promise.h"        // for JS::AutoDebuggerJobQueueInterruption

namespace js {

class Debugger;
class LeaveDebuggeeNoExecute;

// Prevents all the debuggeee compartments of a given Debugger from executing
// scripts. Attempts to run script will throw an
// instance of Debugger.DebuggeeWouldRun from the topmost locked Debugger's
// compartment.
class MOZ_RAII EnterDebuggeeNoExecute {
  friend class LeaveDebuggeeNoExecute;

  Debugger& dbg_;
  EnterDebuggeeNoExecute** stack_;
  EnterDebuggeeNoExecute* prev_;

  // Non-nullptr when unlocked temporarily by a LeaveDebuggeeNoExecute.
  LeaveDebuggeeNoExecute* unlocked_;

  // When DebuggeeWouldRun is a warning instead of an error, whether we've
  // reported a warning already.
  bool reported_;

 public:
  // Mark execution in dbg's debuggees as forbidden, for the lifetime of this
  // object. Require an AutoDebuggerJobQueueInterruption in scope.
  explicit EnterDebuggeeNoExecute(
      JSContext* cx, Debugger& dbg,
      const JS::AutoDebuggerJobQueueInterruption& adjqiProof);

  ~EnterDebuggeeNoExecute() {
    MOZ_ASSERT(*stack_ == this);
    *stack_ = prev_;
  }

  Debugger& debugger() const { return dbg_; }

#ifdef DEBUG
  static bool isLockedInStack(JSContext* cx, Debugger& dbg);
#endif

  // Given a JSContext entered into a debuggee realm, find the lock
  // that locks it. Returns nullptr if not found.
  static EnterDebuggeeNoExecute* findInStack(JSContext* cx);

  // Given a JSContext entered into a debuggee compartment, report a
  // warning or an error if there is a lock that locks it.
  static bool reportIfFoundInStack(JSContext* cx, HandleScript script);
};

// Given a JSContext entered into a debuggee compartment, if it is in
// an NX section, unlock the topmost EnterDebuggeeNoExecute instance.
//
// Does nothing if debuggee is not in an NX section. For example, this
// situation arises when invocation functions are called without entering
// Debugger code, e.g., calling D.O.p.executeInGlobal or D.O.p.apply.
class MOZ_RAII LeaveDebuggeeNoExecute {
  EnterDebuggeeNoExecute* prevLocked_;

 public:
  explicit LeaveDebuggeeNoExecute(JSContext* cx)
      : prevLocked_(EnterDebuggeeNoExecute::findInStack(cx)) {
    if (prevLocked_) {
      MOZ_ASSERT(!prevLocked_->unlocked_);
      prevLocked_->unlocked_ = this;
    }
  }

  ~LeaveDebuggeeNoExecute() {
    if (prevLocked_) {
      MOZ_ASSERT(prevLocked_->unlocked_ == this);
      prevLocked_->unlocked_ = nullptr;
    }
  }
};

} /* namespace js */

#endif /* debugger_NoExecute_h */