// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#ifndef SRC_ENV_H_
#define SRC_ENV_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "aliased_buffer.h"
#if HAVE_INSPECTOR
#include "inspector_agent.h"
#include "inspector_profiler.h"
#endif
#include "callback_queue.h"
#include "cleanup_queue-inl.h"
#include "compile_cache.h"
#include "debug_utils.h"
#include "env_properties.h"
#include "handle_wrap.h"
#include "node.h"
#include "node_binding.h"
#include "node_builtins.h"
#include "node_exit_code.h"
#include "node_main_instance.h"
#include "node_options.h"
#include "node_perf_common.h"
#include "node_realm.h"
#include "node_snapshotable.h"
#include "permission/permission.h"
#include "req_wrap.h"
#include "util.h"
#include "uv.h"
#include "v8-profiler.h"
#include "v8.h"

#if HAVE_OPENSSL
#include <openssl/evp.h>
#endif

#include <array>
#include <atomic>
#include <cstdint>
#include <deque>
#include <functional>
#include <list>
#include <memory>
#include <ostream>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace v8 {
class CppHeap;
}

namespace node {

namespace shadow_realm {
class ShadowRealm;
}
namespace contextify {
class ContextifyScript;
class CompiledFnEntry;
}

namespace performance {
class PerformanceState;
}

namespace tracing {
class AgentWriterHandle;
}

#if HAVE_INSPECTOR
namespace profiler {
class V8CoverageConnection;
class V8CpuProfilerConnection;
class V8HeapProfilerConnection;
}  // namespace profiler

namespace inspector {
class ParentInspectorHandle;
}
#endif  // HAVE_INSPECTOR

namespace worker {
class Worker;
}

namespace loader {
class ModuleWrap;
}  // namespace loader

class Environment;
class Realm;

// Disables zero-filling for ArrayBuffer allocations in this scope. This is
// similar to how we implement Buffer.allocUnsafe() in JS land.
class NoArrayBufferZeroFillScope {
 public:
  inline explicit NoArrayBufferZeroFillScope(IsolateData* isolate_data);
  inline ~NoArrayBufferZeroFillScope();

 private:
  NodeArrayBufferAllocator* node_allocator_;

  friend class Environment;
};

struct IsolateDataSerializeInfo {
  std::vector<SnapshotIndex> primitive_values;
  std::vector<PropInfo> template_values;

  friend std::ostream& operator<<(std::ostream& o,
                                  const IsolateDataSerializeInfo& i);
};

struct PerIsolateWrapperData {
  uint16_t cppgc_id;
  uint16_t non_cppgc_id;
};

class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
 private:
  IsolateData(v8::Isolate* isolate,
              uv_loop_t* event_loop,
              MultiIsolatePlatform* platform,
              ArrayBufferAllocator* node_allocator,
              const SnapshotData* snapshot_data,
              std::shared_ptr<PerIsolateOptions> options);

 public:
  static IsolateData* CreateIsolateData(
      v8::Isolate* isolate,
      uv_loop_t* event_loop,
      MultiIsolatePlatform* platform = nullptr,
      ArrayBufferAllocator* node_allocator = nullptr,
      const EmbedderSnapshotData* embedder_snapshot_data = nullptr,
      std::shared_ptr<PerIsolateOptions> options = nullptr);
  ~IsolateData();

  SET_MEMORY_INFO_NAME(IsolateData)
  SET_SELF_SIZE(IsolateData)
  void MemoryInfo(MemoryTracker* tracker) const override;
  IsolateDataSerializeInfo Serialize(v8::SnapshotCreator* creator);

  bool is_building_snapshot() const { return snapshot_config_.has_value(); }
  const SnapshotConfig* snapshot_config() const {
    return snapshot_config_.has_value() ? &(snapshot_config_.value()) : nullptr;
  }
  void set_snapshot_config(const SnapshotConfig* config) {
    if (config != nullptr) {
      snapshot_config_ = *config;  // Copy the config.
    }
  }

  uint16_t* embedder_id_for_cppgc() const;
  uint16_t* embedder_id_for_non_cppgc() const;

  static inline void SetCppgcReference(v8::Isolate* isolate,
                                       v8::Local<v8::Object> object,
                                       void* wrappable);

  inline uv_loop_t* event_loop() const;
  inline MultiIsolatePlatform* platform() const;
  inline const SnapshotData* snapshot_data() const;
  inline std::shared_ptr<PerIsolateOptions> options();

  inline NodeArrayBufferAllocator* node_allocator() const;

  inline worker::Worker* worker_context() const;
  inline void set_worker_context(worker::Worker* context);

#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName)
#define V(TypeName, PropertyName)                                             \
  inline v8::Local<TypeName> PropertyName() const;
  PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
  PER_ISOLATE_SYMBOL_PROPERTIES(VY)
  PER_ISOLATE_STRING_PROPERTIES(VS)
  PER_REALM_STRONG_PERSISTENT_VALUES(VR)
#undef V
#undef VR
#undef VY
#undef VS
#undef VP

#define VM(PropertyName) V(PropertyName##_binding_template, v8::ObjectTemplate)
#define V(PropertyName, TypeName)                                              \
  inline v8::Local<TypeName> PropertyName() const;                             \
  inline void set_##PropertyName(v8::Local<TypeName> value);
  PER_ISOLATE_TEMPLATE_PROPERTIES(V)
  NODE_BINDINGS_WITH_PER_ISOLATE_INIT(VM)
#undef V
#undef VM

  inline v8::Local<v8::String> async_wrap_provider(int index) const;

  size_t max_young_gen_size = 1;
  std::unordered_map<const char*, v8::Eternal<v8::String>> static_str_map;

  inline v8::Isolate* isolate() const;
  IsolateData(const IsolateData&) = delete;
  IsolateData& operator=(const IsolateData&) = delete;
  IsolateData(IsolateData&&) = delete;
  IsolateData& operator=(IsolateData&&) = delete;

