%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/js_native_api_v8.cc

#include <algorithm>
#include <climits>  // INT_MAX
#include <cmath>
#define NAPI_EXPERIMENTAL
#include "env-inl.h"
#include "js_native_api.h"
#include "js_native_api_v8.h"
#include "util-inl.h"

#define CHECK_MAYBE_NOTHING(env, maybe, status)                                \
  RETURN_STATUS_IF_FALSE((env), !((maybe).IsNothing()), (status))

#define CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe, status)                  \
  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsNothing()), (status))

#define CHECK_TO_NUMBER(env, context, result, src)                             \
  CHECK_TO_TYPE((env), Number, (context), (result), (src), napi_number_expected)

// n-api defines NAPI_AUTO_LENGTH as the indicator that a string
// is null terminated. For V8 the equivalent is -1. The assert
// validates that our cast of NAPI_AUTO_LENGTH results in -1 as
// needed by V8.
#define CHECK_NEW_FROM_UTF8_LEN(env, result, str, len)                         \
  do {                                                                         \
    static_assert(static_cast<int>(NAPI_AUTO_LENGTH) == -1,                    \
                  "Casting NAPI_AUTO_LENGTH to int must result in -1");        \
    RETURN_STATUS_IF_FALSE(                                                    \
        (env), (len == NAPI_AUTO_LENGTH) || len <= INT_MAX, napi_invalid_arg); \
    RETURN_STATUS_IF_FALSE((env), (str) != nullptr, napi_invalid_arg);         \
    auto str_maybe = v8::String::NewFromUtf8((env)->isolate,                   \
                                             (str),                            \
                                             v8::NewStringType::kInternalized, \
                                             static_cast<int>(len));           \
    CHECK_MAYBE_EMPTY((env), str_maybe, napi_generic_failure);                 \
    (result) = str_maybe.ToLocalChecked();                                     \
  } while (0)

#define CHECK_NEW_FROM_UTF8(env, result, str)                                  \
  CHECK_NEW_FROM_UTF8_LEN((env), (result), (str), NAPI_AUTO_LENGTH)

#define CHECK_NEW_STRING_ARGS(env, str, length, result)                        \
  do {                                                                         \
    CHECK_ENV_NOT_IN_GC((env));                                                \
    if ((length) > 0) CHECK_ARG((env), (str));                                 \
    CHECK_ARG((env), (result));                                                \
    RETURN_STATUS_IF_FALSE(                                                    \
        (env),                                                                 \
        ((length) == NAPI_AUTO_LENGTH) || (length) <= INT_MAX,                 \
        napi_invalid_arg);                                                     \
  } while (0)

#define CREATE_TYPED_ARRAY(                                                    \
    env, type, size_of_element, buffer, byte_offset, length, out)              \
  do {                                                                         \
    if ((size_of_element) > 1) {                                               \
      THROW_RANGE_ERROR_IF_FALSE(                                              \
          (env),                                                               \
          (byte_offset) % (size_of_element) == 0,                              \
          "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT",                             \
          "start offset of " #type                                             \
          " should be a multiple of " #size_of_element);                       \
    }                                                                          \
    THROW_RANGE_ERROR_IF_FALSE(                                                \
        (env),                                                                 \
        (length) * (size_of_element) + (byte_offset) <= buffer->ByteLength(),  \
        "ERR_NAPI_INVALID_TYPEDARRAY_LENGTH",                                  \
        "Invalid typed array length");                                         \
    (out) = v8::type::New((buffer), (byte_offset), (length));                  \
  } while (0)

void napi_env__::InvokeFinalizerFromGC(v8impl::RefTracker* finalizer) {
  if (module_api_version != NAPI_VERSION_EXPERIMENTAL) {
    EnqueueFinalizer(finalizer);
  } else {
    // The experimental code calls finalizers immediately to release native
    // objects as soon as possible. In that state any code that may affect GC
    // state causes a fatal error. To work around this issue the finalizer code
    // can call node_api_post_finalizer.
    auto restore_state = node::OnScopeLeave(
        [this, saved = in_gc_finalizer] { in_gc_finalizer = saved; });
    in_gc_finalizer = true;
    finalizer->Finalize();
  }
}

namespace v8impl {
namespace {

template <typename CCharType, typename StringMaker>
napi_status NewString(napi_env env,
                      const CCharType* str,
                      size_t length,
                      napi_value* result,
                      StringMaker string_maker) {
  CHECK_NEW_STRING_ARGS(env, str, length, result);

  auto isolate = env->isolate;
  auto str_maybe = string_maker(isolate);
  CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
  *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
  return napi_clear_last_error(env);
}

template <typename CharType, typename CreateAPI, typename StringMaker>
napi_status NewExternalString(napi_env env,
                              CharType* str,
                              size_t length,
                              napi_finalize finalize_callback,
                              void* finalize_hint,
                              napi_value* result,
                              bool* copied,
                              CreateAPI create_api,
                              StringMaker string_maker) {
  CHECK_NEW_STRING_ARGS(env, str, length, result);

  napi_status status;
#if defined(V8_ENABLE_SANDBOX)
  status = create_api(env, str, length, result);
  if (status == napi_ok) {
    if (copied != nullptr) {
      *copied = true;
    }
    if (finalize_callback) {
      env->CallFinalizer(
          finalize_callback, static_cast<CharType*>(str), finalize_hint);
    }
  }
#else
  status = NewString(env, str, length, result, string_maker);
  if (status == napi_ok && copied != nullptr) {
    *copied = false;
  }
#endif  // V8_ENABLE_SANDBOX
  return status;
}

class TrackedStringResource : public Finalizer, RefTracker {
 public:
  TrackedStringResource(napi_env env,
                        napi_finalize finalize_callback,
                        void* data,
                        void* finalize_hint)
      : Finalizer(env, finalize_callback, data, finalize_hint) {
    Link(finalize_callback == nullptr ? &env->reflist
                                      : &env->finalizing_reflist);
  }

 protected:
  // The only time Finalize() gets called before Dispose() is if the
  // environment is dying. Finalize() expects that the item will be unlinked,
  // so we do it here. V8 will still call Dispose() on us later, so we don't do
  // any deleting here. We just null out env_ to avoid passing a stale pointer
  // to the user's finalizer when V8 does finally call Dispose().
  void Finalize() override {
    Unlink();
    env_ = nullptr;
  }

  ~TrackedStringResource() {
    if (finalize_callback_ == nullptr) return;
    if (env_ == nullptr) {
      // The environment is dead. Call the finalizer directly.
      finalize_callback_(nullptr, finalize_data_, finalize_hint_);
    } else {
      // The environment is still alive. Let's remove ourselves from its list
      // of references and call the user's finalizer.
      Unlink();
      env_->CallFinalizer(finalize_callback_, finalize_data_, finalize_hint_);
    }
  }
};

class ExternalOneByteStringResource
    : public v8::String::ExternalOneByteStringResource,
      TrackedStringResource {
 public:
  ExternalOneByteStringResource(napi_env env,
                                char* string,
                                const size_t length,
                                napi_finalize finalize_callback,
                                void* finalize_hint)
      : TrackedStringResource(env, finalize_callback, string, finalize_hint),
        string_(string),
        length_(length) {}

  const char* data() const override { return string_; }
  size_t length() const override { return length_; }

 private:
  const char* string_;
  const size_t length_;
};

class ExternalStringResource : public v8::String::ExternalStringResource,
                               TrackedStringResource {
 public:
  ExternalStringResource(napi_env env,
                         char16_t* string,
                         const size_t length,
                         napi_finalize finalize_callback,
                         void* finalize_hint)
      : TrackedStringResource(env, finalize_callback, string, finalize_hint),
        string_(reinterpret_cast<uint16_t*>(string)),
        length_(length) {}

  const uint16_t* data() const override { return string_; }
  size_t length() const override { return length_; }

 private:
  const uint16_t* string_;
  const size_t length_;
};

inline napi_status V8NameFromPropertyDescriptor(
    napi_env env,
    const napi_property_descriptor* p,
    v8::Local<v8::Name>* result) {
  if (p->utf8name != nullptr) {
    CHECK_NEW_FROM_UTF8(env, *result, p->utf8name);
  } else {
    v8::Local<v8::Value> property_value =
        v8impl::V8LocalValueFromJsValue(p->name);

    RETURN_STATUS_IF_FALSE(env, property_value->IsName(), napi_name_expected);
    *result = property_value.As<v8::Name>();
  }

  return napi_ok;
}

// convert from n-api property attributes to v8::PropertyAttribute
inline v8::PropertyAttribute V8PropertyAttributesFromDescriptor(
    const napi_property_descriptor* descriptor) {
  unsigned int attribute_flags = v8::PropertyAttribute::None;

  // The napi_writable attribute is ignored for accessor descriptors, but
  // V8 would throw `TypeError`s on assignment with nonexistence of a setter.
  if ((descriptor->getter == nullptr && descriptor->setter == nullptr) &&
      (descriptor->attributes & napi_writable) == 0) {
    attribute_flags |= v8::PropertyAttribute::ReadOnly;
  }

  if ((descriptor->attributes & napi_enumerable) == 0) {
    attribute_flags |= v8::PropertyAttribute::DontEnum;
  }
  if ((descriptor->attributes & napi_configurable) == 0) {
    attribute_flags |= v8::PropertyAttribute::DontDelete;
  }

  return static_cast<v8::PropertyAttribute>(attribute_flags);
}

inline napi_deferred JsDeferredFromNodePersistent(
    v8impl::Persistent<v8::Value>* local) {
  return reinterpret_cast<napi_deferred>(local);
}

inline v8impl::Persistent<v8::Value>* NodePersistentFromJsDeferred(
    napi_deferred local) {
  return reinterpret_cast<v8impl::Persistent<v8::Value>*>(local);
}

class HandleScopeWrapper {
 public:
  explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {}

 private:
  v8::HandleScope scope;
};

// In node v0.10 version of v8, there is no EscapableHandleScope and the
// node v0.10 port use HandleScope::Close(Local<T> v) to mimic the behavior
// of a EscapableHandleScope::Escape(Local<T> v), but it is not the same
// semantics. This is an example of where the api abstraction fail to work
// across different versions.
class EscapableHandleScopeWrapper {
 public:
  explicit EscapableHandleScopeWrapper(v8::Isolate* isolate)
      : scope(isolate), escape_called_(false) {}
  bool escape_called() const { return escape_called_; }
  template <typename T>
  v8::Local<T> Escape(v8::Local<T> handle) {
    escape_called_ = true;
    return scope.Escape(handle);
  }

 private:
  v8::EscapableHandleScope scope;
  bool escape_called_;
};

inline napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) {
  return reinterpret_cast<napi_handle_scope>(s);
}

inline HandleScopeWrapper* V8HandleScopeFromJsHandleScope(napi_handle_scope s) {
  return reinterpret_cast<HandleScopeWrapper*>(s);
}

inline napi_escapable_handle_scope
JsEscapableHandleScopeFromV8EscapableHandleScope(
    EscapableHandleScopeWrapper* s) {
  return reinterpret_cast<napi_escapable_handle_scope>(s);
}

inline EscapableHandleScopeWrapper*
V8EscapableHandleScopeFromJsEscapableHandleScope(
    napi_escapable_handle_scope s) {
  return reinterpret_cast<EscapableHandleScopeWrapper*>(s);
}

inline napi_status ConcludeDeferred(napi_env env,
                                    napi_deferred deferred,
                                    napi_value result,
                                    bool is_resolved) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8impl::Persistent<v8::Value>* deferred_ref =
      NodePersistentFromJsDeferred(deferred);
  v8::Local<v8::Value> v8_deferred =
      v8::Local<v8::Value>::New(env->isolate, *deferred_ref);

  auto v8_resolver = v8_deferred.As<v8::Promise::Resolver>();

  v8::Maybe<bool> success =
      is_resolved ? v8_resolver->Resolve(
                        context, v8impl::V8LocalValueFromJsValue(result))
                  : v8_resolver->Reject(
                        context, v8impl::V8LocalValueFromJsValue(result));

  delete deferred_ref;

  RETURN_STATUS_IF_FALSE(env, success.FromMaybe(false), napi_generic_failure);

  return GET_RETURN_STATUS(env);
}

enum UnwrapAction { KeepWrap, RemoveWrap };

inline napi_status Unwrap(napi_env env,
                          napi_value js_object,
                          void** result,
                          UnwrapAction action) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, js_object);
  if (action == KeepWrap) {
    CHECK_ARG(env, result);
  }

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
  RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
  v8::Local<v8::Object> obj = value.As<v8::Object>();

  auto val = obj->GetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
                 .ToLocalChecked();
  RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg);
  Reference* reference =
      static_cast<v8impl::Reference*>(val.As<v8::External>()->Value());

  if (result) {
    *result = reference->Data();
  }

  if (action == RemoveWrap) {
    CHECK(obj->DeletePrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
              .FromJust());
    if (reference->ownership() == Ownership::kUserland) {
      // When the wrap is been removed, the finalizer should be reset.
      reference->ResetFinalizer();
    } else {
      delete reference;
    }
  }

  return GET_RETURN_STATUS(env);
}

