// Copyright 2013 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_COMMON_ASSERT_SCOPE_H_
#define V8_COMMON_ASSERT_SCOPE_H_

#include <stdint.h>

#include "src/base/macros.h"
#include "src/base/optional.h"
#include "src/common/globals.h"
#include "src/utils/pointer-with-payload.h"

namespace v8 {
namespace internal {

// Forward declarations.
class Isolate;
class PerThreadAssertData;

template <>
struct PointerWithPayloadTraits<PerThreadAssertData> {
  static constexpr int value = 1;
};

enum PerThreadAssertType {
  HEAP_ALLOCATION_ASSERT,
  HANDLE_ALLOCATION_ASSERT,
  HANDLE_DEREFERENCE_ASSERT,
  CODE_DEPENDENCY_CHANGE_ASSERT,
  LAST_PER_THREAD_ASSERT_TYPE
};

enum PerIsolateAssertType {
  JAVASCRIPT_EXECUTION_ASSERT,
  JAVASCRIPT_EXECUTION_THROWS,
  JAVASCRIPT_EXECUTION_DUMP,
  DEOPTIMIZATION_ASSERT,
  COMPILATION_ASSERT,
  NO_EXCEPTION_ASSERT
};

template <PerThreadAssertType kType, bool kAllow>
class PerThreadAssertScope {
 public:
  V8_EXPORT_PRIVATE PerThreadAssertScope();
  V8_EXPORT_PRIVATE ~PerThreadAssertScope();

  V8_EXPORT_PRIVATE static bool IsAllowed();

  void Release();

 private:
  PointerWithPayload<PerThreadAssertData, bool, 1> data_and_old_state_;

  V8_INLINE void set_data(PerThreadAssertData* data) {
    data_and_old_state_.SetPointer(data);
  }

  V8_INLINE PerThreadAssertData* data() const {
    return data_and_old_state_.GetPointer();
  }

  V8_INLINE void set_old_state(bool old_state) {
    return data_and_old_state_.SetPayload(old_state);
  }

  V8_INLINE bool old_state() const { return data_and_old_state_.GetPayload(); }

  DISALLOW_COPY_AND_ASSIGN(PerThreadAssertScope);
};

template <PerIsolateAssertType type, bool allow>
class PerIsolateAssertScope {
 public:
  V8_EXPORT_PRIVATE explicit PerIsolateAssertScope(Isolate* isolate);
  V8_EXPORT_PRIVATE ~PerIsolateAssertScope();

  static bool IsAllowed(Isolate* isolate);

 private:
  Isolate* isolate_;
  uint32_t old_data_;