 private:
  void DeserializeProperties(const IsolateDataSerializeInfo* isolate_data_info);
  void CreateProperties();

#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName)
#define VM(PropertyName) V(v8::ObjectTemplate, PropertyName##_binding_template)
#define VT(PropertyName, TypeName) V(TypeName, PropertyName)
#define V(TypeName, PropertyName)                                             \
  v8::Eternal<TypeName> PropertyName ## _;
  PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
  PER_ISOLATE_SYMBOL_PROPERTIES(VY)
  PER_ISOLATE_STRING_PROPERTIES(VS)
  PER_ISOLATE_TEMPLATE_PROPERTIES(VT)
  PER_REALM_STRONG_PERSISTENT_VALUES(VR)
  NODE_BINDINGS_WITH_PER_ISOLATE_INIT(VM)
#undef V
#undef VM
#undef VR
#undef VT
#undef VS
#undef VY
#undef VP
  // Keep a list of all Persistent strings used for AsyncWrap Provider types.
  std::array<v8::Eternal<v8::String>, AsyncWrap::PROVIDERS_LENGTH>
      async_wrap_providers_;

  v8::Isolate* const isolate_;
  uv_loop_t* const event_loop_;
  NodeArrayBufferAllocator* const node_allocator_;
  MultiIsolatePlatform* platform_;

  const SnapshotData* snapshot_data_;
  std::optional<SnapshotConfig> snapshot_config_;

  std::unique_ptr<v8::CppHeap> cpp_heap_;
  std::shared_ptr<PerIsolateOptions> options_;
  worker::Worker* worker_context_ = nullptr;
  PerIsolateWrapperData* wrapper_data_;

  static Mutex isolate_data_mutex_;
  static std::unordered_map<uint16_t, std::unique_ptr<PerIsolateWrapperData>>
      wrapper_data_map_;
};

struct ContextInfo {
  explicit ContextInfo(const std::string& name) : name(name) {}
  const std::string name;
  std::string origin;
  bool is_default = false;
};

class EnabledDebugList;

namespace per_process {
extern std::shared_ptr<KVStore> system_environment;
}

struct EnvSerializeInfo;

class AsyncHooks : public MemoryRetainer {
 public:
  SET_MEMORY_INFO_NAME(AsyncHooks)
  SET_SELF_SIZE(AsyncHooks)
  void MemoryInfo(MemoryTracker* tracker) const override;

  // Reason for both UidFields and Fields are that one is stored as a double*
  // and the other as a uint32_t*.
  enum Fields {
    kInit,
    kBefore,
    kAfter,
    kDestroy,
    kPromiseResolve,
    kTotals,
    kCheck,
    kStackLength,
    kUsesExecutionAsyncResource,
    kFieldsCount,
  };

  enum UidFields {
    kExecutionAsyncId,
    kTriggerAsyncId,
    kAsyncIdCounter,
    kDefaultTriggerAsyncId,
    kUidFieldsCount,
  };

  inline AliasedUint32Array& fields();
  inline AliasedFloat64Array& async_id_fields();
  inline AliasedFloat64Array& async_ids_stack();
  inline v8::Local<v8::Array> js_execution_async_resources();
  // Returns the native executionAsyncResource value at stack index `index`.
  // Resources provided on the JS side are not stored on the native stack,
  // in which case an empty `Local<>` is returned.
  // The `js_execution_async_resources` array contains the value in that case.
  inline v8::Local<v8::Object> native_execution_async_resource(size_t index);

  void InstallPromiseHooks(v8::Local<v8::Context> ctx);
  void ResetPromiseHooks(v8::Local<v8::Function> init,
                         v8::Local<v8::Function> before,
                         v8::Local<v8::Function> after,
                         v8::Local<v8::Function> resolve);
  // Used for testing since V8 doesn't provide API for retrieving configured
  // JS promise hooks.
  v8::Local<v8::Array> GetPromiseHooks(v8::Isolate* isolate) const;
  inline v8::Local<v8::String> provider_string(int idx);

  inline void no_force_checks();
  inline Environment* env();

  // NB: This call does not take (co-)ownership of `execution_async_resource`.
  // The lifetime of the `v8::Local<>` pointee must last until
  // `pop_async_context()` or `clear_async_id_stack()` are called.
  void push_async_context(double async_id,
                          double trigger_async_id,
                          v8::Local<v8::Object>* execution_async_resource);
  bool pop_async_context(double async_id);
  void clear_async_id_stack();  // Used in fatal exceptions.

  AsyncHooks(const AsyncHooks&) = delete;
  AsyncHooks& operator=(const AsyncHooks&) = delete;
  AsyncHooks(AsyncHooks&&) = delete;
  AsyncHooks& operator=(AsyncHooks&&) = delete;
  ~AsyncHooks() = default;

  // Used to set the kDefaultTriggerAsyncId in a scope. This is instead of
  // passing the trigger_async_id along with other constructor arguments.
  class DefaultTriggerAsyncIdScope {
   public:
    DefaultTriggerAsyncIdScope() = delete;
    explicit DefaultTriggerAsyncIdScope(Environment* env,
                                        double init_trigger_async_id);
    explicit DefaultTriggerAsyncIdScope(AsyncWrap* async_wrap);
    ~DefaultTriggerAsyncIdScope();

    DefaultTriggerAsyncIdScope(const DefaultTriggerAsyncIdScope&) = delete;
    DefaultTriggerAsyncIdScope& operator=(const DefaultTriggerAsyncIdScope&) =
        delete;
    DefaultTriggerAsyncIdScope(DefaultTriggerAsyncIdScope&&) = delete;
    DefaultTriggerAsyncIdScope& operator=(DefaultTriggerAsyncIdScope&&) =
        delete;