//=== Function napi_callback wrapper =================================

// Use this data structure to associate callback data with each N-API function
// exposed to JavaScript. The structure is stored in a v8::External which gets
// passed into our callback wrapper. This reduces the performance impact of
// calling through N-API.
// Ref: benchmark/misc/function_call
// Discussion (incl. perf. data): https://github.com/nodejs/node/pull/21072
class CallbackBundle {
 public:
  // Creates an object to be made available to the static function callback
  // wrapper, used to retrieve the native callback function and data pointer.
  static inline v8::Local<v8::Value> New(napi_env env,
                                         napi_callback cb,
                                         void* data) {
    CallbackBundle* bundle = new CallbackBundle();
    bundle->cb = cb;
    bundle->cb_data = data;
    bundle->env = env;

    v8::Local<v8::Value> cbdata = v8::External::New(env->isolate, bundle);
    Reference::New(
        env, cbdata, 0, Ownership::kRuntime, Delete, bundle, nullptr);
    return cbdata;
  }
  napi_env env;   // Necessary to invoke C++ NAPI callback
  void* cb_data;  // The user provided callback data
  napi_callback cb;

 private:
  static void Delete(napi_env env, void* data, void* hint) {
    CallbackBundle* bundle = static_cast<CallbackBundle*>(data);
    delete bundle;
  }
};

// Base class extended by classes that wrap V8 function and property callback
// info.
class CallbackWrapper {
 public:
  inline CallbackWrapper(napi_value this_arg, size_t args_length, void* data)
      : _this(this_arg), _args_length(args_length), _data(data) {}

  virtual napi_value GetNewTarget() = 0;
  virtual void Args(napi_value* buffer, size_t bufferlength) = 0;
  virtual void SetReturnValue(napi_value value) = 0;

  napi_value This() { return _this; }

  size_t ArgsLength() { return _args_length; }

  void* Data() { return _data; }

 protected:
  const napi_value _this;
  const size_t _args_length;
  void* _data;
};

class CallbackWrapperBase : public CallbackWrapper {
 public:
  inline CallbackWrapperBase(const v8::FunctionCallbackInfo<v8::Value>& cbinfo,
                             const size_t args_length)
      : CallbackWrapper(
            JsValueFromV8LocalValue(cbinfo.This()), args_length, nullptr),
        _cbinfo(cbinfo) {
    _bundle = reinterpret_cast<CallbackBundle*>(
        cbinfo.Data().As<v8::External>()->Value());
    _data = _bundle->cb_data;
  }

 protected:
  inline void InvokeCallback() {
    napi_callback_info cbinfo_wrapper = reinterpret_cast<napi_callback_info>(
        static_cast<CallbackWrapper*>(this));

    // All other pointers we need are stored in `_bundle`
    napi_env env = _bundle->env;
    napi_callback cb = _bundle->cb;

    napi_value result = nullptr;
    bool exceptionOccurred = false;
    env->CallIntoModule([&](napi_env env) { result = cb(env, cbinfo_wrapper); },
                        [&](napi_env env, v8::Local<v8::Value> value) {
                          exceptionOccurred = true;
                          if (env->terminatedOrTerminating()) {
                            return;
                          }
                          env->isolate->ThrowException(value);
                        });

    if (!exceptionOccurred && (result != nullptr)) {
      this->SetReturnValue(result);
    }
  }

  const v8::FunctionCallbackInfo<v8::Value>& _cbinfo;
  CallbackBundle* _bundle;
};

class FunctionCallbackWrapper : public CallbackWrapperBase {
 public:
  static void Invoke(const v8::FunctionCallbackInfo<v8::Value>& info) {
    FunctionCallbackWrapper cbwrapper(info);
    cbwrapper.InvokeCallback();
  }

  static inline napi_status NewFunction(napi_env env,
                                        napi_callback cb,
                                        void* cb_data,
                                        v8::Local<v8::Function>* result) {
    v8::Local<v8::Value> cbdata = v8impl::CallbackBundle::New(env, cb, cb_data);
    RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure);

    v8::MaybeLocal<v8::Function> maybe_function =
        v8::Function::New(env->context(), Invoke, cbdata);
    CHECK_MAYBE_EMPTY(env, maybe_function, napi_generic_failure);

    *result = maybe_function.ToLocalChecked();
    return napi_clear_last_error(env);
  }

  static inline napi_status NewTemplate(
      napi_env env,
      napi_callback cb,
      void* cb_data,
      v8::Local<v8::FunctionTemplate>* result,
      v8::Local<v8::Signature> sig = v8::Local<v8::Signature>()) {
    v8::Local<v8::Value> cbdata = v8impl::CallbackBundle::New(env, cb, cb_data);
    RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure);

    *result = v8::FunctionTemplate::New(env->isolate, Invoke, cbdata, sig);
    return napi_clear_last_error(env);
  }

  explicit FunctionCallbackWrapper(
      const v8::FunctionCallbackInfo<v8::Value>& cbinfo)
      : CallbackWrapperBase(cbinfo, cbinfo.Length()) {}

  napi_value GetNewTarget() override {
    if (_cbinfo.IsConstructCall()) {
      return v8impl::JsValueFromV8LocalValue(_cbinfo.NewTarget());
    } else {
      return nullptr;
    }
  }

  /*virtual*/
  void Args(napi_value* buffer, size_t buffer_length) override {
    size_t i = 0;
    size_t min = std::min(buffer_length, _args_length);

    for (; i < min; i += 1) {
      buffer[i] = v8impl::JsValueFromV8LocalValue(_cbinfo[i]);
    }

    if (i < buffer_length) {
      napi_value undefined =
          v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate()));
      for (; i < buffer_length; i += 1) {
        buffer[i] = undefined;
      }
    }
  }

  /*virtual*/
  void SetReturnValue(napi_value value) override {
    v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
    _cbinfo.GetReturnValue().Set(val);
  }
};

inline napi_status Wrap(napi_env env,
                        napi_value js_object,
                        void* native_object,
                        napi_finalize finalize_cb,
                        void* finalize_hint,
                        napi_ref* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, js_object);

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
  RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
  v8::Local<v8::Object> obj = value.As<v8::Object>();

  // If we've already wrapped this object, we error out.
  RETURN_STATUS_IF_FALSE(
      env,
      !obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).FromJust(),
      napi_invalid_arg);

  v8impl::Reference* reference = nullptr;
  if (result != nullptr) {
    // The returned reference should be deleted via napi_delete_reference()
    // ONLY in response to the finalize callback invocation. (If it is deleted
    // before then, then the finalize callback will never be invoked.)
    // Therefore a finalize callback is required when returning a reference.
    CHECK_ARG(env, finalize_cb);
    reference = v8impl::Reference::New(env,
                                       obj,
                                       0,
                                       v8impl::Ownership::kUserland,
                                       finalize_cb,
                                       native_object,
                                       finalize_hint);
    *result = reinterpret_cast<napi_ref>(reference);
  } else {
    // Create a self-deleting reference.
    reference = v8impl::Reference::New(
        env,
        obj,
        0,
        v8impl::Ownership::kRuntime,
        finalize_cb,
        native_object,
        finalize_cb == nullptr ? nullptr : finalize_hint);
  }

  CHECK(obj->SetPrivate(context,
                        NAPI_PRIVATE_KEY(context, wrapper),
                        v8::External::New(env->isolate, reference))
            .FromJust());

  return GET_RETURN_STATUS(env);
}

// In JavaScript, weak references can be created for object types (Object,
// Function, and external Object) and for local symbols that are created with
// the `Symbol` function call. Global symbols created with the `Symbol.for`
// method cannot be weak references because they are never collected.
//
// Currently, V8 has no API to detect if a symbol is local or global.
// Until we have a V8 API for it, we consider that all symbols can be weak.
// This matches the current Node-API behavior.
inline bool CanBeHeldWeakly(v8::Local<v8::Value> value) {
  return value->IsObject() || value->IsSymbol();
}

}  // end of anonymous namespace

void Finalizer::ResetFinalizer() {
  finalize_callback_ = nullptr;
  finalize_data_ = nullptr;
  finalize_hint_ = nullptr;
}

TrackedFinalizer::TrackedFinalizer(napi_env env,
                                   napi_finalize finalize_callback,
                                   void* finalize_data,
                                   void* finalize_hint)
    : Finalizer(env, finalize_callback, finalize_data, finalize_hint),
      RefTracker() {
  Link(finalize_callback == nullptr ? &env->reflist : &env->finalizing_reflist);
}

TrackedFinalizer* TrackedFinalizer::New(napi_env env,
                                        napi_finalize finalize_callback,
                                        void* finalize_data,
                                        void* finalize_hint) {
  return new TrackedFinalizer(
      env, finalize_callback, finalize_data, finalize_hint);
}

// When a TrackedFinalizer is being deleted, it may have been queued to call its
// finalizer.
TrackedFinalizer::~TrackedFinalizer() {
  // Remove from the env's tracked list.
  Unlink();
  // Try to remove the finalizer from the scheduled second pass callback.
  env_->DequeueFinalizer(this);
}

void TrackedFinalizer::Finalize() {
  FinalizeCore(/*deleteMe:*/ true);
}

void TrackedFinalizer::FinalizeCore(bool deleteMe) {
  // Swap out the field finalize_callback so that it can not be accidentally
  // called more than once.
  napi_finalize finalize_callback = finalize_callback_;
  void* finalize_data = finalize_data_;
  void* finalize_hint = finalize_hint_;
  ResetFinalizer();

  // Either the RefBase is going to be deleted in the finalize_callback or not,
  // it should be removed from the tracked list.
  Unlink();
  // If the finalize_callback is present, it should either delete the
  // derived RefBase, or the RefBase ownership was set to Ownership::kRuntime
  // and the deleteMe parameter is true.
  if (finalize_callback != nullptr) {
    env_->CallFinalizer(finalize_callback, finalize_data, finalize_hint);
  }

  if (deleteMe) {
    delete this;
  }
}

// Wrapper around v8impl::Persistent that implements reference counting.
RefBase::RefBase(napi_env env,
                 uint32_t initial_refcount,
                 Ownership ownership,
                 napi_finalize finalize_callback,
                 void* finalize_data,
                 void* finalize_hint)
    : TrackedFinalizer(env, finalize_callback, finalize_data, finalize_hint),
      refcount_(initial_refcount),
      ownership_(ownership) {}

