/* 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 */