   private:
    AsyncHooks* async_hooks_;
    double old_default_trigger_async_id_;
  };

  struct SerializeInfo {
    AliasedBufferIndex async_ids_stack;
    AliasedBufferIndex fields;
    AliasedBufferIndex async_id_fields;
    SnapshotIndex js_execution_async_resources;
    std::vector<SnapshotIndex> native_execution_async_resources;
  };

  SerializeInfo Serialize(v8::Local<v8::Context> context,
                          v8::SnapshotCreator* creator);
  void Deserialize(v8::Local<v8::Context> context);

 private:
  friend class Environment;  // So we can call the constructor.
  explicit AsyncHooks(v8::Isolate* isolate, const SerializeInfo* info);

  [[noreturn]] void FailWithCorruptedAsyncStack(double expected_async_id);

  // Stores the ids of the current execution context stack.
  AliasedFloat64Array async_ids_stack_;
  // Attached to a Uint32Array that tracks the number of active hooks for
  // each type.
  AliasedUint32Array fields_;
  // Attached to a Float64Array that tracks the state of async resources.
  AliasedFloat64Array async_id_fields_;

  void grow_async_ids_stack();

  v8::Global<v8::Array> js_execution_async_resources_;

  // We avoid storing the handles directly here, because they are already
  // properly allocated on the stack, we just need access to them here.
  std::deque<v8::Local<v8::Object>*> native_execution_async_resources_;

  // Non-empty during deserialization
  const SerializeInfo* info_ = nullptr;

  std::array<v8::Global<v8::Function>, 4> js_promise_hooks_;
};

class ImmediateInfo : public MemoryRetainer {
 public:
  inline AliasedUint32Array& fields();
  inline uint32_t count() const;
  inline uint32_t ref_count() const;
  inline bool has_outstanding() const;
  inline void ref_count_inc(uint32_t increment);
  inline void ref_count_dec(uint32_t decrement);

  ImmediateInfo(const ImmediateInfo&) = delete;
  ImmediateInfo& operator=(const ImmediateInfo&) = delete;
  ImmediateInfo(ImmediateInfo&&) = delete;
  ImmediateInfo& operator=(ImmediateInfo&&) = delete;
  ~ImmediateInfo() = default;

  SET_MEMORY_INFO_NAME(ImmediateInfo)
  SET_SELF_SIZE(ImmediateInfo)
  void MemoryInfo(MemoryTracker* tracker) const override;

  struct SerializeInfo {
    AliasedBufferIndex fields;
  };
  SerializeInfo Serialize(v8::Local<v8::Context> context,
                          v8::SnapshotCreator* creator);
  void Deserialize(v8::Local<v8::Context> context);

 private:
  friend class Environment;  // So we can call the constructor.
  explicit ImmediateInfo(v8::Isolate* isolate, const SerializeInfo* info);

  enum Fields { kCount, kRefCount, kHasOutstanding, kFieldsCount };

  AliasedUint32Array fields_;
};

class TickInfo : public MemoryRetainer {
 public:
  inline AliasedUint8Array& fields();
  inline bool has_tick_scheduled() const;
  inline bool has_rejection_to_warn() const;

  SET_MEMORY_INFO_NAME(TickInfo)
  SET_SELF_SIZE(TickInfo)
  void MemoryInfo(MemoryTracker* tracker) const override;

  TickInfo(const TickInfo&) = delete;
  TickInfo& operator=(const TickInfo&) = delete;
  TickInfo(TickInfo&&) = delete;
  TickInfo& operator=(TickInfo&&) = delete;
  ~TickInfo() = default;

  struct SerializeInfo {
    AliasedBufferIndex fields;
  };
  SerializeInfo Serialize(v8::Local<v8::Context> context,
                          v8::SnapshotCreator* creator);
  void Deserialize(v8::Local<v8::Context> context);

 private:
  friend class Environment;  // So we can call the constructor.
  explicit TickInfo(v8::Isolate* isolate, const SerializeInfo* info);

  enum Fields { kHasTickScheduled = 0, kHasRejectionToWarn, kFieldsCount };

  AliasedUint8Array fields_;
};

class TrackingTraceStateObserver :
    public v8::TracingController::TraceStateObserver {
 public:
  explicit TrackingTraceStateObserver(Environment* env) : env_(env) {}

  void OnTraceEnabled() override {
    UpdateTraceCategoryState();
  }

  void OnTraceDisabled() override {
    UpdateTraceCategoryState();
  }

 private:
  void UpdateTraceCategoryState();

  Environment* env_;
};

class ShouldNotAbortOnUncaughtScope {
 public:
  explicit inline ShouldNotAbortOnUncaughtScope(Environment* env);
  inline void Close();
  inline ~ShouldNotAbortOnUncaughtScope();
  ShouldNotAbortOnUncaughtScope(const ShouldNotAbortOnUncaughtScope&) = delete;
  ShouldNotAbortOnUncaughtScope& operator=(
      const ShouldNotAbortOnUncaughtScope&) = delete;
  ShouldNotAbortOnUncaughtScope(ShouldNotAbortOnUncaughtScope&&) = delete;
  ShouldNotAbortOnUncaughtScope& operator=(ShouldNotAbortOnUncaughtScope&&) =
      delete;

 private:
  Environment* env_;
};

typedef void (*DeserializeRequestCallback)(v8::Local<v8::Context> context,
                                           v8::Local<v8::Object> holder,
                                           int index,
                                           InternalFieldInfoBase* info);
struct DeserializeRequest {
  DeserializeRequestCallback cb;
  v8::Global<v8::Object> holder;
  int index;
  InternalFieldInfoBase* info = nullptr;  // Owned by the request
};