RefBase* RefBase::New(napi_env env,
                      uint32_t initial_refcount,
                      Ownership ownership,
                      napi_finalize finalize_callback,
                      void* finalize_data,
                      void* finalize_hint) {
  return new RefBase(env,
                     initial_refcount,
                     ownership,
                     finalize_callback,
                     finalize_data,
                     finalize_hint);
}

void* RefBase::Data() {
  return finalize_data_;
}

uint32_t RefBase::Ref() {
  return ++refcount_;
}

uint32_t RefBase::Unref() {
  if (refcount_ == 0) {
    return 0;
  }
  return --refcount_;
}

uint32_t RefBase::RefCount() {
  return refcount_;
}

void RefBase::Finalize() {
  // If the RefBase is not Ownership::kRuntime, userland code should delete it.
  // Delete it if it is Ownership::kRuntime.
  FinalizeCore(/*deleteMe:*/ ownership_ == Ownership::kRuntime);
}

template <typename... Args>
Reference::Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args)
    : RefBase(env, std::forward<Args>(args)...),
      persistent_(env->isolate, value),
      can_be_weak_(CanBeHeldWeakly(value)) {
  if (RefCount() == 0) {
    SetWeak();
  }
}

Reference::~Reference() {
  // Reset the handle. And no weak callback will be invoked.
  persistent_.Reset();
}

Reference* Reference::New(napi_env env,
                          v8::Local<v8::Value> value,
                          uint32_t initial_refcount,
                          Ownership ownership,
                          napi_finalize finalize_callback,
                          void* finalize_data,
                          void* finalize_hint) {
  return new Reference(env,
                       value,
                       initial_refcount,
                       ownership,
                       finalize_callback,
                       finalize_data,
                       finalize_hint);
}

uint32_t Reference::Ref() {
  // When the persistent_ is cleared in the WeakCallback, and a second pass
  // callback is pending, return 0 unconditionally.
  if (persistent_.IsEmpty()) {
    return 0;
  }
  uint32_t refcount = RefBase::Ref();
  if (refcount == 1 && can_be_weak_) {
    persistent_.ClearWeak();
  }
  return refcount;
}

uint32_t Reference::Unref() {
  // When the persistent_ is cleared in the WeakCallback, and a second pass
  // callback is pending, return 0 unconditionally.
  if (persistent_.IsEmpty()) {
    return 0;
  }
  uint32_t old_refcount = RefCount();
  uint32_t refcount = RefBase::Unref();
  if (old_refcount == 1 && refcount == 0) {
    SetWeak();
  }
  return refcount;
}

v8::Local<v8::Value> Reference::Get() {
  if (persistent_.IsEmpty()) {
    return v8::Local<v8::Value>();
  } else {
    return v8::Local<v8::Value>::New(env_->isolate, persistent_);
  }
}

void Reference::Finalize() {
  // Unconditionally reset the persistent handle so that no weak callback will
  // be invoked again.
  persistent_.Reset();

  // Chain up to perform the rest of the finalization.
  RefBase::Finalize();
}

// Mark the reference as weak and eligible for collection
// by the gc.
void Reference::SetWeak() {
  if (can_be_weak_) {
    persistent_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
  } else {
    persistent_.Reset();
  }
}

// The N-API finalizer callback may make calls into the engine. V8's heap is
// not in a consistent state during the weak callback, and therefore it does
// not support calls back into it. Enqueue the invocation of the finalizer.
void Reference::WeakCallback(const v8::WeakCallbackInfo<Reference>& data) {
  Reference* reference = data.GetParameter();
  // The reference must be reset during the weak callback as the API protocol.
  reference->persistent_.Reset();
  reference->env_->InvokeFinalizerFromGC(reference);
}

/**
 * A wrapper for `v8::External` to support type-tagging. `v8::External` doesn't
 * support defining any properties and private properties on it, even though it
 * is an object. This wrapper is used to store the type tag and the data of the
 * external value.
 */
class ExternalWrapper {
 private:
  explicit ExternalWrapper(void* data) : data_(data) {}

  static void WeakCallback(const v8::WeakCallbackInfo<ExternalWrapper>& data) {
    ExternalWrapper* wrapper = data.GetParameter();
    delete wrapper;
  }

 public:
  static v8::Local<v8::External> New(napi_env env, void* data) {
    ExternalWrapper* wrapper = new ExternalWrapper(data);
    v8::Local<v8::External> external = v8::External::New(env->isolate, wrapper);
    wrapper->persistent_.Reset(env->isolate, external);
    wrapper->persistent_.SetWeak(
        wrapper, WeakCallback, v8::WeakCallbackType::kParameter);

    return external;
  }

  static ExternalWrapper* From(v8::Local<v8::External> external) {
    return static_cast<ExternalWrapper*>(external->Value());
  }

  void* Data() { return data_; }

  bool TypeTag(const napi_type_tag* type_tag) {
    if (type_tag_ != nullptr) {
      return false;
    }
    type_tag_ = type_tag;
    return true;
  }

  bool CheckTypeTag(const napi_type_tag* type_tag) {
    return type_tag == type_tag_ ||
           (type_tag_ && type_tag->lower == type_tag_->lower &&
            type_tag->upper == type_tag_->upper);
  }

 private:
  v8impl::Persistent<v8::Value> persistent_;
  void* data_;
  const napi_type_tag* type_tag_ = nullptr;
};

}  // end of namespace v8impl

// Warning: Keep in-sync with napi_status enum
static const char* error_messages[] = {
    nullptr,
    "Invalid argument",
    "An object was expected",
    "A string was expected",
    "A string or symbol was expected",
    "A function was expected",
    "A number was expected",
    "A boolean was expected",
    "An array was expected",
    "Unknown failure",
    "An exception is pending",
    "The async work item was cancelled",
    "napi_escape_handle already called on scope",
    "Invalid handle scope usage",
    "Invalid callback scope usage",
    "Thread-safe function queue is full",
    "Thread-safe function handle is closing",
    "A bigint was expected",
    "A date was expected",
    "An arraybuffer was expected",
    "A detachable arraybuffer was expected",
    "Main thread would deadlock",
    "External buffers are not allowed",
    "Cannot run JavaScript",
};

napi_status NAPI_CDECL napi_get_last_error_info(
    node_api_nogc_env nogc_env, const napi_extended_error_info** result) {
  napi_env env = const_cast<napi_env>(nogc_env);
  CHECK_ENV(env);
  CHECK_ARG(env, result);

  // The value of the constant below must be updated to reference the last
  // message in the `napi_status` enum each time a new error message is added.
  // We don't have a napi_status_last as this would result in an ABI
  // change each time a message was added.
  const int last_status = napi_cannot_run_js;

  static_assert(NAPI_ARRAYSIZE(error_messages) == last_status + 1,
                "Count of error messages must match count of error values");
  CHECK_LE(env->last_error.error_code, last_status);
  // Wait until someone requests the last error information to fetch the error
  // message string
  env->last_error.error_message = error_messages[env->last_error.error_code];

  if (env->last_error.error_code == napi_ok) {
    napi_clear_last_error(env);
  }
  *result = &(env->last_error);
  return napi_ok;
}

napi_status NAPI_CDECL napi_create_function(napi_env env,
                                            const char* utf8name,
                                            size_t length,
                                            napi_callback cb,
                                            void* callback_data,
                                            napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);
  CHECK_ARG(env, cb);

  v8::Local<v8::Function> return_value;
  v8::EscapableHandleScope scope(env->isolate);
  v8::Local<v8::Function> fn;
  STATUS_CALL(v8impl::FunctionCallbackWrapper::NewFunction(
      env, cb, callback_data, &fn));
  return_value = scope.Escape(fn);

  if (utf8name != nullptr) {
    v8::Local<v8::String> name_string;
    CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length);
    return_value->SetName(name_string);
  }

  *result = v8impl::JsValueFromV8LocalValue(return_value);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL
napi_define_class(napi_env env,
                  const char* utf8name,
                  size_t length,
                  napi_callback constructor,
                  void* callback_data,
                  size_t property_count,
                  const napi_property_descriptor* properties,
                  napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);
  CHECK_ARG(env, constructor);

  if (property_count > 0) {
    CHECK_ARG(env, properties);
  }

  v8::Isolate* isolate = env->isolate;

  v8::EscapableHandleScope scope(isolate);
  v8::Local<v8::FunctionTemplate> tpl;
  STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate(
      env, constructor, callback_data, &tpl));

  v8::Local<v8::String> name_string;
  CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length);
  tpl->SetClassName(name_string);

  size_t static_property_count = 0;
  for (size_t i = 0; i < property_count; i++) {
    const napi_property_descriptor* p = properties + i;

    if ((p->attributes & napi_static) != 0) {
      // Static properties are handled separately below.
      static_property_count++;
      continue;
    }

    v8::Local<v8::Name> property_name;
    STATUS_CALL(v8impl::V8NameFromPropertyDescriptor(env, p, &property_name));

    v8::PropertyAttribute attributes =
        v8impl::V8PropertyAttributesFromDescriptor(p);

    // This code is similar to that in napi_define_properties(); the
    // difference is it applies to a template instead of an object,
    // and preferred PropertyAttribute for lack of PropertyDescriptor
    // support on ObjectTemplate.
    if (p->getter != nullptr || p->setter != nullptr) {
      v8::Local<v8::FunctionTemplate> getter_tpl;
      v8::Local<v8::FunctionTemplate> setter_tpl;
      if (p->getter != nullptr) {
        STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate(
            env, p->getter, p->data, &getter_tpl));
      }
      if (p->setter != nullptr) {
        STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate(
            env, p->setter, p->data, &setter_tpl));
      }

      tpl->PrototypeTemplate()->SetAccessorProperty(
          property_name, getter_tpl, setter_tpl, attributes);
    } else if (p->method != nullptr) {
      v8::Local<v8::FunctionTemplate> t;
      STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate(
          env, p->method, p->data, &t, v8::Signature::New(isolate, tpl)));

      tpl->PrototypeTemplate()->Set(property_name, t, attributes);
    } else {
      v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(p->value);
      tpl->PrototypeTemplate()->Set(property_name, value, attributes);
    }
  }

  v8::Local<v8::Context> context = env->context();
  *result = v8impl::JsValueFromV8LocalValue(
      scope.Escape(tpl->GetFunction(context).ToLocalChecked()));

  if (static_property_count > 0) {
    std::vector<napi_property_descriptor> static_descriptors;
    static_descriptors.reserve(static_property_count);

    for (size_t i = 0; i < property_count; i++) {
      const napi_property_descriptor* p = properties + i;
      if ((p->attributes & napi_static) != 0) {
        static_descriptors.push_back(*p);
      }
    }

    STATUS_CALL(napi_define_properties(
        env, *result, static_descriptors.size(), static_descriptors.data()));
  }

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_property_names(napi_env env,
                                               napi_value object,
                                               napi_value* result) {
  return napi_get_all_property_names(
      env,
      object,
      napi_key_include_prototypes,
      static_cast<napi_key_filter>(napi_key_enumerable | napi_key_skip_symbols),
      napi_key_numbers_to_strings,
      result);
}

