current commit
261002.2 · ffi: throw on allocation failure in toArrayBuffer()
#if HAVE_FFI

#include "data.h"
#include "base_object-inl.h"
#include "node_errors.h"
#include "util.h"
#include "v8.h"

#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
#include <type_traits>

using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::BigInt;
using v8::FunctionCallbackInfo;
using v8::Integer;
using v8::Isolate;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::NewStringType;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::SharedArrayBuffer;
using v8::String;
using v8::Value;

namespace node {

namespace ffi {

Maybe<size_t> GetValidatedSize(Environment* env,
                               Local<Value> value,
                               const char* label) {
  if (!value->IsNumber()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The %s must be a number", label);
    return Nothing<size_t>();
  }

  double length = value.As<Number>()->Value();
  if (!std::isfinite(length) || length < 0 || std::floor(length) != length) {
    THROW_ERR_INVALID_ARG_VALUE(
        env, "The %s must be a non-negative integer", label);
    return Nothing<size_t>();
  }

  // Values beyond Number.MAX_SAFE_INTEGER may already have been rounded
  // by the caller. On 32-bit platforms SIZE_MAX is the tighter bound.
  if (length > kMaxSafeJsInteger ||
      length > static_cast<double>(std::numeric_limits<size_t>::max())) {
    THROW_ERR_OUT_OF_RANGE(env, "The %s is too large", label);
    return Nothing<size_t>();
  }

  return Just(static_cast<size_t>(length));
}

Maybe<uintptr_t> GetValidatedPointerAddress(Environment* env,
                                            Local<Value> value,
                                            const char* label) {
  if (!value->IsBigInt()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The %s must be a bigint", label);
    return Nothing<uintptr_t>();
  }

  bool lossless;
  uint64_t address = value.As<BigInt>()->Uint64Value(&lossless);
  if (!lossless) {
    THROW_ERR_INVALID_ARG_VALUE(
        env, "The %s must be a non-negative bigint", label);
    return Nothing<uintptr_t>();
  }

  if (address > static_cast<uint64_t>(std::numeric_limits<uintptr_t>::max())) {
    THROW_ERR_INVALID_ARG_VALUE(
        env, "The %s exceeds the platform pointer range", label);
    return Nothing<uintptr_t>();
  }

  return Just(static_cast<uintptr_t>(address));
}

Maybe<int64_t> GetValidatedSignedInt(Environment* env,
                                     Local<Value> value,
                                     int64_t min,
                                     int64_t max,
                                     const char* type_name) {
  if (!value->IsNumber()) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an %s", type_name);
    return Nothing<int64_t>();
  }

  double number = value.As<Number>()->Value();
  if (!std::isfinite(number) || std::floor(number) != number || number < min ||
      number > max) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an %s", type_name);
    return Nothing<int64_t>();
  }

  return Just(static_cast<int64_t>(number));
}

Maybe<uint64_t> GetValidatedUnsignedInt(Environment* env,
                                        Local<Value> value,
                                        uint64_t max,
                                        const char* type_name) {
  if (!value->IsNumber()) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a %s", type_name);
    return Nothing<uint64_t>();
  }

  double number = value.As<Number>()->Value();
  if (!std::isfinite(number) || std::floor(number) != number || number < 0 ||
      number > static_cast<double>(max)) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a %s", type_name);
    return Nothing<uint64_t>();
  }

  return Just(static_cast<uint64_t>(number));
}

Maybe<void> ValidatePointerSpan(Environment* env,
                                uintptr_t raw_ptr,
                                size_t offset,
                                size_t length,
                                const char* error_message) {
  if (offset > std::numeric_limits<uintptr_t>::max() - raw_ptr) {
    THROW_ERR_INVALID_ARG_VALUE(env, error_message);
    return Nothing<void>();
  }

  uintptr_t start = raw_ptr + offset;
  if (length > 0 &&
      length - 1 > std::numeric_limits<uintptr_t>::max() - start) {
    THROW_ERR_INVALID_ARG_VALUE(env, error_message);
    return Nothing<void>();
  }

  return JustVoid();
}

Maybe<void> ValidateBufferLength(Environment* env, size_t len) {
  if (len > Buffer::kMaxLength) {
    THROW_ERR_BUFFER_TOO_LARGE(env, "Buffer is too large");
    return Nothing<void>();
  }

  return JustVoid();
}

Maybe<void> ValidateStringLength(Environment* env, size_t len) {
  if (len > static_cast<size_t>(String::kMaxLength)) {
    THROW_ERR_STRING_TOO_LONG(env, "String is too long");
    return Nothing<void>();
  }

  return JustVoid();
}