  DISALLOW_COPY_AND_ASSIGN(PerIsolateAssertScope);
};

template <PerThreadAssertType type, bool allow>
#ifdef DEBUG
class PerThreadAssertScopeDebugOnly : public PerThreadAssertScope<type, allow> {
#else
class PerThreadAssertScopeDebugOnly {
 public:
  PerThreadAssertScopeDebugOnly() {  // NOLINT (modernize-use-equals-default)
    // Define a constructor to avoid unused variable warnings.
  }
  void Release() {}
#endif
};

template <PerIsolateAssertType type, bool allow>
#ifdef DEBUG
class PerIsolateAssertScopeDebugOnly
    : public PerIsolateAssertScope<type, allow> {
 public:
  explicit PerIsolateAssertScopeDebugOnly(Isolate* isolate)
      : PerIsolateAssertScope<type, allow>(isolate) {}
#else
class PerIsolateAssertScopeDebugOnly {
 public:
  explicit PerIsolateAssertScopeDebugOnly(Isolate* isolate) {}
#endif
};

// Per-thread assert scopes.

// Scope to document where we do not expect handles to be created.
using DisallowHandleAllocation =
    PerThreadAssertScopeDebugOnly<HANDLE_ALLOCATION_ASSERT, false>;

// Scope to introduce an exception to DisallowHandleAllocation.
using AllowHandleAllocation =
    PerThreadAssertScopeDebugOnly<HANDLE_ALLOCATION_ASSERT, true>;

// Scope to document where we do not expect any allocation and GC.
using DisallowHeapAllocation =
    PerThreadAssertScopeDebugOnly<HEAP_ALLOCATION_ASSERT, false>;
#ifdef DEBUG
#define DISALLOW_HEAP_ALLOCATION(name) DisallowHeapAllocation name;
#else
#define DISALLOW_HEAP_ALLOCATION(name)
#endif

// Scope to introduce an exception to DisallowHeapAllocation.
using AllowHeapAllocation =
    PerThreadAssertScopeDebugOnly<HEAP_ALLOCATION_ASSERT, true>;

// Scope to document where we do not expect any handle dereferences.
using DisallowHandleDereference =
    PerThreadAssertScopeDebugOnly<HANDLE_DEREFERENCE_ASSERT, false>;

// Scope to introduce an exception to DisallowHandleDereference.
using AllowHandleDereference =
    PerThreadAssertScopeDebugOnly<HANDLE_DEREFERENCE_ASSERT, true>;

// Scope to document where we do not expect code dependencies to change.
using DisallowCodeDependencyChange =
    PerThreadAssertScopeDebugOnly<CODE_DEPENDENCY_CHANGE_ASSERT, false>;

// Scope to introduce an exception to DisallowCodeDependencyChange.
using AllowCodeDependencyChange =
    PerThreadAssertScopeDebugOnly<CODE_DEPENDENCY_CHANGE_ASSERT, true>;

class DisallowHeapAccess {
  DisallowCodeDependencyChange no_dependency_change_;
  DisallowHandleAllocation no_handle_allocation_;
  DisallowHandleDereference no_handle_dereference_;
  DisallowHeapAllocation no_heap_allocation_;
};

class DisallowHeapAccessIf {
 public:
  explicit DisallowHeapAccessIf(bool condition) {
    if (condition) maybe_disallow_.emplace();
  }

 private:
  base::Optional<DisallowHeapAccess> maybe_disallow_;
};

// Per-isolate assert scopes.

// Scope to document where we do not expect javascript execution.
using DisallowJavascriptExecution =
    PerIsolateAssertScope<JAVASCRIPT_EXECUTION_ASSERT, false>;

// Scope to introduce an exception to DisallowJavascriptExecution.
using AllowJavascriptExecution =
    PerIsolateAssertScope<JAVASCRIPT_EXECUTION_ASSERT, true>;

// Scope to document where we do not expect javascript execution (debug only)
using DisallowJavascriptExecutionDebugOnly =
    PerIsolateAssertScopeDebugOnly<JAVASCRIPT_EXECUTION_ASSERT, false>;

// Scope to introduce an exception to DisallowJavascriptExecutionDebugOnly.
using AllowJavascriptExecutionDebugOnly =
    PerIsolateAssertScopeDebugOnly<JAVASCRIPT_EXECUTION_ASSERT, true>;

// Scope in which javascript execution leads to exception being thrown.
using ThrowOnJavascriptExecution =
    PerIsolateAssertScope<JAVASCRIPT_EXECUTION_THROWS, false>;

// Scope to introduce an exception to ThrowOnJavascriptExecution.
using NoThrowOnJavascriptExecution =
    PerIsolateAssertScope<JAVASCRIPT_EXECUTION_THROWS, true>;

// Scope in which javascript execution causes dumps.
using DumpOnJavascriptExecution =
    PerIsolateAssertScope<JAVASCRIPT_EXECUTION_DUMP, false>;

// Scope in which javascript execution causes dumps.
using NoDumpOnJavascriptExecution =
    PerIsolateAssertScope<JAVASCRIPT_EXECUTION_DUMP, true>;

// Scope to document where we do not expect deoptimization.
using DisallowDeoptimization =
    PerIsolateAssertScopeDebugOnly<DEOPTIMIZATION_ASSERT, false>;

// Scope to introduce an exception to DisallowDeoptimization.
using AllowDeoptimization =
    PerIsolateAssertScopeDebugOnly<DEOPTIMIZATION_ASSERT, true>;

// Scope to document where we do not expect deoptimization.
using DisallowCompilation =
    PerIsolateAssertScopeDebugOnly<COMPILATION_ASSERT, false>;

// Scope to introduce an exception to DisallowDeoptimization.
using AllowCompilation =
    PerIsolateAssertScopeDebugOnly<COMPILATION_ASSERT, true>;

// Scope to document where we do not expect exceptions.
using DisallowExceptions =
    PerIsolateAssertScopeDebugOnly<NO_EXCEPTION_ASSERT, false>;

// Scope to introduce an exception to DisallowExceptions.
using AllowExceptions =
    PerIsolateAssertScopeDebugOnly<NO_EXCEPTION_ASSERT, true>;

// Explicit instantiation declarations.
extern template class PerThreadAssertScope<HEAP_ALLOCATION_ASSERT, false>;
extern template class PerThreadAssertScope<HEAP_ALLOCATION_ASSERT, true>;
extern template class PerThreadAssertScope<HANDLE_ALLOCATION_ASSERT, false>;
extern template class PerThreadAssertScope<HANDLE_ALLOCATION_ASSERT, true>;
extern template class PerThreadAssertScope<HANDLE_DEREFERENCE_ASSERT, false>;
extern template class PerThreadAssertScope<HANDLE_DEREFERENCE_ASSERT, true>;
extern template class PerThreadAssertScope<CODE_DEPENDENCY_CHANGE_ASSERT,
                                           false>;
extern template class PerThreadAssertScope<CODE_DEPENDENCY_CHANGE_ASSERT, true>;

extern template class PerIsolateAssertScope<JAVASCRIPT_EXECUTION_ASSERT, false>;
extern template class PerIsolateAssertScope<JAVASCRIPT_EXECUTION_ASSERT, true>;
extern template class PerIsolateAssertScope<JAVASCRIPT_EXECUTION_THROWS, false>;
extern template class PerIsolateAssertScope<JAVASCRIPT_EXECUTION_THROWS, true>;
extern template class PerIsolateAssertScope<JAVASCRIPT_EXECUTION_DUMP, false>;
extern template class PerIsolateAssertScope<JAVASCRIPT_EXECUTION_DUMP, true>;
extern template class PerIsolateAssertScope<DEOPTIMIZATION_ASSERT, false>;
extern template class PerIsolateAssertScope<DEOPTIMIZATION_ASSERT, true>;
extern template class PerIsolateAssertScope<COMPILATION_ASSERT, false>;
extern template class PerIsolateAssertScope<COMPILATION_ASSERT, true>;
extern template class PerIsolateAssertScope<NO_EXCEPTION_ASSERT, false>;
extern template class PerIsolateAssertScope<NO_EXCEPTION_ASSERT, true>;

}  // namespace internal
}  // namespace v8

#endif  // V8_COMMON_ASSERT_SCOPE_H_