napi_status NAPI_CDECL
napi_get_all_property_names(napi_env env,
                            napi_value object,
                            napi_key_collection_mode key_mode,
                            napi_key_filter key_filter,
                            napi_key_conversion key_conversion,
                            napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;
  CHECK_TO_OBJECT(env, context, obj, object);

  v8::PropertyFilter filter = v8::PropertyFilter::ALL_PROPERTIES;
  if (key_filter & napi_key_writable) {
    filter = static_cast<v8::PropertyFilter>(filter |
                                             v8::PropertyFilter::ONLY_WRITABLE);
  }
  if (key_filter & napi_key_enumerable) {
    filter = static_cast<v8::PropertyFilter>(
        filter | v8::PropertyFilter::ONLY_ENUMERABLE);
  }
  if (key_filter & napi_key_configurable) {
    filter = static_cast<v8::PropertyFilter>(
        filter | v8::PropertyFilter::ONLY_CONFIGURABLE);
  }
  if (key_filter & napi_key_skip_strings) {
    filter = static_cast<v8::PropertyFilter>(filter |
                                             v8::PropertyFilter::SKIP_STRINGS);
  }
  if (key_filter & napi_key_skip_symbols) {
    filter = static_cast<v8::PropertyFilter>(filter |
                                             v8::PropertyFilter::SKIP_SYMBOLS);
  }
  v8::KeyCollectionMode collection_mode;
  v8::KeyConversionMode conversion_mode;

  switch (key_mode) {
    case napi_key_include_prototypes:
      collection_mode = v8::KeyCollectionMode::kIncludePrototypes;
      break;
    case napi_key_own_only:
      collection_mode = v8::KeyCollectionMode::kOwnOnly;
      break;
    default:
      return napi_set_last_error(env, napi_invalid_arg);
  }

  switch (key_conversion) {
    case napi_key_keep_numbers:
      conversion_mode = v8::KeyConversionMode::kKeepNumbers;
      break;
    case napi_key_numbers_to_strings:
      conversion_mode = v8::KeyConversionMode::kConvertToString;
      break;
    default:
      return napi_set_last_error(env, napi_invalid_arg);
  }

  v8::MaybeLocal<v8::Array> maybe_all_propertynames =
      obj->GetPropertyNames(context,
                            collection_mode,
                            filter,
                            v8::IndexFilter::kIncludeIndices,
                            conversion_mode);

  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(
      env, maybe_all_propertynames, napi_generic_failure);

  *result =
      v8impl::JsValueFromV8LocalValue(maybe_all_propertynames.ToLocalChecked());
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_set_property(napi_env env,
                                         napi_value object,
                                         napi_value key,
                                         napi_value value) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, key);
  CHECK_ARG(env, value);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  v8::Maybe<bool> set_maybe = obj->Set(context, k, val);

  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, set_maybe.FromMaybe(false), napi_generic_failure);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_has_property(napi_env env,
                                         napi_value object,
                                         napi_value key,
                                         bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);
  CHECK_ARG(env, key);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
  v8::Maybe<bool> has_maybe = obj->Has(context, k);

  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, has_maybe, napi_generic_failure);

  *result = has_maybe.FromMaybe(false);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_property(napi_env env,
                                         napi_value object,
                                         napi_value key,
                                         napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, key);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  auto get_maybe = obj->Get(context, k);

  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, get_maybe, napi_generic_failure);

  v8::Local<v8::Value> val = get_maybe.ToLocalChecked();
  *result = v8impl::JsValueFromV8LocalValue(val);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_delete_property(napi_env env,
                                            napi_value object,
                                            napi_value key,
                                            bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, key);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);
  v8::Maybe<bool> delete_maybe = obj->Delete(context, k);
  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, delete_maybe, napi_generic_failure);

  if (result != nullptr) *result = delete_maybe.FromMaybe(false);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_has_own_property(napi_env env,
                                             napi_value object,
                                             napi_value key,
                                             bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, key);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);
  v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
  RETURN_STATUS_IF_FALSE(env, k->IsName(), napi_name_expected);
  v8::Maybe<bool> has_maybe = obj->HasOwnProperty(context, k.As<v8::Name>());
  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, has_maybe, napi_generic_failure);
  *result = has_maybe.FromMaybe(false);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_set_named_property(napi_env env,
                                               napi_value object,
                                               const char* utf8name,
                                               napi_value value) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, value);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Local<v8::Name> key;
  CHECK_NEW_FROM_UTF8(env, key, utf8name);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  v8::Maybe<bool> set_maybe = obj->Set(context, key, val);

  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, set_maybe.FromMaybe(false), napi_generic_failure);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_has_named_property(napi_env env,
                                               napi_value object,
                                               const char* utf8name,
                                               bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Local<v8::Name> key;
  CHECK_NEW_FROM_UTF8(env, key, utf8name);

  v8::Maybe<bool> has_maybe = obj->Has(context, key);

  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, has_maybe, napi_generic_failure);

  *result = has_maybe.FromMaybe(false);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_named_property(napi_env env,
                                               napi_value object,
                                               const char* utf8name,
                                               napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Name> key;
  CHECK_NEW_FROM_UTF8(env, key, utf8name);

  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  auto get_maybe = obj->Get(context, key);

  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, get_maybe, napi_generic_failure);

  v8::Local<v8::Value> val = get_maybe.ToLocalChecked();
  *result = v8impl::JsValueFromV8LocalValue(val);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_set_element(napi_env env,
                                        napi_value object,
                                        uint32_t index,
                                        napi_value value) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, value);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  auto set_maybe = obj->Set(context, index, val);

  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, set_maybe.FromMaybe(false), napi_generic_failure);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_has_element(napi_env env,
                                        napi_value object,
                                        uint32_t index,
                                        bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Maybe<bool> has_maybe = obj->Has(context, index);

  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, has_maybe, napi_generic_failure);

  *result = has_maybe.FromMaybe(false);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_element(napi_env env,
                                        napi_value object,
                                        uint32_t index,
                                        napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  auto get_maybe = obj->Get(context, index);

  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, get_maybe, napi_generic_failure);

  *result = v8impl::JsValueFromV8LocalValue(get_maybe.ToLocalChecked());
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_delete_element(napi_env env,
                                           napi_value object,
                                           uint32_t index,
                                           bool* result) {
  NAPI_PREAMBLE(env);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);
  v8::Maybe<bool> delete_maybe = obj->Delete(context, index);
  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, delete_maybe, napi_generic_failure);

  if (result != nullptr) *result = delete_maybe.FromMaybe(false);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL
napi_define_properties(napi_env env,
                       napi_value object,
                       size_t property_count,
                       const napi_property_descriptor* properties) {
  NAPI_PREAMBLE(env);
  if (property_count > 0) {
    CHECK_ARG(env, properties);
  }

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Object> obj;
  CHECK_TO_OBJECT(env, context, obj, object);

  for (size_t i = 0; i < property_count; i++) {
    const napi_property_descriptor* p = &properties[i];

    v8::Local<v8::Name> property_name;
    STATUS_CALL(v8impl::V8NameFromPropertyDescriptor(env, p, &property_name));

    if (p->getter != nullptr || p->setter != nullptr) {
      v8::Local<v8::Function> local_getter;
      v8::Local<v8::Function> local_setter;

      if (p->getter != nullptr) {
        STATUS_CALL(v8impl::FunctionCallbackWrapper::NewFunction(
            env, p->getter, p->data, &local_getter));
      }
      if (p->setter != nullptr) {
        STATUS_CALL(v8impl::FunctionCallbackWrapper::NewFunction(
            env, p->setter, p->data, &local_setter));
      }

      v8::PropertyDescriptor descriptor(local_getter, local_setter);
      descriptor.set_enumerable((p->attributes & napi_enumerable) != 0);
      descriptor.set_configurable((p->attributes & napi_configurable) != 0);

      auto define_maybe =
          obj->DefineProperty(context, property_name, descriptor);

      RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
          env, define_maybe.FromMaybe(false), napi_invalid_arg);
    } else if (p->method != nullptr) {
      v8::Local<v8::Function> method;
      STATUS_CALL(v8impl::FunctionCallbackWrapper::NewFunction(
          env, p->method, p->data, &method));
      v8::PropertyDescriptor descriptor(method,
                                        (p->attributes & napi_writable) != 0);
      descriptor.set_enumerable((p->attributes & napi_enumerable) != 0);
      descriptor.set_configurable((p->attributes & napi_configurable) != 0);

      auto define_maybe =
          obj->DefineProperty(context, property_name, descriptor);

      RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
          env, define_maybe.FromMaybe(false), napi_generic_failure);
    } else {
      v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(p->value);
      v8::Maybe<bool> define_maybe = v8::Just(false);

      if ((p->attributes & napi_enumerable) &&
          (p->attributes & napi_writable) &&
          (p->attributes & napi_configurable)) {
        // Use a fast path for this type of data property.
        define_maybe = obj->CreateDataProperty(context, property_name, value);
      } else {
        v8::PropertyDescriptor descriptor(value,
                                          (p->attributes & napi_writable) != 0);
        descriptor.set_enumerable((p->attributes & napi_enumerable) != 0);
        descriptor.set_configurable((p->attributes & napi_configurable) != 0);

        define_maybe = obj->DefineProperty(context, property_name, descriptor);
      }

      RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
          env, define_maybe.FromMaybe(false), napi_invalid_arg);
    }
  }

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_object_freeze(napi_env env, napi_value object) {
  NAPI_PREAMBLE(env);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Maybe<bool> set_frozen =
      obj->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen);

  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, set_frozen.FromMaybe(false), napi_generic_failure);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_object_seal(napi_env env, napi_value object) {
  NAPI_PREAMBLE(env);

  v8::Local<v8::Context> context = env->context();
  v8::Local<v8::Object> obj;

  CHECK_TO_OBJECT(env, context, obj, object);

  v8::Maybe<bool> set_sealed =
      obj->SetIntegrityLevel(context, v8::IntegrityLevel::kSealed);

  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, set_sealed.FromMaybe(false), napi_generic_failure);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_is_array(napi_env env,
                                     napi_value value,
                                     bool* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  *result = val->IsArray();
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_array_length(napi_env env,
                                             napi_value value,
                                             uint32_t* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsArray(), napi_array_expected);

  v8::Local<v8::Array> arr = val.As<v8::Array>();
  *result = arr->Length();

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_strict_equals(napi_env env,
                                          napi_value lhs,
                                          napi_value rhs,
                                          bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, lhs);
  CHECK_ARG(env, rhs);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> a = v8impl::V8LocalValueFromJsValue(lhs);
  v8::Local<v8::Value> b = v8impl::V8LocalValueFromJsValue(rhs);

  *result = a->StrictEquals(b);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_prototype(napi_env env,
                                          napi_value object,
                                          napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Object> obj;
  CHECK_TO_OBJECT(env, context, obj, object);

  // This doesn't invokes Proxy's [[GetPrototypeOf]] handler.
  v8::Local<v8::Value> val = obj->GetPrototype();
  *result = v8impl::JsValueFromV8LocalValue(val);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(v8::Object::New(env->isolate));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_array(napi_env env, napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(v8::Array::New(env->isolate));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_array_with_length(napi_env env,
                                                     size_t length,
                                                     napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result =
      v8impl::JsValueFromV8LocalValue(v8::Array::New(env->isolate, length));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_string_latin1(napi_env env,
                                                 const char* str,
                                                 size_t length,
                                                 napi_value* result) {
  return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
    return v8::String::NewFromOneByte(isolate,
                                      reinterpret_cast<const uint8_t*>(str),
                                      v8::NewStringType::kNormal,
                                      length);
  });
}

napi_status NAPI_CDECL napi_create_string_utf8(napi_env env,
                                               const char* str,
                                               size_t length,
                                               napi_value* result) {
  return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
    return v8::String::NewFromUtf8(
        isolate, str, v8::NewStringType::kNormal, static_cast<int>(length));
  });
}

napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
                                                const char16_t* str,
                                                size_t length,
                                                napi_value* result) {
  return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
    return v8::String::NewFromTwoByte(isolate,
                                      reinterpret_cast<const uint16_t*>(str),
                                      v8::NewStringType::kNormal,
                                      length);
  });
}