Maybe<std::pair<uint8_t*, size_t>> GetValidatedPointerAndOffset(
    Environment* env, const FunctionCallbackInfo<Value>& args) {
  uintptr_t raw_ptr;
  if (!GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) {
    return {};
  }

  if (raw_ptr == 0) {
    THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer");
    return {};
  }

  size_t offset = 0;
  if (args.Length() > 1 && !args[1]->IsUndefined()) {
    if (!GetValidatedSize(env, args[1], "offset").To(&offset)) {
      return {};
    }
  }

  if (ValidatePointerSpan(
          env,
          raw_ptr,
          offset,
          1,
          "The pointer and offset exceed the platform address range")
          .IsNothing()) {
    return {};
  }

  return Just(std::make_pair(reinterpret_cast<uint8_t*>(raw_ptr), offset));
}

struct PointerOffsetAndValue {
  uint8_t* ptr;
  size_t offset;
  Local<Value> value;
};

Maybe<PointerOffsetAndValue> GetValidatedPointerOffsetAndValue(
    Environment* env, const FunctionCallbackInfo<Value>& args) {
  size_t offset;
  Local<Value> value;
  uintptr_t raw_ptr;
  if (!GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) {
    return {};
  }

  if (raw_ptr == 0) {
    THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer");
    return {};
  }

  if (args.Length() < 2 || args[1]->IsUndefined()) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Expected an offset argument");
    return {};
  }

  if (!GetValidatedSize(env, args[1], "offset").To(&offset)) {
    return {};
  }

  if (ValidatePointerSpan(
          env,
          raw_ptr,
          offset,
          1,
          "The pointer and offset exceed the platform address range")
          .IsNothing()) {
    return {};
  }

  if (args.Length() < 3 || args[2]->IsUndefined()) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Expected a value argument");
    return {};
  }

  value = args[2];

  uint8_t* ptr = reinterpret_cast<uint8_t*>(static_cast<uintptr_t>(raw_ptr));
  return Just(PointerOffsetAndValue{ptr, offset, value});
}

template <typename T>
void GetValue(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");
  Isolate* isolate = env->isolate();
  std::pair<uint8_t*, size_t> ptr_and_offset;

  if (!GetValidatedPointerAndOffset(env, args).To(&ptr_and_offset)) {
    return;
  }

  auto [ptr, offset] = ptr_and_offset;
  uintptr_t raw_ptr = reinterpret_cast<uintptr_t>(ptr);
  if (ValidatePointerSpan(
          env,
          raw_ptr,
          offset,
          sizeof(T),
          "The accessed range exceeds the platform address range")
          .IsNothing()) {
    return;
  }

  T value;
  std::memcpy(&value, ptr + offset, sizeof(value));
  if constexpr (std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t> ||
                std::is_same_v<T, int32_t>) {
    args.GetReturnValue().Set(Integer::New(isolate, value));
  } else if constexpr (std::is_same_v<T, uint8_t> ||
                       std::is_same_v<T, uint16_t> ||
                       std::is_same_v<T, uint32_t>) {
    args.GetReturnValue().Set(Integer::NewFromUnsigned(isolate, value));
  } else if constexpr (std::is_same_v<T, int64_t>) {
    args.GetReturnValue().Set(BigInt::New(isolate, value));
  } else if constexpr (std::is_same_v<T, uint64_t>) {
    args.GetReturnValue().Set(BigInt::NewFromUnsigned(isolate, value));
  } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
    args.GetReturnValue().Set(Number::New(isolate, value));
  } else {
    UNREACHABLE();
  }
}