struct EnvSerializeInfo {
  AsyncHooks::SerializeInfo async_hooks;
  TickInfo::SerializeInfo tick_info;
  ImmediateInfo::SerializeInfo immediate_info;
  AliasedBufferIndex timeout_info;
  performance::PerformanceState::SerializeInfo performance_state;
  AliasedBufferIndex exit_info;
  AliasedBufferIndex stream_base_state;
  AliasedBufferIndex should_abort_on_uncaught_toggle;

  RealmSerializeInfo principal_realm;
  friend std::ostream& operator<<(std::ostream& o, const EnvSerializeInfo& i);
};

struct SnapshotMetadata {
  // For now kFullyCustomized is only built with the --build-snapshot CLI flag.
  // We might want to add more types of snapshots in the future.
  enum class Type : uint8_t { kDefault, kFullyCustomized };

  Type type;
  std::string node_version;
  std::string node_arch;
  std::string node_platform;
  SnapshotFlags flags;
};

struct SnapshotData {
  enum class DataOwnership { kOwned, kNotOwned };

  static const uint32_t kMagic = 0x143da19;
  static const SnapshotIndex kNodeVMContextIndex = 0;
  static const SnapshotIndex kNodeBaseContextIndex = kNodeVMContextIndex + 1;
  static const SnapshotIndex kNodeMainContextIndex = kNodeBaseContextIndex + 1;

  DataOwnership data_ownership = DataOwnership::kOwned;

  SnapshotMetadata metadata;

  // The result of v8::SnapshotCreator::CreateBlob() during the snapshot
  // building process.
  v8::StartupData v8_snapshot_blob_data{nullptr, 0};

  IsolateDataSerializeInfo isolate_data_info;
  // TODO(joyeecheung): there should be a vector of env_info once we snapshot
  // the worker environments.
  EnvSerializeInfo env_info;

  // A vector of built-in ids and v8::ScriptCompiler::CachedData, this can be
  // shared across Node.js instances because they are supposed to share the
  // read only space. We use builtins::CodeCacheInfo because
  // v8::ScriptCompiler::CachedData is not copyable.
  std::vector<builtins::CodeCacheInfo> code_cache;

  void ToFile(FILE* out) const;
  std::vector<char> ToBlob() const;
  // If returns false, the metadata doesn't match the current Node.js binary,
  // and the caller should not consume the snapshot data.
  bool Check() const;
  static bool FromFile(SnapshotData* out, FILE* in);
  static bool FromBlob(SnapshotData* out, const std::vector<char>& in);
  static bool FromBlob(SnapshotData* out, std::string_view in);
  static const SnapshotData* FromEmbedderWrapper(
      const EmbedderSnapshotData* data);
  EmbedderSnapshotData::Pointer AsEmbedderWrapper() const;

  ~SnapshotData();
};

void DefaultProcessExitHandlerInternal(Environment* env, ExitCode exit_code);
v8::Maybe<ExitCode> SpinEventLoopInternal(Environment* env);
v8::Maybe<ExitCode> EmitProcessExitInternal(Environment* env);

class Cleanable {
 public:
  virtual ~Cleanable() = default;

 protected:
  ListNode<Cleanable> cleanable_queue_;

 private:
  virtual void Clean() = 0;
  friend class Environment;
};

/**
 * Environment is a per-isolate data structure that represents an execution
 * environment. Each environment has a principal realm. An environment can
 * create multiple subsidiary synthetic realms.
 */
class Environment final : public MemoryRetainer {
 public:
  Environment(const Environment&) = delete;
  Environment& operator=(const Environment&) = delete;
  Environment(Environment&&) = delete;
  Environment& operator=(Environment&&) = delete;

  SET_MEMORY_INFO_NAME(Environment)

  static std::string GetExecPath(const std::vector<std::string>& argv);
  static std::string GetCwd(const std::string& exec_path);

  inline size_t SelfSize() const override;
  bool IsRootNode() const override { return true; }
  void MemoryInfo(MemoryTracker* tracker) const override;

  EnvSerializeInfo Serialize(v8::SnapshotCreator* creator);
  void DeserializeProperties(const EnvSerializeInfo* info);

  void PrintInfoForSnapshotIfDebug();
  void EnqueueDeserializeRequest(DeserializeRequestCallback cb,
                                 v8::Local<v8::Object> holder,
                                 int index,
                                 InternalFieldInfoBase* info);
  void RunDeserializeRequests();
  // Should be called before InitializeInspector()
  void InitializeDiagnostics();

#if HAVE_INSPECTOR
  // If the environment is created for a worker, pass parent_handle and
  // the ownership if transferred into the Environment.
  void InitializeInspector(
      std::unique_ptr<inspector::ParentInspectorHandle> parent_handle);
  void WaitForInspectorFrontendByOptions();
#endif

  inline size_t async_callback_scope_depth() const;
  inline void PushAsyncCallbackScope();
  inline void PopAsyncCallbackScope();

  static inline Environment* GetCurrent(v8::Isolate* isolate);
  static inline Environment* GetCurrent(v8::Local<v8::Context> context);
  static inline Environment* GetCurrent(
      const v8::FunctionCallbackInfo<v8::Value>& info);

  template <typename T>
  static inline Environment* GetCurrent(
      const v8::PropertyCallbackInfo<T>& info);

  // Create an Environment without initializing a main Context. Use
  // InitializeMainContext() to initialize a main context for it.
  Environment(IsolateData* isolate_data,
              v8::Isolate* isolate,
              const std::vector<std::string>& args,
              const std::vector<std::string>& exec_args,
              const EnvSerializeInfo* env_info,
              EnvironmentFlags::Flags flags,
              ThreadId thread_id,
              std::string_view thread_name = "");
  void InitializeMainContext(v8::Local<v8::Context> context,
                             const EnvSerializeInfo* env_info);
  ~Environment() override;

  void InitializeLibuv();
  inline const std::vector<std::string>& exec_argv();
  inline const std::vector<std::string>& argv();
  const std::string& exec_path() const;