napi_status NAPI_CDECL node_api_create_external_string_latin1(
    napi_env env,
    char* str,
    size_t length,
    node_api_nogc_finalize nogc_finalize_callback,
    void* finalize_hint,
    napi_value* result,
    bool* copied) {
  napi_finalize finalize_callback =
      reinterpret_cast<napi_finalize>(nogc_finalize_callback);
  return v8impl::NewExternalString(
      env,
      str,
      length,
      finalize_callback,
      finalize_hint,
      result,
      copied,
      napi_create_string_latin1,
      [&](v8::Isolate* isolate) {
        if (length == NAPI_AUTO_LENGTH) {
          length = (std::string_view(str)).length();
        }
        auto resource = new v8impl::ExternalOneByteStringResource(
            env, str, length, finalize_callback, finalize_hint);
        return v8::String::NewExternalOneByte(isolate, resource);
      });
}

napi_status NAPI_CDECL node_api_create_external_string_utf16(
    napi_env env,
    char16_t* str,
    size_t length,
    node_api_nogc_finalize nogc_finalize_callback,
    void* finalize_hint,
    napi_value* result,
    bool* copied) {
  napi_finalize finalize_callback =
      reinterpret_cast<napi_finalize>(nogc_finalize_callback);
  return v8impl::NewExternalString(
      env,
      str,
      length,
      finalize_callback,
      finalize_hint,
      result,
      copied,
      napi_create_string_utf16,
      [&](v8::Isolate* isolate) {
        if (length == NAPI_AUTO_LENGTH) {
          length = (std::u16string_view(str)).length();
        }
        auto resource = new v8impl::ExternalStringResource(
            env, str, length, finalize_callback, finalize_hint);
        return v8::String::NewExternalTwoByte(isolate, resource);
      });
}

napi_status NAPI_CDECL node_api_create_property_key_utf16(napi_env env,
                                                          const char16_t* str,
                                                          size_t length,
                                                          napi_value* result) {
  return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
    return v8::String::NewFromTwoByte(isolate,
                                      reinterpret_cast<const uint16_t*>(str),
                                      v8::NewStringType::kInternalized,
                                      static_cast<int>(length));
  });
}

napi_status NAPI_CDECL napi_create_double(napi_env env,
                                          double value,
                                          napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result =
      v8impl::JsValueFromV8LocalValue(v8::Number::New(env->isolate, value));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_int32(napi_env env,
                                         int32_t value,
                                         napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result =
      v8impl::JsValueFromV8LocalValue(v8::Integer::New(env->isolate, value));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_uint32(napi_env env,
                                          uint32_t value,
                                          napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(
      v8::Integer::NewFromUnsigned(env->isolate, value));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_int64(napi_env env,
                                         int64_t value,
                                         napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(
      v8::Number::New(env->isolate, static_cast<double>(value)));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env,
                                                int64_t value,
                                                napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result =
      v8impl::JsValueFromV8LocalValue(v8::BigInt::New(env->isolate, value));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_bigint_uint64(napi_env env,
                                                 uint64_t value,
                                                 napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(
      v8::BigInt::NewFromUnsigned(env->isolate, value));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_bigint_words(napi_env env,
                                                int sign_bit,
                                                size_t word_count,
                                                const uint64_t* words,
                                                napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, words);
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();

  RETURN_STATUS_IF_FALSE(env, word_count <= INT_MAX, napi_invalid_arg);

  v8::MaybeLocal<v8::BigInt> b =
      v8::BigInt::NewFromWords(context, sign_bit, word_count, words);

  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, b, napi_generic_failure);

  *result = v8impl::JsValueFromV8LocalValue(b.ToLocalChecked());
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_boolean(napi_env env,
                                        bool value,
                                        napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  v8::Isolate* isolate = env->isolate;

  if (value) {
    *result = v8impl::JsValueFromV8LocalValue(v8::True(isolate));
  } else {
    *result = v8impl::JsValueFromV8LocalValue(v8::False(isolate));
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_symbol(napi_env env,
                                          napi_value description,
                                          napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  v8::Isolate* isolate = env->isolate;

  if (description == nullptr) {
    *result = v8impl::JsValueFromV8LocalValue(v8::Symbol::New(isolate));
  } else {
    v8::Local<v8::Value> desc = v8impl::V8LocalValueFromJsValue(description);
    RETURN_STATUS_IF_FALSE(env, desc->IsString(), napi_string_expected);

    *result = v8impl::JsValueFromV8LocalValue(
        v8::Symbol::New(isolate, desc.As<v8::String>()));
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL node_api_symbol_for(napi_env env,
                                           const char* utf8description,
                                           size_t length,
                                           napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  napi_value js_description_string;
  STATUS_CALL(napi_create_string_utf8(
      env, utf8description, length, &js_description_string));
  v8::Local<v8::String> description_string =
      v8impl::V8LocalValueFromJsValue(js_description_string).As<v8::String>();

  *result = v8impl::JsValueFromV8LocalValue(
      v8::Symbol::For(env->isolate, description_string));

  return napi_clear_last_error(env);
}

static inline napi_status set_error_code(napi_env env,
                                         v8::Local<v8::Value> error,
                                         napi_value code,
                                         const char* code_cstring) {
  if ((code != nullptr) || (code_cstring != nullptr)) {
    v8::Local<v8::Context> context = env->context();
    v8::Local<v8::Object> err_object = error.As<v8::Object>();

    v8::Local<v8::Value> code_value = v8impl::V8LocalValueFromJsValue(code);
    if (code != nullptr) {
      code_value = v8impl::V8LocalValueFromJsValue(code);
      RETURN_STATUS_IF_FALSE(env, code_value->IsString(), napi_string_expected);
    } else {
      CHECK_NEW_FROM_UTF8(env, code_value, code_cstring);
    }

    v8::Local<v8::Name> code_key;
    CHECK_NEW_FROM_UTF8(env, code_key, "code");

    v8::Maybe<bool> set_maybe = err_object->Set(context, code_key, code_value);
    RETURN_STATUS_IF_FALSE(
        env, set_maybe.FromMaybe(false), napi_generic_failure);
  }
  return napi_ok;
}

napi_status NAPI_CDECL napi_create_error(napi_env env,
                                         napi_value code,
                                         napi_value msg,
                                         napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, msg);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg);
  RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected);

  v8::Local<v8::Value> error_obj =
      v8::Exception::Error(message_value.As<v8::String>());
  STATUS_CALL(set_error_code(env, error_obj, code, nullptr));

  *result = v8impl::JsValueFromV8LocalValue(error_obj);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_type_error(napi_env env,
                                              napi_value code,
                                              napi_value msg,
                                              napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, msg);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg);
  RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected);

  v8::Local<v8::Value> error_obj =
      v8::Exception::TypeError(message_value.As<v8::String>());
  STATUS_CALL(set_error_code(env, error_obj, code, nullptr));

  *result = v8impl::JsValueFromV8LocalValue(error_obj);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_range_error(napi_env env,
                                               napi_value code,
                                               napi_value msg,
                                               napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, msg);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg);
  RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected);

  v8::Local<v8::Value> error_obj =
      v8::Exception::RangeError(message_value.As<v8::String>());
  STATUS_CALL(set_error_code(env, error_obj, code, nullptr));

  *result = v8impl::JsValueFromV8LocalValue(error_obj);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL node_api_create_syntax_error(napi_env env,
                                                    napi_value code,
                                                    napi_value msg,
                                                    napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, msg);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg);
  RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected);

  v8::Local<v8::Value> error_obj =
      v8::Exception::SyntaxError(message_value.As<v8::String>());
  STATUS_CALL(set_error_code(env, error_obj, code, nullptr));

  *result = v8impl::JsValueFromV8LocalValue(error_obj);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_typeof(napi_env env,
                                   napi_value value,
                                   napi_valuetype* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> v = v8impl::V8LocalValueFromJsValue(value);

  if (v->IsNumber()) {
    *result = napi_number;
  } else if (v->IsBigInt()) {
    *result = napi_bigint;
  } else if (v->IsString()) {
    *result = napi_string;
  } else if (v->IsFunction()) {
    // This test has to come before IsObject because IsFunction
    // implies IsObject
    *result = napi_function;
  } else if (v->IsExternal()) {
    // This test has to come before IsObject because IsExternal
    // implies IsObject
    *result = napi_external;
  } else if (v->IsObject()) {
    *result = napi_object;
  } else if (v->IsBoolean()) {
    *result = napi_boolean;
  } else if (v->IsUndefined()) {
    *result = napi_undefined;
  } else if (v->IsSymbol()) {
    *result = napi_symbol;
  } else if (v->IsNull()) {
    *result = napi_null;
  } else {
    // Should not get here unless V8 has added some new kind of value.
    return napi_set_last_error(env, napi_invalid_arg);
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_undefined(napi_env env, napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(v8::Undefined(env->isolate));

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_null(napi_env env, napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(v8::Null(env->isolate));

  return napi_clear_last_error(env);
}

// Gets all callback info in a single call. (Ugly, but faster.)
napi_status NAPI_CDECL napi_get_cb_info(
    napi_env env,               // [in] NAPI environment handle
    napi_callback_info cbinfo,  // [in] Opaque callback-info handle
    size_t* argc,      // [in-out] Specifies the size of the provided argv array
                       // and receives the actual count of args.
    napi_value* argv,  // [out] Array of values
    napi_value* this_arg,  // [out] Receives the JS 'this' arg for the call
    void** data) {         // [out] Receives the data pointer for the callback.
  CHECK_ENV(env);
  CHECK_ARG(env, cbinfo);

  v8impl::CallbackWrapper* info =
      reinterpret_cast<v8impl::CallbackWrapper*>(cbinfo);

  if (argv != nullptr) {
    CHECK_ARG(env, argc);
    info->Args(argv, *argc);
  }
  if (argc != nullptr) {
    *argc = info->ArgsLength();
  }
  if (this_arg != nullptr) {
    *this_arg = info->This();
  }
  if (data != nullptr) {
    *data = info->Data();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_new_target(napi_env env,
                                           napi_callback_info cbinfo,
                                           napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, cbinfo);
  CHECK_ARG(env, result);

  v8impl::CallbackWrapper* info =
      reinterpret_cast<v8impl::CallbackWrapper*>(cbinfo);

  *result = info->GetNewTarget();
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_call_function(napi_env env,
                                          napi_value recv,
                                          napi_value func,
                                          size_t argc,
                                          const napi_value* argv,
                                          napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, recv);
  if (argc > 0) {
    CHECK_ARG(env, argv);
  }

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Value> v8recv = v8impl::V8LocalValueFromJsValue(recv);

  v8::Local<v8::Function> v8func;
  CHECK_TO_FUNCTION(env, v8func, func);

  auto maybe = v8func->Call(
      context,
      v8recv,
      argc,
      reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)));

  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, napi_generic_failure);
  if (result != nullptr) {
    *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked());
  }
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_global(napi_env env, napi_value* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsValueFromV8LocalValue(env->context()->Global());

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, error);

  v8::Isolate* isolate = env->isolate;

  isolate->ThrowException(v8impl::V8LocalValueFromJsValue(error));
  // any VM calls after this point and before returning
  // to the javascript invoker will fail
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_throw_error(napi_env env,
                                        const char* code,
                                        const char* msg) {
  NAPI_PREAMBLE(env);

  v8::Isolate* isolate = env->isolate;
  v8::Local<v8::String> str;
  CHECK_NEW_FROM_UTF8(env, str, msg);

  v8::Local<v8::Value> error_obj = v8::Exception::Error(str);
  STATUS_CALL(set_error_code(env, error_obj, nullptr, code));

  isolate->ThrowException(error_obj);
  // any VM calls after this point and before returning
  // to the javascript invoker will fail
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_throw_type_error(napi_env env,
                                             const char* code,
                                             const char* msg) {
  NAPI_PREAMBLE(env);

  v8::Isolate* isolate = env->isolate;
  v8::Local<v8::String> str;
  CHECK_NEW_FROM_UTF8(env, str, msg);

  v8::Local<v8::Value> error_obj = v8::Exception::TypeError(str);
  STATUS_CALL(set_error_code(env, error_obj, nullptr, code));

  isolate->ThrowException(error_obj);
  // any VM calls after this point and before returning
  // to the javascript invoker will fail
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_throw_range_error(napi_env env,
                                              const char* code,
                                              const char* msg) {
  NAPI_PREAMBLE(env);

  v8::Isolate* isolate = env->isolate;
  v8::Local<v8::String> str;
  CHECK_NEW_FROM_UTF8(env, str, msg);

  v8::Local<v8::Value> error_obj = v8::Exception::RangeError(str);
  STATUS_CALL(set_error_code(env, error_obj, nullptr, code));

  isolate->ThrowException(error_obj);
  // any VM calls after this point and before returning
  // to the javascript invoker will fail
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL node_api_throw_syntax_error(napi_env env,
                                                   const char* code,
                                                   const char* msg) {
  NAPI_PREAMBLE(env);

  v8::Isolate* isolate = env->isolate;
  v8::Local<v8::String> str;
  CHECK_NEW_FROM_UTF8(env, str, msg);

  v8::Local<v8::Value> error_obj = v8::Exception::SyntaxError(str);
  STATUS_CALL(set_error_code(env, error_obj, nullptr, code));

  isolate->ThrowException(error_obj);
  // any VM calls after this point and before returning
  // to the javascript invoker will fail
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_is_error(napi_env env,
                                     napi_value value,
                                     bool* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot
  // throw JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  *result = val->IsNativeError();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_double(napi_env env,
                                             napi_value value,
                                             double* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected);

  *result = val.As<v8::Number>()->Value();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_int32(napi_env env,
                                            napi_value value,
                                            int32_t* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  if (val->IsInt32()) {
    *result = val.As<v8::Int32>()->Value();
  } else {
    RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected);

    // Empty context: https://github.com/nodejs/node/issues/14379
    v8::Local<v8::Context> context;
    *result = val->Int32Value(context).FromJust();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_uint32(napi_env env,
                                             napi_value value,
                                             uint32_t* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  if (val->IsUint32()) {
    *result = val.As<v8::Uint32>()->Value();
  } else {
    RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected);

    // Empty context: https://github.com/nodejs/node/issues/14379
    v8::Local<v8::Context> context;
    *result = val->Uint32Value(context).FromJust();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_int64(napi_env env,
                                            napi_value value,
                                            int64_t* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  // This is still a fast path very likely to be taken.
  if (val->IsInt32()) {
    *result = val.As<v8::Int32>()->Value();
    return napi_clear_last_error(env);
  }

  RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected);

  // v8::Value::IntegerValue() converts NaN, +Inf, and -Inf to INT64_MIN,
  // inconsistent with v8::Value::Int32Value() which converts those values to 0.
  // Special-case all non-finite values to match that behavior.
  double doubleValue = val.As<v8::Number>()->Value();
  if (std::isfinite(doubleValue)) {
    // Empty context: https://github.com/nodejs/node/issues/14379
    v8::Local<v8::Context> context;
    *result = val->IntegerValue(context).FromJust();
  } else {
    *result = 0;
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env,
                                                   napi_value value,
                                                   int64_t* result,
                                                   bool* lossless) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);
  CHECK_ARG(env, lossless);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected);

  *result = val.As<v8::BigInt>()->Int64Value(lossless);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_bigint_uint64(napi_env env,
                                                    napi_value value,
                                                    uint64_t* result,
                                                    bool* lossless) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);
  CHECK_ARG(env, lossless);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected);

  *result = val.As<v8::BigInt>()->Uint64Value(lossless);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_bigint_words(napi_env env,
                                                   napi_value value,
                                                   int* sign_bit,
                                                   size_t* word_count,
                                                   uint64_t* words) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, word_count);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

  RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected);

  v8::Local<v8::BigInt> big = val.As<v8::BigInt>();

  int word_count_int = *word_count;

  if (sign_bit == nullptr && words == nullptr) {
    word_count_int = big->WordCount();
  } else {
    CHECK_ARG(env, sign_bit);
    CHECK_ARG(env, words);
    big->ToWordsArray(sign_bit, &word_count_int, words);
  }

  *word_count = word_count_int;

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_value_bool(napi_env env,
                                           napi_value value,
                                           bool* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsBoolean(), napi_boolean_expected);

  *result = val.As<v8::Boolean>()->Value();

  return napi_clear_last_error(env);
}