template <typename T>
void SetValue(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");
  PointerOffsetAndValue data;

  if (!GetValidatedPointerOffsetAndValue(env, args).To(&data)) {
    return;
  }
  auto [ptr, offset, value] = data;

  uintptr_t raw_ptr = reinterpret_cast<uintptr_t>(ptr);
  if (ValidatePointerSpan(
          env,
          raw_ptr,
          offset,
          sizeof(T),
          "The accessed range exceeds the platform address range")
          .IsNothing()) {
    return;
  }

  T converted;

  if constexpr (std::is_same_v<T, int8_t>) {
    int64_t validated;
    if (!GetValidatedSignedInt(env, value, INT8_MIN, INT8_MAX, "int8")
             .To(&validated)) {
      return;
    }
    converted = static_cast<T>(validated);
  } else if constexpr (std::is_same_v<T, uint8_t>) {
    uint64_t validated;
    if (!GetValidatedUnsignedInt(env, value, UINT8_MAX, "uint8")
             .To(&validated)) {
      return;
    }
    converted = static_cast<T>(validated);
  } else if constexpr (std::is_same_v<T, int16_t>) {
    int64_t validated;
    if (!GetValidatedSignedInt(env, value, INT16_MIN, INT16_MAX, "int16")
             .To(&validated)) {
      return;
    }
    converted = static_cast<T>(validated);
  } else if constexpr (std::is_same_v<T, uint16_t>) {
    uint64_t validated;
    if (!GetValidatedUnsignedInt(env, value, UINT16_MAX, "uint16")
             .To(&validated)) {
      return;
    }
    converted = static_cast<T>(validated);
  } else if constexpr (std::is_same_v<T, int32_t>) {
    int64_t validated;
    if (!GetValidatedSignedInt(env, value, INT32_MIN, INT32_MAX, "int32")
             .To(&validated)) {
      return;
    }
    converted = static_cast<T>(validated);
  } else if constexpr (std::is_same_v<T, uint32_t>) {
    uint64_t validated;
    if (!GetValidatedUnsignedInt(env, value, UINT32_MAX, "uint32")
             .To(&validated)) {
      return;
    }
    converted = static_cast<T>(validated);
  } else if constexpr (std::is_same_v<T, int64_t>) {
    if (value->IsBigInt()) {
      bool lossless;
      converted = static_cast<T>(value.As<BigInt>()->Int64Value(&lossless));
      if (!lossless) {
        THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an int64");
        return;
      }
    } else if (value->IsNumber()) {
      int64_t validated;
      if (!GetValidatedSignedInt(env,
                                 value,
                                 -static_cast<int64_t>(kMaxSafeJsInteger),
                                 static_cast<int64_t>(kMaxSafeJsInteger),
                                 "int64")
               .To(&validated)) {
        return;
      }
      converted = static_cast<T>(validated);
    } else {
      THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a bigint or a number");
      return;
    }
  } else if constexpr (std::is_same_v<T, uint64_t>) {
    if (value->IsBigInt()) {
      bool lossless;
      converted = static_cast<T>(value.As<BigInt>()->Uint64Value(&lossless));
      if (!lossless) {
        THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a uint64");
        return;
      }
    } else if (value->IsNumber()) {
      uint64_t validated;
      if (!GetValidatedUnsignedInt(
               env, value, static_cast<uint64_t>(kMaxSafeJsInteger), "uint64")
               .To(&validated)) {
        return;
      }
      converted = static_cast<T>(validated);
    } else {
      THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a bigint or a number");
      return;
    }
  } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
    if (!value->IsNumber()) {
      if constexpr (std::is_same_v<T, float>) {
        THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a float");
      } else {
        THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a double");
      }
      return;
    }

    converted = static_cast<T>(value.As<Number>()->Value());
  } else {
    UNREACHABLE();
  }

  std::memcpy(ptr + offset, &converted, sizeof(converted));
}

// Raw FFI memory helpers are low-level and unsafe. Pointer validity,
// lifetime, and zero-copy writable views are the caller's responsibility.
void GetInt8(const FunctionCallbackInfo<Value>& args) {
  GetValue<int8_t>(args);
}

void GetUint8(const FunctionCallbackInfo<Value>& args) {
  GetValue<uint8_t>(args);
}

void GetInt16(const FunctionCallbackInfo<Value>& args) {
  GetValue<int16_t>(args);
}

void GetUint16(const FunctionCallbackInfo<Value>& args) {
  GetValue<uint16_t>(args);
}

void GetInt32(const FunctionCallbackInfo<Value>& args) {
  GetValue<int32_t>(args);
}

void GetUint32(const FunctionCallbackInfo<Value>& args) {
  GetValue<uint32_t>(args);
}

void GetInt64(const FunctionCallbackInfo<Value>& args) {
  GetValue<int64_t>(args);
}

void GetUint64(const FunctionCallbackInfo<Value>& args) {
  GetValue<uint64_t>(args);
}

void GetFloat32(const FunctionCallbackInfo<Value>& args) {
  GetValue<float>(args);
}

void GetFloat64(const FunctionCallbackInfo<Value>& args) {
  GetValue<double>(args);
}

void SetInt8(const FunctionCallbackInfo<Value>& args) {
  SetValue<int8_t>(args);
}