  void CleanupHandles();
  void Exit(ExitCode code);
  void ExitEnv(StopFlags::Flags flags);
  void ClosePerEnvHandles();

  template <typename T, typename OnCloseCallback>
  inline void CloseHandle(T* handle, OnCloseCallback callback);

  void ResetPromiseHooks(v8::Local<v8::Function> init,
                         v8::Local<v8::Function> before,
                         v8::Local<v8::Function> after,
                         v8::Local<v8::Function> resolve);
  void AssignToContext(v8::Local<v8::Context> context,
                       Realm* realm,
                       const ContextInfo& info);
  void UnassignFromContext(v8::Local<v8::Context> context);
  void TrackShadowRealm(shadow_realm::ShadowRealm* realm);
  void UntrackShadowRealm(shadow_realm::ShadowRealm* realm);

  void StartProfilerIdleNotifier();

  inline v8::Isolate* isolate() const;
  inline uv_loop_t* event_loop() const;
  void TryLoadAddon(const char* filename,
                    int flags,
                    const std::function<bool(binding::DLib*)>& was_loaded);

  static inline Environment* from_timer_handle(uv_timer_t* handle);
  inline uv_timer_t* timer_handle();

  static inline Environment* from_immediate_check_handle(uv_check_t* handle);
  inline uv_check_t* immediate_check_handle();
  inline uv_idle_t* immediate_idle_handle();

  inline void IncreaseWaitingRequestCounter();
  inline void DecreaseWaitingRequestCounter();

  inline AsyncHooks* async_hooks();
  inline ImmediateInfo* immediate_info();
  inline AliasedInt32Array& timeout_info();
  inline TickInfo* tick_info();
  inline uint64_t timer_base() const;
  inline permission::Permission* permission();
  inline std::shared_ptr<KVStore> env_vars();
  inline void set_env_vars(std::shared_ptr<KVStore> env_vars);

  inline IsolateData* isolate_data() const;

  inline bool printed_error() const;
  inline void set_printed_error(bool value);

  void PrintSyncTrace() const;
  inline void set_trace_sync_io(bool value);

  inline void set_force_context_aware(bool value);
  inline bool force_context_aware() const;

  // This contains fields that are a pseudo-boolean that keeps track of whether
  // the process is exiting, an integer representing the process exit code, and
  // a pseudo-boolean to indicate whether the exit code is undefined.
  inline AliasedInt32Array& exit_info();
  inline void set_exiting(bool value);
  bool exiting() const;
  inline ExitCode exit_code(const ExitCode default_code) const;

  inline void set_exit_code(const ExitCode code);

  // This stores whether the --abort-on-uncaught-exception flag was passed
  // to Node.
  inline bool abort_on_uncaught_exception() const;
  inline void set_abort_on_uncaught_exception(bool value);
  // This is a pseudo-boolean that keeps track of whether an uncaught exception
  // should abort the process or not if --abort-on-uncaught-exception was
  // passed to Node. If the flag was not passed, it is ignored.
  inline AliasedUint32Array& should_abort_on_uncaught_toggle();

  inline AliasedInt32Array& stream_base_state();

  // The necessary API for async_hooks.
  inline double new_async_id();
  inline double execution_async_id();
  inline double trigger_async_id();
  inline double get_default_trigger_async_id();

  // List of id's that have been destroyed and need the destroy() cb called.
  inline std::vector<double>* destroy_async_id_list();

  builtins::BuiltinLoader* builtin_loader();

  std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;

  EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; }

  inline performance::PerformanceState* performance_state();

  v8::Maybe<void> CollectUVExceptionInfo(v8::Local<v8::Value> context,
                                         int errorno,
                                         const char* syscall = nullptr,
                                         const char* message = nullptr,
                                         const char* path = nullptr,
                                         const char* dest = nullptr);

  // If this flag is set, calls into JS (if they would be observable
  // from userland) must be avoided.  This flag does not indicate whether
  // calling into JS is allowed from a VM perspective at this point.
  inline bool can_call_into_js() const;
  inline void set_can_call_into_js(bool can_call_into_js);

  // Increase or decrease a counter that manages whether this Environment
  // keeps the event loop alive on its own or not. The counter starts out at 0,
  // meaning it does not, and any positive value will make it keep the event
  // loop alive.
  // This is used by Workers to manage their own .ref()/.unref() implementation,
  // as Workers aren't directly associated with their own libuv handles.
  void add_refs(int64_t diff);

  // Convenient getter of the principal realm's has_run_bootstrapping_code().
  inline bool has_run_bootstrapping_code() const;

  inline bool has_serialized_options() const;
  inline void set_has_serialized_options(bool has_serialized_options);

  inline bool is_main_thread() const;
  inline bool no_native_addons() const;
  inline bool should_not_register_esm_loader() const;
  inline bool should_create_inspector() const;
  inline bool should_wait_for_inspector_frontend() const;
  inline bool owns_process_state() const;
  inline bool owns_inspector() const;
  inline bool tracks_unmanaged_fds() const;
  inline bool hide_console_windows() const;
  inline bool no_global_search_paths() const;
  inline bool should_start_debug_signal_handler() const;
  inline bool no_browser_globals() const;
  inline uint64_t thread_id() const;
  inline std::string_view thread_name() const;
  inline worker::Worker* worker_context() const;
  Environment* worker_parent_env() const;
  inline void add_sub_worker_context(worker::Worker* context);
  inline void remove_sub_worker_context(worker::Worker* context);
  void stop_sub_worker_contexts();
  template <typename Fn>
  inline void ForEachWorker(Fn&& iterator);
  // Determine if the environment is stopping. This getter is thread-safe.
  inline bool is_stopping() const;
  inline void set_stopping(bool value);
  inline std::list<node_module>* extra_linked_bindings();
  inline node_module* extra_linked_bindings_head();
  inline node_module* extra_linked_bindings_tail();
  inline const Mutex& extra_linked_bindings_mutex() const;

  inline bool filehandle_close_warning() const;
  inline void set_filehandle_close_warning(bool on);

  inline void set_source_maps_enabled(bool on);
  inline bool source_maps_enabled() const;

  inline void ThrowError(const char* errmsg);
  inline void ThrowTypeError(const char* errmsg);
  inline void ThrowRangeError(const char* errmsg);
  inline void ThrowStdErrException(std::error_code error_code,
                                   const char* syscall,
                                   const char* path = nullptr);
  inline void ThrowErrnoException(int errorno,
                                  const char* syscall = nullptr,
                                  const char* message = nullptr,
                                  const char* path = nullptr);
  inline void ThrowUVException(int errorno,
                               const char* syscall = nullptr,
                               const char* message = nullptr,
                               const char* path = nullptr,
                               const char* dest = nullptr);

  void AtExit(void (*cb)(void* arg), void* arg);
  void RunAtExitCallbacks();

  v8::Maybe<bool> CheckUnsettledTopLevelAwait() const;
  void RunWeakRefCleanup();

  v8::MaybeLocal<v8::Value> RunSnapshotSerializeCallback() const;
  v8::MaybeLocal<v8::Value> RunSnapshotDeserializeCallback() const;
  v8::MaybeLocal<v8::Value> RunSnapshotDeserializeMain() const;

  // Primitive values are shared across realms.
  // The getters simply proxy to the per-isolate primitive.
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define V(TypeName, PropertyName)                                             \
  inline v8::Local<TypeName> PropertyName() const;
  PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
  PER_ISOLATE_SYMBOL_PROPERTIES(VY)
  PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VY