// Copies a JavaScript string into a LATIN-1 string buffer. The result is the
// number of bytes (excluding the null terminator) copied into buf.
// A sufficient buffer size should be greater than the length of string,
// reserving space for null terminator.
// If bufsize is insufficient, the string will be truncated and null terminated.
// If buf is NULL, this method returns the length of the string (in bytes)
// via the result parameter.
// The result argument is optional unless buf is NULL.
napi_status NAPI_CDECL napi_get_value_string_latin1(
    napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected);

  if (!buf) {
    CHECK_ARG(env, result);
    *result = val.As<v8::String>()->Length();
  } else if (bufsize != 0) {
    int copied =
        val.As<v8::String>()->WriteOneByte(env->isolate,
                                           reinterpret_cast<uint8_t*>(buf),
                                           0,
                                           bufsize - 1,
                                           v8::String::NO_NULL_TERMINATION);

    buf[copied] = '\0';
    if (result != nullptr) {
      *result = copied;
    }
  } else if (result != nullptr) {
    *result = 0;
  }

  return napi_clear_last_error(env);
}

// Copies a JavaScript string into a UTF-8 string buffer. The result is the
// number of bytes (excluding the null terminator) copied into buf.
// A sufficient buffer size should be greater than the length of string,
// reserving space for null terminator.
// If bufsize is insufficient, the string will be truncated and null terminated.
// If buf is NULL, this method returns the length of the string (in bytes)
// via the result parameter.
// The result argument is optional unless buf is NULL.
napi_status NAPI_CDECL napi_get_value_string_utf8(
    napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected);

  if (!buf) {
    CHECK_ARG(env, result);
    *result = val.As<v8::String>()->Utf8Length(env->isolate);
  } else if (bufsize != 0) {
    int copied = val.As<v8::String>()->WriteUtf8(
        env->isolate,
        buf,
        bufsize - 1,
        nullptr,
        v8::String::REPLACE_INVALID_UTF8 | v8::String::NO_NULL_TERMINATION);

    buf[copied] = '\0';
    if (result != nullptr) {
      *result = copied;
    }
  } else if (result != nullptr) {
    *result = 0;
  }

  return napi_clear_last_error(env);
}

// Copies a JavaScript string into a UTF-16 string buffer. The result is the
// number of 2-byte code units (excluding the null terminator) copied into buf.
// A sufficient buffer size should be greater than the length of string,
// reserving space for null terminator.
// If bufsize is insufficient, the string will be truncated and null terminated.
// If buf is NULL, this method returns the length of the string (in 2-byte
// code units) via the result parameter.
// The result argument is optional unless buf is NULL.
napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env,
                                                   napi_value value,
                                                   char16_t* buf,
                                                   size_t bufsize,
                                                   size_t* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected);

  if (!buf) {
    CHECK_ARG(env, result);
    // V8 assumes UTF-16 length is the same as the number of characters.
    *result = val.As<v8::String>()->Length();
  } else if (bufsize != 0) {
    int copied = val.As<v8::String>()->Write(env->isolate,
                                             reinterpret_cast<uint16_t*>(buf),
                                             0,
                                             bufsize - 1,
                                             v8::String::NO_NULL_TERMINATION);

    buf[copied] = '\0';
    if (result != nullptr) {
      *result = copied;
    }
  } else if (result != nullptr) {
    *result = 0;
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env,
                                           napi_value value,
                                           napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Isolate* isolate = env->isolate;
  v8::Local<v8::Boolean> b =
      v8impl::V8LocalValueFromJsValue(value)->ToBoolean(isolate);
  *result = v8impl::JsValueFromV8LocalValue(b);
  return GET_RETURN_STATUS(env);
}

#define GEN_COERCE_FUNCTION(UpperCaseName, MixedCaseName, LowerCaseName)       \
  napi_status NAPI_CDECL napi_coerce_to_##LowerCaseName(                       \
      napi_env env, napi_value value, napi_value* result) {                    \
    NAPI_PREAMBLE(env);                                                        \
    CHECK_ARG(env, value);                                                     \
    CHECK_ARG(env, result);                                                    \
                                                                               \
    v8::Local<v8::Context> context = env->context();                           \
    v8::Local<v8::MixedCaseName> str;                                          \
                                                                               \
    CHECK_TO_##UpperCaseName(env, context, str, value);                        \
                                                                               \
    *result = v8impl::JsValueFromV8LocalValue(str);                            \
    return GET_RETURN_STATUS(env);                                             \
  }

GEN_COERCE_FUNCTION(NUMBER, Number, number)
GEN_COERCE_FUNCTION(OBJECT, Object, object)
GEN_COERCE_FUNCTION(STRING, String, string)

#undef GEN_COERCE_FUNCTION

napi_status NAPI_CDECL napi_wrap(napi_env env,
                                 napi_value js_object,
                                 void* native_object,
                                 node_api_nogc_finalize nogc_finalize_cb,
                                 void* finalize_hint,
                                 napi_ref* result) {
  napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
  return v8impl::Wrap(
      env, js_object, native_object, finalize_cb, finalize_hint, result);
}

napi_status NAPI_CDECL napi_unwrap(napi_env env,
                                   napi_value obj,
                                   void** result) {
  return v8impl::Unwrap(env, obj, result, v8impl::KeepWrap);
}

napi_status NAPI_CDECL napi_remove_wrap(napi_env env,
                                        napi_value obj,
                                        void** result) {
  return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap);
}