void SetUint8(const FunctionCallbackInfo<Value>& args) {
  SetValue<uint8_t>(args);
}

void SetInt16(const FunctionCallbackInfo<Value>& args) {
  SetValue<int16_t>(args);
}

void SetUint16(const FunctionCallbackInfo<Value>& args) {
  SetValue<uint16_t>(args);
}

void SetInt32(const FunctionCallbackInfo<Value>& args) {
  SetValue<int32_t>(args);
}

void SetUint32(const FunctionCallbackInfo<Value>& args) {
  SetValue<uint32_t>(args);
}

void SetInt64(const FunctionCallbackInfo<Value>& args) {
  SetValue<int64_t>(args);
}

void SetUint64(const FunctionCallbackInfo<Value>& args) {
  SetValue<uint64_t>(args);
}

void SetFloat32(const FunctionCallbackInfo<Value>& args) {
  SetValue<float>(args);
}

void SetFloat64(const FunctionCallbackInfo<Value>& args) {
  SetValue<double>(args);
}

void ToString(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();

  // Raw FFI memory helpers are low-level and unsafe. Pointer validity,
  // lifetime, and zero-copy writable views are the caller's responsibility.
  // `toString()` requires a valid NUL-terminated C string pointer.
  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");

  if (args.Length() < 1 || !args[0]->IsBigInt()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint");
    return;
  }

  uintptr_t ptr;
  if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) {
    return;
  }

  if (ptr == 0) {
    args.GetReturnValue().SetNull();
    return;
  }

  const char* str = reinterpret_cast<const char*>(ptr);
  size_t len = std::strlen(str);
  if (ValidateStringLength(env, len).IsNothing()) {
    return;
  }

  Local<String> out;
  if (!String::NewFromUtf8(isolate, str, NewStringType::kNormal, len)
           .ToLocal(&out)) {
    return;
  }

  args.GetReturnValue().Set(out);
}

// Foreign memory lies outside the V8 sandbox and cannot back an ArrayBuffer.
static bool ZeroCopyUnavailable(Environment* env) {
#ifdef V8_ENABLE_SANDBOX
  THROW_ERR_OPERATION_FAILED(
      env, "Zero-copy views are not available when the V8 sandbox is enabled");
  return true;
#else
  return false;
#endif
}

void ToBuffer(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");

  // `copy === false` exposes a zero-copy writable view over foreign memory.
  // This is intentionally unsafe and the caller must guarantee pointer
  // validity, lifetime, and bounds.

  if (args.Length() < 1 || !args[0]->IsBigInt()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint");
    return;
  }

  uintptr_t ptr;
  if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) {
    return;
  }

  size_t len;
  if (!GetValidatedSize(env, args[1], "length").To(&len)) {
    return;
  }

  if (ptr == 0 && len > 0) {
    THROW_ERR_FFI_INVALID_POINTER(env,
                                  "Cannot create a buffer from a null pointer");
    return;
  }

  if (ValidatePointerSpan(
          env,
          ptr,
          0,
          len,
          "The pointer and length exceed the platform address range")
          .IsNothing()) {
    return;
  }

  if (ValidateBufferLength(env, len).IsNothing()) {
    return;
  }

  if (!args[2]->IsUndefined() && !args[2]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The copy argument must be a boolean");
    return;
  }

  bool copy = !args[2]->IsFalse();
  if (!copy && ZeroCopyUnavailable(env)) return;

  Local<Object> buf;
  if (copy) {
    if (!Buffer::Copy(env, reinterpret_cast<char*>(ptr), len).ToLocal(&buf)) {
      return;
    }
  } else {
    if (!Buffer::New(
             env,
             reinterpret_cast<char*>(ptr),
             len,
             [](char* data, void* hint) {},
             nullptr)
             .ToLocal(&buf)) {
      return;
    }
  }

  args.GetReturnValue().Set(buf);
}

void ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();

  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");

  if (args.Length() < 1 || !args[0]->IsBigInt()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint");
    return;
  }

  uintptr_t ptr;
  if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) {
    return;
  }

  size_t len;
  if (!GetValidatedSize(env, args[1], "length").To(&len)) {
    return;
  }

  if (ptr == 0 && len > 0) {
    THROW_ERR_FFI_INVALID_POINTER(
        env, "Cannot create an ArrayBuffer from a null pointer");
    return;
  }

  if (ValidatePointerSpan(
          env,
          ptr,
          0,
          len,
          "The pointer and length exceed the platform address range")
          .IsNothing()) {
    return;
  }

  if (ValidateBufferLength(env, len).IsNothing()) {
    return;
  }

  if (!args[2]->IsUndefined() && !args[2]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The copy argument must be a boolean");
    return;
  }

  bool copy = !args[2]->IsFalse();
  if (!copy && ZeroCopyUnavailable(env)) return;

  Local<ArrayBuffer> ab;
  if (copy) {
    std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
        isolate,
        len,
        BackingStoreInitializationMode::kUninitialized,
        BackingStoreOnFailureMode::kReturnNull);
    if (!store) [[unlikely]] {
      THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
      return;
    }
    if (len > 0) memcpy(store->Data(), reinterpret_cast<void*>(ptr), len);
    ab = ArrayBuffer::New(isolate, std::move(store));
  } else {
    std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
        reinterpret_cast<void*>(ptr),
        len,
        [](void* data, size_t length, void* deleter_data) {},
        nullptr);

    ab = ArrayBuffer::New(isolate, std::move(store));
  }

  args.GetReturnValue().Set(ab);
}

void ExportBytes(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");

  if (args.Length() < 1) {
    THROW_ERR_INVALID_ARG_TYPE(
        env,
        "The first argument must be a Buffer, ArrayBuffer, SharedArrayBuffer, "
        "or ArrayBufferView");
    return;
  }

  // This needs to be kept alive until the data
  // is actually copied.
  ArrayBufferViewContents<uint8_t> view;

  if (args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer() ||
      args[0]->IsArrayBufferView()) {
    view.ReadValue(args[0]);
    if (view.WasDetached()) {
      THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
      return;
    }
  } else {
    THROW_ERR_INVALID_ARG_TYPE(
        env,
        "The first argument must be a Buffer, ArrayBuffer, SharedArrayBuffer, "
        "or ArrayBufferView");
    return;
  }

  uintptr_t ptr;
  if (!GetValidatedPointerAddress(env, args[1], "pointer").To(&ptr)) {
    return;
  }

  size_t len;
  if (!GetValidatedSize(env, args[2], "length").To(&len)) {
    return;
  }

  if (len < view.length()) {
    THROW_ERR_OUT_OF_RANGE(env, "The length must be >= source byte length");
    return;
  }

  if (ptr == 0 && view.length() > 0) {
    THROW_ERR_FFI_INVALID_POINTER(env,
                                  "Cannot create a buffer from a null pointer");
    return;
  }

  if (ValidatePointerSpan(
          env,
          ptr,
          0,
          len,
          "The pointer and length exceed the platform address range")
          .IsNothing()) {
    return;
  }

  if (view.length() > 0) {
    std::memcpy(reinterpret_cast<void*>(ptr), view.data(), view.length());
  }
}

void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();

  THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");

  if (args.Length() < 1) {
    THROW_ERR_INVALID_ARG_TYPE(
        env,
        "The first argument must be a Buffer, ArrayBuffer, SharedArrayBuffer, "
        "or ArrayBufferView");
    return;
  }

  uintptr_t ptr = 0;
  size_t offset = 0;
  std::shared_ptr<BackingStore> store;

  if (args[0]->IsArrayBuffer()) {
    Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
    if (buffer->WasDetached()) {
      THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
      return;
    }
    store = buffer->GetBackingStore();
  } else if (args[0]->IsSharedArrayBuffer()) {
    store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
  } else if (args[0]->IsArrayBufferView()) {
    Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
    if (view->Buffer()->WasDetached()) {
      THROW_ERR_INVALID_ARG_VALUE(
          env, "ArrayBufferView is backed by a detached ArrayBuffer");
      return;
    }
    // Access the store here to ensure that it exists. Small typed arrays
    // may not have a store until this point and can instead be stored
    // entirely in-heap.
    store = view->Buffer()->GetBackingStore();
    offset = view->ByteOffset();
  } else {
    THROW_ERR_INVALID_ARG_TYPE(
        env,
        "The first argument must be a Buffer, "
        "ArrayBuffer, SharedArrayBuffer, or ArrayBufferView");
    return;
  }

  // WARNING: There is no inherent guarantee that the pointer returned
  // from this function will be valid beyond the lifetime of the BackingStore
  // instance!
  if (!store) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBuffer backing store");
    return;
  }
  ptr = reinterpret_cast<uintptr_t>(store->Data()) + offset;

  args.GetReturnValue().Set(
      BigInt::NewFromUnsigned(isolate, static_cast<uint64_t>(ptr)));
}

}  // namespace ffi

}  // namespace node

#endif  // HAVE_FFI