#undef VP

#define V(PropertyName, TypeName)                                             \
  inline v8::Local<TypeName> PropertyName() const;                            \
  inline void set_ ## PropertyName(v8::Local<TypeName> value);
  PER_ISOLATE_TEMPLATE_PROPERTIES(V)
  // Per-realm strong persistent values of the principal realm.
  // Get/set the value with an explicit realm instead when possible.
  // Deprecate soon.
  PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V

  // Return the context of the principal realm.
  // Get the context with an explicit realm instead when possible.
  // Deprecate soon.
  inline v8::Local<v8::Context> context() const;
  inline PrincipalRealm* principal_realm() const;

#if HAVE_INSPECTOR
  inline inspector::Agent* inspector_agent() const {
    return inspector_agent_.get();
  }
  inline void StopInspector() {
    inspector_agent_.reset();
  }

  inline bool is_in_inspector_console_call() const;
  inline void set_is_in_inspector_console_call(bool value);
#endif

  typedef ListHead<HandleWrap, &HandleWrap::handle_wrap_queue_> HandleWrapQueue;
  typedef ListHead<ReqWrapBase, &ReqWrapBase::req_wrap_queue_> ReqWrapQueue;
  typedef ListHead<Cleanable, &Cleanable::cleanable_queue_> CleanableQueue;

  inline HandleWrapQueue* handle_wrap_queue() { return &handle_wrap_queue_; }
  inline CleanableQueue* cleanable_queue() {
    return &cleanable_queue_;
  }
  inline ReqWrapQueue* req_wrap_queue() { return &req_wrap_queue_; }

  // https://w3c.github.io/hr-time/#dfn-time-origin
  inline uint64_t time_origin() {
    return time_origin_;
  }
  // https://w3c.github.io/hr-time/#dfn-get-time-origin-timestamp
  inline double time_origin_timestamp() {
    return time_origin_timestamp_;
  }

  inline bool EmitProcessEnvWarning() {
    bool current_value = emit_env_nonstring_warning_;
    emit_env_nonstring_warning_ = false;
    return current_value;
  }

  inline bool EmitErrNameWarning() {
    bool current_value = emit_err_name_warning_;
    emit_err_name_warning_ = false;
    return current_value;
  }

  // cb will be called as cb(env) on the next event loop iteration.
  // Unlike the JS setImmediate() function, nested SetImmediate() calls will
  // be run without returning control to the event loop, similar to nextTick().
  template <typename Fn>
  inline void SetImmediate(
      Fn&& cb, CallbackFlags::Flags flags = CallbackFlags::kRefed);
  template <typename Fn>
  // This behaves like SetImmediate() but can be called from any thread.
  inline void SetImmediateThreadsafe(
      Fn&& cb, CallbackFlags::Flags flags = CallbackFlags::kRefed);
  // This behaves like V8's Isolate::RequestInterrupt(), but also accounts for
  // the event loop (i.e. combines the V8 function with SetImmediate()).
  // The passed callback may not throw exceptions.
  // This function can be called from any thread.
  template <typename Fn>
  inline void RequestInterrupt(Fn&& cb);
  // This needs to be available for the JS-land setImmediate().
  void ToggleImmediateRef(bool ref);

  inline void PushShouldNotAbortOnUncaughtScope();
  inline void PopShouldNotAbortOnUncaughtScope();
  inline bool inside_should_not_abort_on_uncaught_scope() const;

  static inline Environment* ForAsyncHooks(AsyncHooks* hooks);

  v8::Local<v8::Value> GetNow();
  uint64_t GetNowUint64();

  void ScheduleTimer(int64_t duration);
  void ToggleTimerRef(bool ref);

  inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg);
  inline void RemoveCleanupHook(CleanupQueue::Callback cb, void* arg);
  void RunCleanup();

  static void TracePromises(v8::PromiseHookType type,
                            v8::Local<v8::Promise> promise,
                            v8::Local<v8::Value> parent);
  static size_t NearHeapLimitCallback(void* data,
                                      size_t current_heap_limit,
                                      size_t initial_heap_limit);
  static void BuildEmbedderGraph(v8::Isolate* isolate,
                                 v8::EmbedderGraph* graph,
                                 void* data);

  inline std::shared_ptr<EnvironmentOptions> options();
  inline std::shared_ptr<ExclusiveAccess<HostPort>> inspector_host_port();

  inline int64_t stack_trace_limit() const;