napi_status NAPI_CDECL
napi_create_external(napi_env env,
                     void* data,
                     node_api_nogc_finalize nogc_finalize_cb,
                     void* finalize_hint,
                     napi_value* result) {
  napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Local<v8::External> external_value =
      v8impl::ExternalWrapper::New(env, data);

  if (finalize_cb) {
    // The Reference object will delete itself after invoking the finalizer
    // callback.
    v8impl::Reference::New(env,
                           external_value,
                           0,
                           v8impl::Ownership::kRuntime,
                           finalize_cb,
                           data,
                           finalize_hint);
  }

  *result = v8impl::JsValueFromV8LocalValue(external_value);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_type_tag_object(napi_env env,
                                            napi_value object_or_external,
                                            const napi_type_tag* type_tag) {
  NAPI_PREAMBLE(env);
  v8::Local<v8::Context> context = env->context();

  CHECK_ARG(env, object_or_external);
  v8::Local<v8::Value> val =
      v8impl::V8LocalValueFromJsValue(object_or_external);
  if (val->IsExternal()) {
    v8impl::ExternalWrapper* wrapper =
        v8impl::ExternalWrapper::From(val.As<v8::External>());
    RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
        env, wrapper->TypeTag(type_tag), napi_invalid_arg);
    return GET_RETURN_STATUS(env);
  }

  v8::Local<v8::Object> obj;
  CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, obj, object_or_external);
  CHECK_ARG_WITH_PREAMBLE(env, type_tag);

  auto key = NAPI_PRIVATE_KEY(context, type_tag);
  auto maybe_has = obj->HasPrivate(context, key);
  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe_has, napi_generic_failure);
  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, !maybe_has.FromJust(), napi_invalid_arg);

  auto tag = v8::BigInt::NewFromWords(
      context, 0, 2, reinterpret_cast<const uint64_t*>(type_tag));
  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, tag, napi_generic_failure);

  auto maybe_set = obj->SetPrivate(context, key, tag.ToLocalChecked());
  CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe_set, napi_generic_failure);
  RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(
      env, maybe_set.FromJust(), napi_generic_failure);

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_check_object_type_tag(napi_env env,
                                                  napi_value object_or_external,
                                                  const napi_type_tag* type_tag,
                                                  bool* result) {
  NAPI_PREAMBLE(env);
  v8::Local<v8::Context> context = env->context();

  CHECK_ARG(env, object_or_external);
  v8::Local<v8::Value> obj_val =
      v8impl::V8LocalValueFromJsValue(object_or_external);
  if (obj_val->IsExternal()) {
    v8impl::ExternalWrapper* wrapper =
        v8impl::ExternalWrapper::From(obj_val.As<v8::External>());
    *result = wrapper->CheckTypeTag(type_tag);
    return GET_RETURN_STATUS(env);
  }

  v8::Local<v8::Object> obj;
  CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, obj, object_or_external);
  CHECK_ARG_WITH_PREAMBLE(env, type_tag);
  CHECK_ARG_WITH_PREAMBLE(env, result);

  auto maybe_value =
      obj->GetPrivate(context, NAPI_PRIVATE_KEY(context, type_tag));
  CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe_value, napi_generic_failure);
  v8::Local<v8::Value> val = maybe_value.ToLocalChecked();

  // We consider the type check to have failed unless we reach the line below
  // where we set whether the type check succeeded or not based on the
  // comparison of the two type tags.
  *result = false;
  if (val->IsBigInt()) {
    int sign;
    int size = 2;
    napi_type_tag tag;
    val.As<v8::BigInt>()->ToWordsArray(
        &sign, &size, reinterpret_cast<uint64_t*>(&tag));
    if (sign == 0) {
      if (size == 2) {
        *result =
            (tag.lower == type_tag->lower && tag.upper == type_tag->upper);
      } else if (size == 1) {
        *result = (tag.lower == type_tag->lower && 0 == type_tag->upper);
      } else if (size == 0) {
        *result = (0 == type_tag->lower && 0 == type_tag->upper);
      }
    }
  }

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_value_external(napi_env env,
                                               napi_value value,
                                               void** result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg);

  v8::Local<v8::External> external_value = val.As<v8::External>();
  *result = v8impl::ExternalWrapper::From(external_value)->Data();

  return napi_clear_last_error(env);
}

// Set initial_refcount to 0 for a weak reference, >0 for a strong reference.
napi_status NAPI_CDECL napi_create_reference(napi_env env,
                                             napi_value value,
                                             uint32_t initial_refcount,
                                             napi_ref* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(value);
  if (env->module_api_version != NAPI_VERSION_EXPERIMENTAL) {
    if (!(v8_value->IsObject() || v8_value->IsFunction() ||
          v8_value->IsSymbol())) {
      return napi_set_last_error(env, napi_invalid_arg);
    }
  }

  v8impl::Reference* reference = v8impl::Reference::New(
      env, v8_value, initial_refcount, v8impl::Ownership::kUserland);

  *result = reinterpret_cast<napi_ref>(reference);
  return napi_clear_last_error(env);
}

// Deletes a reference. The referenced value is released, and may be GC'd unless
// there are other references to it.
napi_status NAPI_CDECL napi_delete_reference(napi_env env, napi_ref ref) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, ref);

  delete reinterpret_cast<v8impl::Reference*>(ref);

  return napi_clear_last_error(env);
}

// Increments the reference count, optionally returning the resulting count.
// After this call the reference will be a strong reference because its
// refcount is >0, and the referenced object is effectively "pinned".
// Calling this when the refcount is 0 and the object is unavailable
// results in an error.
napi_status NAPI_CDECL napi_reference_ref(napi_env env,
                                          napi_ref ref,
                                          uint32_t* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, ref);

  v8impl::Reference* reference = reinterpret_cast<v8impl::Reference*>(ref);
  uint32_t count = reference->Ref();

  if (result != nullptr) {
    *result = count;
  }

  return napi_clear_last_error(env);
}

// Decrements the reference count, optionally returning the resulting count. If
// the result is 0 the reference is now weak and the object may be GC'd at any
// time if there are no other references. Calling this when the refcount is
// already 0 results in an error.
napi_status NAPI_CDECL napi_reference_unref(napi_env env,
                                            napi_ref ref,
                                            uint32_t* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, ref);

  v8impl::Reference* reference = reinterpret_cast<v8impl::Reference*>(ref);

  if (reference->RefCount() == 0) {
    return napi_set_last_error(env, napi_generic_failure);
  }

  uint32_t count = reference->Unref();

  if (result != nullptr) {
    *result = count;
  }

  return napi_clear_last_error(env);
}

// Attempts to get a referenced value. If the reference is weak, the value might
// no longer be available, in that case the call is still successful but the
// result is NULL.
napi_status NAPI_CDECL napi_get_reference_value(napi_env env,
                                                napi_ref ref,
                                                napi_value* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, ref);
  CHECK_ARG(env, result);

  v8impl::Reference* reference = reinterpret_cast<v8impl::Reference*>(ref);
  *result = v8impl::JsValueFromV8LocalValue(reference->Get());

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_open_handle_scope(napi_env env,
                                              napi_handle_scope* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsHandleScopeFromV8HandleScope(
      new v8impl::HandleScopeWrapper(env->isolate));
  env->open_handle_scopes++;
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_close_handle_scope(napi_env env,
                                               napi_handle_scope scope) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, scope);
  if (env->open_handle_scopes == 0) {
    return napi_handle_scope_mismatch;
  }

  env->open_handle_scopes--;
  delete v8impl::V8HandleScopeFromJsHandleScope(scope);
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_open_escapable_handle_scope(
    napi_env env, napi_escapable_handle_scope* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = v8impl::JsEscapableHandleScopeFromV8EscapableHandleScope(
      new v8impl::EscapableHandleScopeWrapper(env->isolate));
  env->open_handle_scopes++;
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_close_escapable_handle_scope(
    napi_env env, napi_escapable_handle_scope scope) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, scope);
  if (env->open_handle_scopes == 0) {
    return napi_handle_scope_mismatch;
  }

  delete v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope);
  env->open_handle_scopes--;
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_escape_handle(napi_env env,
                                          napi_escapable_handle_scope scope,
                                          napi_value escapee,
                                          napi_value* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, scope);
  CHECK_ARG(env, escapee);
  CHECK_ARG(env, result);

  v8impl::EscapableHandleScopeWrapper* s =
      v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope);
  if (!s->escape_called()) {
    *result = v8impl::JsValueFromV8LocalValue(
        s->Escape(v8impl::V8LocalValueFromJsValue(escapee)));
    return napi_clear_last_error(env);
  }
  return napi_set_last_error(env, napi_escape_called_twice);
}

napi_status NAPI_CDECL napi_new_instance(napi_env env,
                                         napi_value constructor,
                                         size_t argc,
                                         const napi_value* argv,
                                         napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, constructor);
  if (argc > 0) {
    CHECK_ARG(env, argv);
  }
  CHECK_ARG(env, result);

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Function> ctor;
  CHECK_TO_FUNCTION(env, ctor, constructor);

  auto maybe = ctor->NewInstance(
      context,
      argc,
      reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)));

  CHECK_MAYBE_EMPTY(env, maybe, napi_pending_exception);

  *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked());
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_instanceof(napi_env env,
                                       napi_value object,
                                       napi_value constructor,
                                       bool* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, object);
  CHECK_ARG(env, result);

  *result = false;

  v8::Local<v8::Object> ctor;
  v8::Local<v8::Context> context = env->context();

  CHECK_TO_OBJECT(env, context, ctor, constructor);

  if (!ctor->IsFunction()) {
    napi_throw_type_error(
        env, "ERR_NAPI_CONS_FUNCTION", "Constructor must be a function");

    return napi_set_last_error(env, napi_function_expected);
  }

  napi_status status = napi_generic_failure;

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(object);
  auto maybe_result = val->InstanceOf(context, ctor);
  CHECK_MAYBE_NOTHING(env, maybe_result, status);
  *result = maybe_result.FromJust();
  return GET_RETURN_STATUS(env);
}