#if HAVE_INSPECTOR
  void set_coverage_connection(
      std::unique_ptr<profiler::V8CoverageConnection> connection);
  profiler::V8CoverageConnection* coverage_connection();

  inline void set_coverage_directory(const char* directory);
  inline const std::string& coverage_directory() const;

  void set_cpu_profiler_connection(
      std::unique_ptr<profiler::V8CpuProfilerConnection> connection);
  profiler::V8CpuProfilerConnection* cpu_profiler_connection();

  inline void set_cpu_prof_name(const std::string& name);
  inline const std::string& cpu_prof_name() const;

  inline void set_cpu_prof_interval(uint64_t interval);
  inline uint64_t cpu_prof_interval() const;

  inline void set_cpu_prof_dir(const std::string& dir);
  inline const std::string& cpu_prof_dir() const;

  void set_heap_profiler_connection(
      std::unique_ptr<profiler::V8HeapProfilerConnection> connection);
  profiler::V8HeapProfilerConnection* heap_profiler_connection();

  inline void set_heap_prof_name(const std::string& name);
  inline const std::string& heap_prof_name() const;

  inline void set_heap_prof_dir(const std::string& dir);
  inline const std::string& heap_prof_dir() const;

  inline void set_heap_prof_interval(uint64_t interval);
  inline uint64_t heap_prof_interval() const;

#endif  // HAVE_INSPECTOR

  inline const EmbedderPreloadCallback& embedder_preload() const;
  inline void set_embedder_preload(EmbedderPreloadCallback fn);

  inline void set_process_exit_handler(
      std::function<void(Environment*, ExitCode)>&& handler);

  inline CompileCacheHandler* compile_cache_handler();
  inline bool use_compile_cache() const;
  void InitializeCompileCache();
  // Enable built-in compile cache if it has not yet been enabled.
  // The cache will be persisted to disk on exit.
  CompileCacheEnableResult EnableCompileCache(const std::string& cache_dir);
  void FlushCompileCache();

  void RunAndClearNativeImmediates(bool only_refed = false);
  void RunAndClearInterrupts();

  uv_buf_t allocate_managed_buffer(const size_t suggested_size);
  std::unique_ptr<v8::BackingStore> release_managed_buffer(const uv_buf_t& buf);

  void AddUnmanagedFd(int fd);
  void RemoveUnmanagedFd(int fd);

  template <typename T>
  void ForEachRealm(T&& iterator) const;

  inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
  inline bool is_in_heapsnapshot_heap_limit_callback() const;

  inline bool report_exclude_env() const;

  inline void AddHeapSnapshotNearHeapLimitCallback();

  inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);

  v8::CpuProfilingResult StartCpuProfile(std::string_view name);
  v8::CpuProfile* StopCpuProfile(std::string_view name);

  // Field identifiers for exit_info_
  enum ExitInfoField {
    kExiting = 0,
    kExitCode,
    kHasExitCode,
    kExitInfoFieldCount
  };

#if HAVE_OPENSSL
#if OPENSSL_VERSION_MAJOR >= 3
  // We declare another alias here to avoid having to include crypto_util.h
  using EVPMDPointer = DeleteFnPtr<EVP_MD, EVP_MD_free>;
  std::vector<EVPMDPointer> evp_md_cache;
#endif  // OPENSSL_VERSION_MAJOR >= 3
  std::unordered_map<std::string, size_t> alias_to_md_id_map;
  std::vector<std::string> supported_hash_algorithms;
#endif  // HAVE_OPENSSL

  v8::Global<v8::Module> temporary_required_module_facade_original;

  void SetAsyncResourceContextFrame(std::uintptr_t async_resource_handle,
                                    v8::Global<v8::Value>&&);

  const v8::Global<v8::Value>& GetAsyncResourceContextFrame(
      std::uintptr_t async_resource_handle);

  void RemoveAsyncResourceContextFrame(std::uintptr_t async_resource_handle);

 private:
  inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>,
                                                     v8::Local<v8::Value>),
                         const char* errmsg);
  void TrackContext(v8::Local<v8::Context> context);
  void UntrackContext(v8::Local<v8::Context> context);

  std::list<binding::DLib> loaded_addons_;
  v8::Isolate* const isolate_;
  IsolateData* const isolate_data_;

  bool env_handle_initialized_ = false;
  uv_timer_t timer_handle_;
  uv_check_t immediate_check_handle_;
  uv_idle_t immediate_idle_handle_;
  uv_prepare_t idle_prepare_handle_;
  uv_check_t idle_check_handle_;
  uv_async_t task_queues_async_;
  int64_t task_queues_async_refs_ = 0;

  // These may be read by ctors and should be listed before complex fields.
  std::atomic_bool is_stopping_{false};
  std::atomic_bool can_call_into_js_{true};

  AsyncHooks async_hooks_;
  ImmediateInfo immediate_info_;
  AliasedInt32Array timeout_info_;
  TickInfo tick_info_;
  permission::Permission permission_;
  const uint64_t timer_base_;
  std::shared_ptr<KVStore> env_vars_;
  bool printed_error_ = false;
  bool trace_sync_io_ = false;
  bool emit_env_nonstring_warning_ = true;
  bool emit_err_name_warning_ = true;
  bool emit_filehandle_warning_ = true;
  bool source_maps_enabled_ = false;

  size_t async_callback_scope_depth_ = 0;
  std::vector<double> destroy_async_id_list_;
  std::unordered_set<shadow_realm::ShadowRealm*> shadow_realms_;

#if HAVE_INSPECTOR
  std::unique_ptr<profiler::V8CoverageConnection> coverage_connection_;
  std::unique_ptr<profiler::V8CpuProfilerConnection> cpu_profiler_connection_;
  std::string coverage_directory_;
  std::string cpu_prof_dir_;
  std::string cpu_prof_name_;
  uint64_t cpu_prof_interval_;
  std::unique_ptr<profiler::V8HeapProfilerConnection> heap_profiler_connection_;
  std::string heap_prof_dir_;
  std::string heap_prof_name_;
  uint64_t heap_prof_interval_;
#endif  // HAVE_INSPECTOR

  std::unique_ptr<CompileCacheHandler> compile_cache_handler_;
  std::shared_ptr<EnvironmentOptions> options_;
  // options_ contains debug options parsed from CLI arguments,
  // while inspector_host_port_ stores the actual inspector host
  // and port being used. For example the port is -1 by default
  // and can be specified as 0 (meaning any port allocated when the
  // server starts listening), but when the inspector server starts
  // the inspector_host_port_->port() will be the actual port being
  // used.
  std::shared_ptr<ExclusiveAccess<HostPort>> inspector_host_port_;
  std::vector<std::string> exec_argv_;
  std::vector<std::string> argv_;
  std::string exec_path_;

  bool is_in_heapsnapshot_heap_limit_callback_ = false;
  uint32_t heap_limit_snapshot_taken_ = 0;
  uint32_t heap_snapshot_near_heap_limit_ = 0;
  bool heapsnapshot_near_heap_limit_callback_added_ = false;

  uint32_t module_id_counter_ = 0;
  uint32_t script_id_counter_ = 0;
  uint32_t function_id_counter_ = 0;
  uint32_t trace_promise_id_counter_ = 0;

  AliasedInt32Array exit_info_;

  AliasedUint32Array should_abort_on_uncaught_toggle_;
  int should_not_abort_scope_counter_ = 0;

  std::unique_ptr<TrackingTraceStateObserver> trace_state_observer_;

  AliasedInt32Array stream_base_state_;

  // As PerformanceNodeTiming is exposed in worker_threads, the per_process
  // time origin is exposed in the worker threads. This is an intentional
  // diverge from the HTML spec of web workers.
  // Process start time from the monotonic clock. This should not be used as an
  // absolute time, but only as a time relative to another monotonic clock time.
  const uint64_t time_origin_;
  // Process start timestamp from the wall clock. This is an absolute time
  // exposed as `performance.timeOrigin`.
  const double time_origin_timestamp_;
  // This is the time when the environment is created.
  const uint64_t environment_start_;
  std::unique_ptr<performance::PerformanceState> performance_state_;

  bool has_serialized_options_ = false;

  uint64_t flags_;
  uint64_t thread_id_;
  std::string thread_name_;
  std::unordered_set<worker::Worker*> sub_worker_contexts_;

#if HAVE_INSPECTOR
  std::unique_ptr<inspector::Agent> inspector_agent_;
  bool is_in_inspector_console_call_ = false;
#endif

  std::list<DeserializeRequest> deserialize_requests_;

  // handle_wrap_queue_ and req_wrap_queue_ needs to be at a fixed offset from
  // the start of the class because it is used by
  // src/node_postmortem_metadata.cc to calculate offsets and generate debug
  // symbols for Environment, which assumes that the position of members in
  // memory are predictable. For more information please refer to
  // `doc/contributing/node-postmortem-support.md`
  friend int GenDebugSymbols();
  CleanableQueue cleanable_queue_;
  HandleWrapQueue handle_wrap_queue_;
  ReqWrapQueue req_wrap_queue_;
  int handle_cleanup_waiting_ = 0;
  int request_waiting_ = 0;

  EnabledDebugList enabled_debug_list_;

  std::vector<v8::Global<v8::Context>> contexts_;
  std::list<node_module> extra_linked_bindings_;
  Mutex extra_linked_bindings_mutex_;

  static void RunTimers(uv_timer_t* handle);

  struct ExitCallback {
    void (*cb_)(void* arg);
    void* arg_;
  };

  std::list<ExitCallback> at_exit_functions_;

  typedef CallbackQueue<void, Environment*> NativeImmediateQueue;
  NativeImmediateQueue native_immediates_;
  Mutex native_immediates_threadsafe_mutex_;
  NativeImmediateQueue native_immediates_threadsafe_;
  NativeImmediateQueue native_immediates_interrupts_;
  // Also guarded by native_immediates_threadsafe_mutex_. This can be used when
  // trying to post tasks from other threads to an Environment, as the libuv
  // handle for the immediate queues (task_queues_async_) may not be initialized
  // yet or already have been destroyed.
  bool task_queues_async_initialized_ = false;

  std::atomic<Environment**> interrupt_data_ {nullptr};
  void RequestInterruptFromV8();
  static void CheckImmediate(uv_check_t* handle);

  CleanupQueue cleanup_queue_;
  bool started_cleanup_ = false;

  std::unordered_set<int> unmanaged_fds_;

  std::function<void(Environment*, ExitCode)> process_exit_handler_{
      DefaultProcessExitHandlerInternal};

  std::unique_ptr<PrincipalRealm> principal_realm_ = nullptr;

  builtins::BuiltinLoader builtin_loader_;
  EmbedderPreloadCallback embedder_preload_;

  // Used by allocate_managed_buffer() and release_managed_buffer() to keep
  // track of the BackingStore for a given pointer.
  std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
      released_allocated_buffers_;

  std::unordered_map<std::uintptr_t, v8::Global<v8::Value>>
      async_resource_context_frames_;

  v8::CpuProfiler* cpu_profiler_ = nullptr;
  std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;
};

}  // namespace node

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif  // SRC_ENV_H_