// Methods to support catching exceptions
napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, bool* result) {
  // NAPI_PREAMBLE is not used here: this function must execute when there is a
  // pending exception.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  *result = !env->last_exception.IsEmpty();
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_and_clear_last_exception(napi_env env,
                                                         napi_value* result) {
  // NAPI_PREAMBLE is not used here: this function must execute when there is a
  // pending exception.
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, result);

  if (env->last_exception.IsEmpty()) {
    return napi_get_undefined(env, result);
  } else {
    *result = v8impl::JsValueFromV8LocalValue(
        v8::Local<v8::Value>::New(env->isolate, env->last_exception));
    env->last_exception.Reset();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_is_arraybuffer(napi_env env,
                                           napi_value value,
                                           bool* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  *result = val->IsArrayBuffer();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_arraybuffer(napi_env env,
                                               size_t byte_length,
                                               void** data,
                                               napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::Isolate* isolate = env->isolate;
  v8::Local<v8::ArrayBuffer> buffer =
      v8::ArrayBuffer::New(isolate, byte_length);

  // Optionally return a pointer to the buffer's data, to avoid another call to
  // retrieve it.
  if (data != nullptr) {
    *data = buffer->Data();
  }

  *result = v8impl::JsValueFromV8LocalValue(buffer);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL
napi_create_external_arraybuffer(napi_env env,
                                 void* external_data,
                                 size_t byte_length,
                                 node_api_nogc_finalize finalize_cb,
                                 void* finalize_hint,
                                 napi_value* result) {
  // The API contract here is that the cleanup function runs on the JS thread,
  // and is able to use napi_env. Implementing that properly is hard, so use the
  // `Buffer` variant for easier implementation.
  napi_value buffer;
  STATUS_CALL(napi_create_external_buffer(
      env, byte_length, external_data, finalize_cb, finalize_hint, &buffer));
  return napi_get_typedarray_info(
      env, buffer, nullptr, nullptr, nullptr, result, nullptr);
}

napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env,
                                                 napi_value arraybuffer,
                                                 void** data,
                                                 size_t* byte_length) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, arraybuffer);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);
  RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg);

  v8::Local<v8::ArrayBuffer> ab = value.As<v8::ArrayBuffer>();

  if (data != nullptr) {
    *data = ab->Data();
  }

  if (byte_length != nullptr) {
    *byte_length = ab->ByteLength();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_is_typedarray(napi_env env,
                                          napi_value value,
                                          bool* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  *result = val->IsTypedArray();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_typedarray(napi_env env,
                                              napi_typedarray_type type,
                                              size_t length,
                                              napi_value arraybuffer,
                                              size_t byte_offset,
                                              napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, arraybuffer);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);
  RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg);

  v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
  v8::Local<v8::TypedArray> typedArray;

  switch (type) {
    case napi_int8_array:
      CREATE_TYPED_ARRAY(
          env, Int8Array, 1, buffer, byte_offset, length, typedArray);
      break;
    case napi_uint8_array:
      CREATE_TYPED_ARRAY(
          env, Uint8Array, 1, buffer, byte_offset, length, typedArray);
      break;
    case napi_uint8_clamped_array:
      CREATE_TYPED_ARRAY(
          env, Uint8ClampedArray, 1, buffer, byte_offset, length, typedArray);
      break;
    case napi_int16_array:
      CREATE_TYPED_ARRAY(
          env, Int16Array, 2, buffer, byte_offset, length, typedArray);
      break;
    case napi_uint16_array:
      CREATE_TYPED_ARRAY(
          env, Uint16Array, 2, buffer, byte_offset, length, typedArray);
      break;
    case napi_int32_array:
      CREATE_TYPED_ARRAY(
          env, Int32Array, 4, buffer, byte_offset, length, typedArray);
      break;
    case napi_uint32_array:
      CREATE_TYPED_ARRAY(
          env, Uint32Array, 4, buffer, byte_offset, length, typedArray);
      break;
    case napi_float32_array:
      CREATE_TYPED_ARRAY(
          env, Float32Array, 4, buffer, byte_offset, length, typedArray);
      break;
    case napi_float64_array:
      CREATE_TYPED_ARRAY(
          env, Float64Array, 8, buffer, byte_offset, length, typedArray);
      break;
    case napi_bigint64_array:
      CREATE_TYPED_ARRAY(
          env, BigInt64Array, 8, buffer, byte_offset, length, typedArray);
      break;
    case napi_biguint64_array:
      CREATE_TYPED_ARRAY(
          env, BigUint64Array, 8, buffer, byte_offset, length, typedArray);
      break;
    default:
      return napi_set_last_error(env, napi_invalid_arg);
  }

  *result = v8impl::JsValueFromV8LocalValue(typedArray);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_get_typedarray_info(napi_env env,
                                                napi_value typedarray,
                                                napi_typedarray_type* type,
                                                size_t* length,
                                                void** data,
                                                napi_value* arraybuffer,
                                                size_t* byte_offset) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, typedarray);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(typedarray);
  RETURN_STATUS_IF_FALSE(env, value->IsTypedArray(), napi_invalid_arg);

  v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>();

  if (type != nullptr) {
    if (value->IsInt8Array()) {
      *type = napi_int8_array;
    } else if (value->IsUint8Array()) {
      *type = napi_uint8_array;
    } else if (value->IsUint8ClampedArray()) {
      *type = napi_uint8_clamped_array;
    } else if (value->IsInt16Array()) {
      *type = napi_int16_array;
    } else if (value->IsUint16Array()) {
      *type = napi_uint16_array;
    } else if (value->IsInt32Array()) {
      *type = napi_int32_array;
    } else if (value->IsUint32Array()) {
      *type = napi_uint32_array;
    } else if (value->IsFloat32Array()) {
      *type = napi_float32_array;
    } else if (value->IsFloat64Array()) {
      *type = napi_float64_array;
    } else if (value->IsBigInt64Array()) {
      *type = napi_bigint64_array;
    } else if (value->IsBigUint64Array()) {
      *type = napi_biguint64_array;
    }
  }

  if (length != nullptr) {
    *length = array->Length();
  }

  v8::Local<v8::ArrayBuffer> buffer;
  if (data != nullptr || arraybuffer != nullptr) {
    // Calling Buffer() may have the side effect of allocating the buffer,
    // so only do this when it’s needed.
    buffer = array->Buffer();
  }

  if (data != nullptr) {
    *data = static_cast<uint8_t*>(buffer->Data()) + array->ByteOffset();
  }

  if (arraybuffer != nullptr) {
    *arraybuffer = v8impl::JsValueFromV8LocalValue(buffer);
  }

  if (byte_offset != nullptr) {
    *byte_offset = array->ByteOffset();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_dataview(napi_env env,
                                            size_t byte_length,
                                            napi_value arraybuffer,
                                            size_t byte_offset,
                                            napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, arraybuffer);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);
  RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg);

  v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
  if (byte_length + byte_offset > buffer->ByteLength()) {
    napi_throw_range_error(env,
                           "ERR_NAPI_INVALID_DATAVIEW_ARGS",
                           "byte_offset + byte_length should be less than or "
                           "equal to the size in bytes of the array passed in");
    return napi_set_last_error(env, napi_pending_exception);
  }
  v8::Local<v8::DataView> DataView =
      v8::DataView::New(buffer, byte_offset, byte_length);

  *result = v8impl::JsValueFromV8LocalValue(DataView);
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_is_dataview(napi_env env,
                                        napi_value value,
                                        bool* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  *result = val->IsDataView();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_dataview_info(napi_env env,
                                              napi_value dataview,
                                              size_t* byte_length,
                                              void** data,
                                              napi_value* arraybuffer,
                                              size_t* byte_offset) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, dataview);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(dataview);
  RETURN_STATUS_IF_FALSE(env, value->IsDataView(), napi_invalid_arg);

  v8::Local<v8::DataView> array = value.As<v8::DataView>();

  if (byte_length != nullptr) {
    *byte_length = array->ByteLength();
  }

  v8::Local<v8::ArrayBuffer> buffer;
  if (data != nullptr || arraybuffer != nullptr) {
    // Calling Buffer() may have the side effect of allocating the buffer,
    // so only do this when it’s needed.
    buffer = array->Buffer();
  }

  if (data != nullptr) {
    *data = static_cast<uint8_t*>(buffer->Data()) + array->ByteOffset();
  }

  if (arraybuffer != nullptr) {
    *arraybuffer = v8impl::JsValueFromV8LocalValue(buffer);
  }

  if (byte_offset != nullptr) {
    *byte_offset = array->ByteOffset();
  }

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_version(node_api_nogc_env env,
                                        uint32_t* result) {
  CHECK_ENV(env);
  CHECK_ARG(env, result);
  *result = NODE_API_SUPPORTED_VERSION_MAX;
  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_promise(napi_env env,
                                           napi_deferred* deferred,
                                           napi_value* promise) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, deferred);
  CHECK_ARG(env, promise);

  auto maybe = v8::Promise::Resolver::New(env->context());
  CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);

  auto v8_resolver = maybe.ToLocalChecked();
  auto v8_deferred = new v8impl::Persistent<v8::Value>();
  v8_deferred->Reset(env->isolate, v8_resolver);

  *deferred = v8impl::JsDeferredFromNodePersistent(v8_deferred);
  *promise = v8impl::JsValueFromV8LocalValue(v8_resolver->GetPromise());
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_resolve_deferred(napi_env env,
                                             napi_deferred deferred,
                                             napi_value resolution) {
  return v8impl::ConcludeDeferred(env, deferred, resolution, true);
}

napi_status NAPI_CDECL napi_reject_deferred(napi_env env,
                                            napi_deferred deferred,
                                            napi_value resolution) {
  return v8impl::ConcludeDeferred(env, deferred, resolution, false);
}

napi_status NAPI_CDECL napi_is_promise(napi_env env,
                                       napi_value value,
                                       bool* is_promise) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, is_promise);

  *is_promise = v8impl::V8LocalValueFromJsValue(value)->IsPromise();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_date(napi_env env,
                                        double time,
                                        napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, result);

  v8::MaybeLocal<v8::Value> maybe_date = v8::Date::New(env->context(), time);
  CHECK_MAYBE_EMPTY(env, maybe_date, napi_generic_failure);

  *result = v8impl::JsValueFromV8LocalValue(maybe_date.ToLocalChecked());

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_is_date(napi_env env,
                                    napi_value value,
                                    bool* is_date) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, is_date);

  *is_date = v8impl::V8LocalValueFromJsValue(value)->IsDate();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_date_value(napi_env env,
                                           napi_value value,
                                           double* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, value);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
  RETURN_STATUS_IF_FALSE(env, val->IsDate(), napi_date_expected);

  v8::Local<v8::Date> date = val.As<v8::Date>();
  *result = date->ValueOf();

  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL napi_run_script(napi_env env,
                                       napi_value script,
                                       napi_value* result) {
  NAPI_PREAMBLE(env);
  CHECK_ARG(env, script);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> v8_script = v8impl::V8LocalValueFromJsValue(script);

  if (!v8_script->IsString()) {
    return napi_set_last_error(env, napi_string_expected);
  }

  v8::Local<v8::Context> context = env->context();

  auto maybe_script = v8::Script::Compile(context, v8_script.As<v8::String>());
  CHECK_MAYBE_EMPTY(env, maybe_script, napi_generic_failure);

  auto script_result = maybe_script.ToLocalChecked()->Run(context);
  CHECK_MAYBE_EMPTY(env, script_result, napi_generic_failure);

  *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked());
  return GET_RETURN_STATUS(env);
}

napi_status NAPI_CDECL
napi_add_finalizer(napi_env env,
                   napi_value js_object,
                   void* finalize_data,
                   node_api_nogc_finalize nogc_finalize_cb,
                   void* finalize_hint,
                   napi_ref* result) {
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
  // JS exceptions.
  napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, js_object);
  CHECK_ARG(env, finalize_cb);

  v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(js_object);
  RETURN_STATUS_IF_FALSE(env, v8_value->IsObject(), napi_invalid_arg);

  // Create a self-deleting reference if the optional out-param result is not
  // set.
  v8impl::Ownership ownership = result == nullptr
                                    ? v8impl::Ownership::kRuntime
                                    : v8impl::Ownership::kUserland;
  v8impl::Reference* reference = v8impl::Reference::New(
      env, v8_value, 0, ownership, finalize_cb, finalize_data, finalize_hint);

  if (result != nullptr) {
    *result = reinterpret_cast<napi_ref>(reference);
  }
  return napi_clear_last_error(env);
}

#ifdef NAPI_EXPERIMENTAL

napi_status NAPI_CDECL node_api_post_finalizer(node_api_nogc_env nogc_env,
                                               napi_finalize finalize_cb,
                                               void* finalize_data,
                                               void* finalize_hint) {
  napi_env env = const_cast<napi_env>(nogc_env);
  CHECK_ENV(env);
  env->EnqueueFinalizer(v8impl::TrackedFinalizer::New(
      env, finalize_cb, finalize_data, finalize_hint));
  return napi_clear_last_error(env);
}

#endif

napi_status NAPI_CDECL napi_adjust_external_memory(node_api_nogc_env env,
                                                   int64_t change_in_bytes,
                                                   int64_t* adjusted_value) {
  CHECK_ENV(env);
  CHECK_ARG(env, adjusted_value);

  *adjusted_value =
      env->isolate->AdjustAmountOfExternalAllocatedMemory(change_in_bytes);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_set_instance_data(node_api_nogc_env nogc_env,
                                              void* data,
                                              napi_finalize finalize_cb,
                                              void* finalize_hint) {
  napi_env env = const_cast<napi_env>(nogc_env);
  CHECK_ENV(env);

  v8impl::RefBase* old_data = static_cast<v8impl::RefBase*>(env->instance_data);
  if (old_data != nullptr) {
    // Our contract so far has been to not finalize any old data there may be.
    // So we simply delete it.
    delete old_data;
  }

  env->instance_data = v8impl::RefBase::New(
      env, 0, v8impl::Ownership::kRuntime, finalize_cb, data, finalize_hint);

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_get_instance_data(node_api_nogc_env env,
                                              void** data) {
  CHECK_ENV(env);
  CHECK_ARG(env, data);

  v8impl::RefBase* idata = static_cast<v8impl::RefBase*>(env->instance_data);

  *data = (idata == nullptr ? nullptr : idata->Data());

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_detach_arraybuffer(napi_env env,
                                               napi_value arraybuffer) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, arraybuffer);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);
  RETURN_STATUS_IF_FALSE(
      env, value->IsArrayBuffer(), napi_arraybuffer_expected);

  v8::Local<v8::ArrayBuffer> it = value.As<v8::ArrayBuffer>();
  RETURN_STATUS_IF_FALSE(
      env, it->IsDetachable(), napi_detachable_arraybuffer_expected);

  it->Detach(v8::Local<v8::Value>()).Check();

  return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_is_detached_arraybuffer(napi_env env,
                                                    napi_value arraybuffer,
                                                    bool* result) {
  CHECK_ENV_NOT_IN_GC(env);
  CHECK_ARG(env, arraybuffer);
  CHECK_ARG(env, result);

  v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);

  *result =
      value->IsArrayBuffer() && value.As<v8::ArrayBuffer>()->WasDetached();

  return napi_clear_last_error(env);
}

Zerion Mini Shell 1.0