From 5d4656887af7397700006e1beec28dc9abd3333f Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Wed, 10 Apr 2024 15:56:15 -0700 Subject: [PATCH] Start adding Node-API to Hermes --- API/CMakeLists.txt | 1 + API/hermes/CMakeLists.txt | 6 +- API/hermes/MurmurHash.cpp | 170 + API/hermes/MurmurHash.h | 10 + API/hermes/ScriptStore.h | 79 + API/hermes/hermes_api.h | 85 + API/hermes/hermes_napi.cpp | 7891 ++++++++++++++++++++++++++++ API/node-api/.clang-format | 111 + API/node-api/CMakeLists.txt | 13 + API/node-api/js_native_api.h | 553 ++ API/node-api/js_native_api_types.h | 167 + API/node-api/js_runtime_api.h | 186 + 12 files changed, 9270 insertions(+), 2 deletions(-) create mode 100644 API/hermes/MurmurHash.cpp create mode 100644 API/hermes/MurmurHash.h create mode 100644 API/hermes/ScriptStore.h create mode 100644 API/hermes/hermes_api.h create mode 100644 API/hermes/hermes_napi.cpp create mode 100644 API/node-api/.clang-format create mode 100644 API/node-api/CMakeLists.txt create mode 100644 API/node-api/js_native_api.h create mode 100644 API/node-api/js_native_api_types.h create mode 100644 API/node-api/js_runtime_api.h diff --git a/API/CMakeLists.txt b/API/CMakeLists.txt index 867e9683f34..5ce62052982 100644 --- a/API/CMakeLists.txt +++ b/API/CMakeLists.txt @@ -3,6 +3,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +add_subdirectory(node-api) add_subdirectory(hermes) add_subdirectory(hermes_abi) add_subdirectory(hermes_sandbox) diff --git a/API/hermes/CMakeLists.txt b/API/hermes/CMakeLists.txt index 4bf27626b7b..9c410193bfc 100644 --- a/API/hermes/CMakeLists.txt +++ b/API/hermes/CMakeLists.txt @@ -73,6 +73,8 @@ add_subdirectory(cdp) # against the internal functionality they need. set(api_sources hermes.cpp + hermes_napi.cpp + MurmurHash.cpp DebuggerAPI.cpp AsyncDebuggerAPI.cpp RuntimeTaskRunner.cpp @@ -102,7 +104,7 @@ add_hermes_library(traceInterpreter TraceInterpreter.cpp LINK_LIBS libhermes hermesInstrumentation synthTrace synthTraceParser) add_library(libhermes ${api_sources}) -target_link_libraries(libhermes PUBLIC jsi PRIVATE hermesVMRuntime ${INSPECTOR_DEPS}) +target_link_libraries(libhermes PUBLIC jsi hermesNapi PRIVATE hermesVMRuntime ${INSPECTOR_DEPS}) target_link_options(libhermes PRIVATE ${HERMES_EXTRA_LINKER_FLAGS}) # Export the required header directory @@ -113,7 +115,7 @@ set_target_properties(libhermes PROPERTIES OUTPUT_NAME hermes) # Create a lean version of libhermes in the same way. add_library(libhermes_lean ${api_sources}) -target_link_libraries(libhermes_lean PUBLIC jsi PRIVATE hermesVMRuntimeLean ${INSPECTOR_DEPS}) +target_link_libraries(libhermes_lean PUBLIC jsi hermesNapi PRIVATE hermesVMRuntimeLean ${INSPECTOR_DEPS}) target_link_options(libhermes_lean PRIVATE ${HERMES_EXTRA_LINKER_FLAGS}) target_include_directories(libhermes_lean PUBLIC .. ../../public ${HERMES_JSI_DIR}) set_target_properties(libhermes_lean PROPERTIES OUTPUT_NAME hermes_lean) diff --git a/API/hermes/MurmurHash.cpp b/API/hermes/MurmurHash.cpp new file mode 100644 index 00000000000..5269ec016c1 --- /dev/null +++ b/API/hermes/MurmurHash.cpp @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// This code was adapted from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.h +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. +#include "MurmurHash.h" +#include + +#if defined(_MSC_VER) + +#define FORCE_INLINE __forceinline +#define ROTL64(x, y) _rotl64(x, y) +#define BIG_CONSTANT(x) (x) + +#else // defined(_MSC_VER) + +#define FORCE_INLINE inline __attribute__((always_inline)) +inline uint64_t rotl64(uint64_t x, int8_t r) { + return (x << r) | (x >> (64 - r)); +} +#define ROTL64(x, y) rotl64(x, y) +#define BIG_CONSTANT(x) (x##LLU) + +#endif // !defined(_MSC_VER) + +using namespace std; + +FORCE_INLINE uint64_t fmix64(uint64_t k) { + k ^= k >> 33; + k *= BIG_CONSTANT(0xff51afd7ed558ccd); + k ^= k >> 33; + k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); + k ^= k >> 33; + + return k; +} + +bool isAscii(uint64_t k) { + return (k & 0x8080808080808080) == 0ull; +} + +bool MurmurHash3_x64_128(const void *key, const int len, const uint32_t seed, void *out) { + const uint8_t *data = (const uint8_t *)key; + const int nblocks = len / 16; + + uint64_t h1 = seed; + uint64_t h2 = seed; + + const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); + const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + + //---------- + // body + + const uint64_t *blocks = (const uint64_t *)(data); + + bool isAsciiString{true}; + for (int i = 0; i < nblocks; i++) { + uint64_t k1 = blocks[i * 2 + 0]; + uint64_t k2 = blocks[i * 2 + 1]; + + isAsciiString &= isAscii(k1) && isAscii(k2); + + k1 *= c1; + k1 = ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + + h1 = ROTL64(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + + k2 *= c2; + k2 = ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + h2 = ROTL64(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + //---------- + // tail + + const uint8_t *tail = (const uint8_t *)(data + nblocks * 16); + + for (auto i = 0; i < len % 16; i++) { + if (tail[i] > 127) { + isAsciiString = false; + break; + } + } + + uint64_t k1 = 0; + uint64_t k2 = 0; + + switch (len & 15) { + case 15: + k2 ^= ((uint64_t)tail[14]) << 48; + case 14: + k2 ^= ((uint64_t)tail[13]) << 40; + case 13: + k2 ^= ((uint64_t)tail[12]) << 32; + case 12: + k2 ^= ((uint64_t)tail[11]) << 24; + case 11: + k2 ^= ((uint64_t)tail[10]) << 16; + case 10: + k2 ^= ((uint64_t)tail[9]) << 8; + case 9: + k2 ^= ((uint64_t)tail[8]) << 0; + k2 *= c2; + k2 = ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + case 8: + k1 ^= ((uint64_t)tail[7]) << 56; + case 7: + k1 ^= ((uint64_t)tail[6]) << 48; + case 6: + k1 ^= ((uint64_t)tail[5]) << 40; + case 5: + k1 ^= ((uint64_t)tail[4]) << 32; + case 4: + k1 ^= ((uint64_t)tail[3]) << 24; + case 3: + k1 ^= ((uint64_t)tail[2]) << 16; + case 2: + k1 ^= ((uint64_t)tail[1]) << 8; + case 1: + k1 ^= ((uint64_t)tail[0]) << 0; + k1 *= c1; + k1 = ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; + h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + ((uint64_t *)out)[0] = h1; + ((uint64_t *)out)[1] = h2; + + return isAsciiString; +} + +bool murmurhash(const uint8_t *key, size_t length, uint64_t &hash) { + uint64_t hashes[2]; + + bool isAscii = MurmurHash3_x64_128(key, length, 31, &hashes); + + hash = hashes[0]; + + return isAscii; +} \ No newline at end of file diff --git a/API/hermes/MurmurHash.h b/API/hermes/MurmurHash.h new file mode 100644 index 00000000000..33fc7660cb0 --- /dev/null +++ b/API/hermes/MurmurHash.h @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +#pragma once + +#include +#include + +// Computes the hash of key using MurmurHash3 algorithm, the value is planced in the "hash" output parameter +// The function returns whether or not key is comprised of only ASCII characters (<=127) +bool murmurhash(const uint8_t *key, size_t length, uint64_t &hash); \ No newline at end of file diff --git a/API/hermes/ScriptStore.h b/API/hermes/ScriptStore.h new file mode 100644 index 00000000000..25d8a12e1fd --- /dev/null +++ b/API/hermes/ScriptStore.h @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +#pragma once + +#include +#include + +namespace facebook { +namespace jsi { + +// Integer type as it's persist friendly. +using ScriptVersion_t = uint64_t; // It should be std::optional once we have c++17 available everywhere. Until + // then, 0 implies versioning not available. +using JSRuntimeVersion_t = uint64_t; // 0 implies version can't be computed. We assert whenever that happens. + +struct VersionedBuffer { + std::shared_ptr buffer; + ScriptVersion_t version; +}; + +struct ScriptSignature { + std::string url; + ScriptVersion_t version; +}; + +struct JSRuntimeSignature { + std::string runtimeName; // e.g. Chakra, V8 + JSRuntimeVersion_t version; +}; + +// Most JSI::Runtime implementation offer some form of prepared JavaScript which offers better performance +// characteristics when loading comparing to plain JavaScript. Embedders can provide an instance of this interface +// (through JSI::Runtime implementation's factory method), to enable persistance of the prepared script and retrieval on +// subsequent evaluation of a script. +struct PreparedScriptStore { + virtual ~PreparedScriptStore() = default; + + // Try to retrieve the prepared javascript for a given combination of script & runtime. + // scriptSignature : Javascript url and version + // RuntimeSignature : Javascript engine type and version + // prepareTag : Custom tag to uniquely identify JS engine specific preparation schemes. It is usually useful while + // experimentation and can be null. It is possible that no prepared script is available for a given script & runtime + // signature. This method should null if so + virtual std::shared_ptr tryGetPreparedScript( + const ScriptSignature &scriptSignature, + const JSRuntimeSignature &runtimeSignature, + const char *prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. + ) noexcept = 0; + + // Persist the prepared javascript for a given combination of script & runtime. + // scriptSignature : Javascript url and version + // RuntimeSignature : Javascript engine type and version + // prepareTag : Custom tag to uniquely identify JS engine specific preparation schemes. It is usually useful while + // experimentation and can be null. It is possible that no prepared script is available for a given script & runtime + // signature. This method should null if so Any failure in persistance should be identified during the subsequent + // retrieval through the integrity mechanism which must be put into the storage. + virtual void persistPreparedScript( + std::shared_ptr preparedScript, + const ScriptSignature &scriptMetadata, + const JSRuntimeSignature &runtimeMetadata, + const char *prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. + ) noexcept = 0; +}; + +// JSI::Runtime implementation must be provided an instance on this interface to enable version sensitive capabilities +// such as usage of pre-prepared javascript script. Alternatively, this entity can be used to directly provide the +// Javascript buffer and rich metadata to the JSI::Runtime instance. +struct ScriptStore { + virtual ~ScriptStore() = default; + + // Return the Javascript buffer and version corresponding to a given url. + virtual VersionedBuffer getVersionedScript(const std::string &url) noexcept = 0; + + // Return the version of the Javascript buffer corresponding to a given url. + virtual ScriptVersion_t getScriptVersion(const std::string &url) noexcept = 0; +}; + +} // namespace jsi +} // namespace facebook diff --git a/API/hermes/hermes_api.h b/API/hermes/hermes_api.h new file mode 100644 index 00000000000..b3e28a4236b --- /dev/null +++ b/API/hermes/hermes_api.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifndef HERMES_HERMES_API_H +#define HERMES_HERMES_API_H + +#include "js_runtime_api.h" + +EXTERN_C_START + +typedef struct hermes_local_connection_s *hermes_local_connection; +typedef struct hermes_remote_connection_s *hermes_remote_connection; + +//============================================================================= +// jsr_runtime +//============================================================================= + +JSR_API hermes_dump_crash_data(jsr_runtime runtime, int32_t fd); +JSR_API hermes_sampling_profiler_enable(); +JSR_API hermes_sampling_profiler_disable(); +JSR_API hermes_sampling_profiler_add(jsr_runtime runtime); +JSR_API hermes_sampling_profiler_remove(jsr_runtime runtime); +JSR_API hermes_sampling_profiler_dump_to_file(const char *filename); + +//============================================================================= +// jsr_config +//============================================================================= + +JSR_API hermes_config_enable_default_crash_handler( + jsr_config config, + bool value); + +//============================================================================= +// Setting inspector singleton +//============================================================================= + +typedef int32_t(NAPI_CDECL *hermes_inspector_add_page_cb)( + const char *title, + const char *vm, + void *connectFunc); + +typedef void(NAPI_CDECL *hermes_inspector_remove_page_cb)(int32_t page_id); + +JSR_API hermes_set_inspector( + hermes_inspector_add_page_cb add_page_cb, + hermes_inspector_remove_page_cb remove_page_cb); + +//============================================================================= +// Local and remote inspector connections. +// Local is defined in Hermes VM, Remote is defined by inspector outside of VM. +//============================================================================= + +typedef void(NAPI_CDECL *hermes_remote_connection_send_message_cb)( + hermes_remote_connection remote_connection, + const char *message); + +typedef void(NAPI_CDECL *hermes_remote_connection_disconnect_cb)( + hermes_remote_connection remote_connection); + +JSR_API hermes_create_local_connection( + void *connect_func, + hermes_remote_connection remote_connection, + hermes_remote_connection_send_message_cb on_send_message_cb, + hermes_remote_connection_disconnect_cb on_disconnect_cb, + jsr_data_delete_cb on_delete_cb, + void *deleter_data, + hermes_local_connection *local_connection); + +JSR_API hermes_delete_local_connection( + hermes_local_connection local_connection); + +JSR_API hermes_local_connection_send_message( + hermes_local_connection local_connection, + const char *message); + +JSR_API hermes_local_connection_disconnect( + hermes_local_connection local_connection); + +EXTERN_C_END + +#endif // !HERMES_HERMES_API_H diff --git a/API/hermes/hermes_napi.cpp b/API/hermes/hermes_napi.cpp new file mode 100644 index 00000000000..8015f72198a --- /dev/null +++ b/API/hermes/hermes_napi.cpp @@ -0,0 +1,7891 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * + * Copyright notices for portions of code adapted from Hermes, Node.js, and V8 + * projects: + * + * Copyright (c) Facebook, Inc. and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * Copyright Node.js contributors. All rights reserved. + * https://github.com/nodejs/node/blob/master/LICENSE + * + * Copyright 2011 the V8 project authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * https://github.com/v8/v8/blob/main/LICENSE + */ + +// +// Implementation of Node-API for Hermes engine. +// +// The Node-API C functions are redirecting all calls to the NapiEnvironment +// class which implements the API details. +// The most notable parts of the implementation are: +// - The NapiEnvironment class is ref-counted. +// - It maintains local stack-based GC roots as napiValueStack_. +// - The napiValueStackScopes_ is used to control napiValueStack_ handle +// scopes. +// - The napiValueStack_ and napiValueStackScopes_ are instances of +// NapiStableAddressStack to maintain stable address of returned napi_value +// and handle scopes. +// - napi_value is a pointer to the vm::PinnedHermesValue stored in +// napiValueStack_. +// - The heap-based GC roots are in the references_ and finalizingReferences_. +// - references_ vs finalizingReferences_ is chosen based on whether the root +// needs +// finalizer call or not. +// - references_ and finalizingReferences_ are double-linked list. +// - All heap-based GC roots are stored as references - instances of classes +// derived from NapiReference class. There are many varieties of that class +// to accommodate different lifetime strategies and to optimize storage +// size. +// - napi_ref and napi_ext_ref are pointers to references_ and +// finalizingReferences_ +// items. +// - NapiReference finalizers are run in JS thread by processFinalizerQueue +// method which is called by NapiHandleScope::setResult. +// - Each returned error status is backed up by the extended error message +// stored in lastError_ that can be retrieved by napi_get_last_error_info. +// - We use macros to handle error statuses. It is done to reduce extensive use +// of "if-return" statements, and to report failing expressions along with the +// file name and code line number. + +// TODO: Allow DebugBreak in unexpected cases - add functions to indicate +// expected errors +// TODO: Create NapiEnvironment with JSI Runtime +// TODO: Fix Inspector CMake definitions + +// TODO: Cannot use functions as a base class +// TODO: NativeFunction vs NativeConstructor +// TODO: Different error messages +// TODO: Arrays with 2^32-1 elements (sparse arrays?) +// TODO: How to provide detailed error messages without breaking tests? +// TODO: Why console.log compiles in V8_JSI? + +#define NAPI_VERSION 8 +#define NAPI_EXPERIMENTAL + +#include "MurmurHash.h" +#include "ScriptStore.h" +#include "hermes_api.h" + +#include "hermes/BCGen/HBC/BytecodeProviderFromSrc.h" +#include "hermes/DebuggerAPI.h" +#include "hermes/SourceMap/SourceMapParser.h" +#include "hermes/Support/SimpleDiagHandler.h" +#include "hermes/VM/Callable.h" +#include "hermes/VM/HostModel.h" +#include "hermes/VM/JSArray.h" +#include "hermes/VM/JSArrayBuffer.h" +#include "hermes/VM/JSDataView.h" +#include "hermes/VM/JSDate.h" +#include "hermes/VM/JSError.h" +#include "hermes/VM/JSProxy.h" +#include "hermes/VM/JSTypedArray.h" +#include "hermes/VM/PropertyAccessor.h" +#include "hermes/VM/Runtime.h" +#include "llvh/ADT/SmallVector.h" +#include "llvh/Support/ConvertUTF.h" + +#include +#include +#include + +//============================================================================= +// Macros +//============================================================================= + +// Check the NAPI status and return it if it is not napi_ok. +#define CHECK_NAPI(...) \ + do { \ + if (napi_status status__ = (__VA_ARGS__)) { \ + return status__; \ + } \ + } while (false) + +// Crash if the condition is false. +#define CRASH_IF_FALSE(condition) \ + do { \ + if (!(condition)) { \ + assert(false && #condition); \ + __builtin_trap(); \ + std::terminate(); \ + } \ + } while (false) + +// Return error status with message. +#define ERROR_STATUS(status, ...) \ + env.setLastNativeError( \ + (status), (__FILE__), (uint32_t)(__LINE__), __VA_ARGS__) + +// Return napi_generic_failure with message. +#define GENERIC_FAILURE(...) ERROR_STATUS(napi_generic_failure, __VA_ARGS__) + +// Cast env to NapiEnvironment if it is not null. +#define CHECKED_ENV(env) \ + ((env) == nullptr) ? napi_invalid_arg \ + : reinterpret_cast(env) + +// Check env and return error status with message. +#define CHECKED_ENV_ERROR_STATUS(env, status, ...) \ + ((env) == nullptr) \ + ? napi_invalid_arg \ + : reinterpret_cast(env) \ + ->setLastNativeError( \ + (status), (__FILE__), (uint32_t)(__LINE__), __VA_ARGS__) + +// Check env and return napi_generic_failure with message. +#define CHECKED_ENV_GENERIC_FAILURE(env, ...) \ + CHECKED_ENV_ERROR_STATUS(env, napi_generic_failure, __VA_ARGS__) + +// Check conditions and return error status with message if it is false. +#define RETURN_STATUS_IF_FALSE_WITH_MESSAGE(condition, status, ...) \ + do { \ + if (!(condition)) { \ + return env.setLastNativeError( \ + (status), (__FILE__), (uint32_t)(__LINE__), __VA_ARGS__); \ + } \ + } while (false) + +// Check conditions and return error status if it is false. +#define RETURN_STATUS_IF_FALSE(condition, status) \ + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( \ + (condition), (status), "Condition is false: " #condition) + +// Check conditions and return napi_generic_failure if it is false. +#define RETURN_FAILURE_IF_FALSE(condition) \ + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( \ + (condition), napi_generic_failure, "Condition is false: " #condition) + +// Check that the argument is not nullptr. +#define CHECK_ARG(arg) \ + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( \ + (arg) != nullptr, napi_invalid_arg, "Argument is null: " #arg) + +// Check that the argument is of Object or Function type. +#define CHECK_OBJECT_ARG(arg) \ + do { \ + CHECK_ARG(arg); \ + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( \ + phv(arg)->isObject(), \ + napi_object_expected, \ + "Argument is not an Object: " #arg); \ + } while (false) + +// Check that the argument is of String type. +#define CHECK_STRING_ARG(arg) \ + do { \ + CHECK_ARG(arg); \ + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( \ + phv(arg)->isString(), \ + napi_string_expected, \ + "Argument is not a String: " #arg); \ + } while (false) + +#define RAISE_ERROR_IF_FALSE(condition, message) \ + do { \ + if (!(condition)) { \ + return runtime.raiseTypeError(message " Condition: " u## #condition); \ + } \ + } while (false) + +#if defined(_WIN32) && !defined(NDEBUG) +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +#endif + +namespace hermes { +namespace napi { + +union HermesBuildVersionInfo { + struct { + uint16_t major; + uint16_t minor; + uint16_t patch; + uint16_t revision; + }; + uint64_t version; +}; + +#ifndef HERMESVM_LEAN +// TODO: [vmoroz] Fix it +// constexpr HermesBuildVersionInfo HermesBuildVersion = +// {HERMES_FILE_VERSION_BIN}; +constexpr HermesBuildVersionInfo HermesBuildVersion = {{0, 0, 0, 1}}; +#endif + +//============================================================================= +// Forward declaration of all classes. +//============================================================================= + +class NapiCallbackInfo; +class NapiDoubleConversion; +class NapiEnvironment; +class NapiExternalBuffer; +class NapiExternalValue; +class NapiHandleScope; +class NapiHostFunctionContext; +template +class NapiOrderedSet; +class NapiPendingFinalizers; +template +class NapiRefCountedPtr; +class NapiScriptModel; +template +class NapiStableAddressStack; +class NapiStringBuilder; + +// Forward declaration of NapiReference-related classes. +class NapiAtomicRefCountReference; +class NapiComplexReference; +template +class NapiFinalizeCallbackHolder; +template +class NapiFinalizeHintHolder; +class NapiFinalizer; +class NapiFinalizingAnonymousReference; +class NapiFinalizingComplexReference; +template +class NapiFinalizingReference; +template +class NapiFinalizingReferenceFactory; +class NapiFinalizingStrongReference; +class NapiInstanceData; +template +class NapiLinkedList; +template +class NapiNativeDataHolder; +class NapiReference; +class NapiStrongReference; +class NapiWeakReference; + +using NapiNativeError = napi_extended_error_info; + +//============================================================================= +// Enums +//============================================================================= + +// Controls behavior of NapiEnvironment::unwrapObject. +enum class NapiUnwrapAction { KeepWrap, RemoveWrap }; + +// Predefined values used by NapiEnvironment. +enum class NapiPredefined { + Promise, + allRejections, + code, + hostFunction, + napi_externalValue, + napi_typeTag, + onHandled, + onUnhandled, + reject, + resolve, + PredefinedCount // a special value that must be last in the enum +}; + +// The action to take when an external value is not found. +enum class NapiIfNotFound { + ThenCreate, + ThenReturnNull, +}; + +//============================================================================= +// Forward declaration of standalone functions. +//============================================================================= + +// Size of an array - it must be replaced by std::size after switching to C++17 +template +constexpr std::size_t size(const T (&array)[N]) noexcept; + +// Check if the enum value is in the provided range. +template +bool isInEnumRange( + TEnum value, + TEnum lowerBoundInclusive, + TEnum upperBoundInclusive) noexcept; + +// Reinterpret cast NapiEnvironment to napi_env +napi_env napiEnv(NapiEnvironment *env) noexcept; + +// Reinterpret cast vm::PinnedHermesValue pointer to napi_value +napi_value napiValue(const vm::PinnedHermesValue *value) noexcept; + +// Get underlying vm::PinnedHermesValue and reinterpret cast it napi_value +template +napi_value napiValue(vm::Handle value) noexcept; + +// Reinterpret cast napi_value to vm::PinnedHermesValue pointer +const vm::PinnedHermesValue *phv(napi_value value) noexcept; +// Useful in templates and macros +const vm::PinnedHermesValue *phv(const vm::PinnedHermesValue *value) noexcept; + +// Reinterpret cast napi_ref to NapiReference pointer +NapiReference *asReference(napi_ref ref) noexcept; +// Reinterpret cast void* to NapiReference pointer +NapiReference *asReference(void *ref) noexcept; + +// Reinterpret cast to NapiHostFunctionContext::NapiCallbackInfo +NapiCallbackInfo *asCallbackInfo(napi_callback_info callbackInfo) noexcept; + +// Get object from HermesValue and cast it to JSObject +vm::JSObject *getObjectUnsafe(const vm::HermesValue &value) noexcept; + +// Get object from napi_value and cast it to JSObject +vm::JSObject *getObjectUnsafe(napi_value value) noexcept; + +// Copy ASCII input to UTF8 buffer. It is a convenience function to match the +// convertUTF16ToUTF8WithReplacements signature when using std::copy. +size_t copyASCIIToUTF8( + llvh::ArrayRef input, + char *buf, + size_t maxCharacters) noexcept; + +// Return length of UTF-8 string after converting a UTF-16 encoded string \p +// input to UTF-8, replacing unpaired surrogates halves with the Unicode +// replacement character. The length does not include the terminating '\0' +// character. +size_t utf8LengthWithReplacements(llvh::ArrayRef input); + +// Convert a UTF-16 encoded string \p input to UTF-8 stored in \p buf, +// replacing unpaired surrogates halves with the Unicode replacement character. +// The terminating '\0' is not written. +// \return number of bytes written to \p buf. +size_t convertUTF16ToUTF8WithReplacements( + llvh::ArrayRef input, + char *buf, + size_t bufSize); + +//============================================================================= +// Definitions of classes and structs. +//============================================================================= + +struct NapiAttachTag { +} attachTag; + +// A smart pointer for types that implement intrusive ref count using +// methods incRefCount and decRefCount. +template +class NapiRefCountedPtr final { + public: + NapiRefCountedPtr() noexcept = default; + + explicit NapiRefCountedPtr(T *ptr, NapiAttachTag) noexcept : ptr_(ptr) {} + + NapiRefCountedPtr(const NapiRefCountedPtr &other) noexcept + : ptr_(other.ptr_) { + if (ptr_ != nullptr) { + ptr_->incRefCount(); + } + } + + NapiRefCountedPtr(NapiRefCountedPtr &&other) + : ptr_(std::exchange(other.ptr_, nullptr)) {} + + ~NapiRefCountedPtr() noexcept { + if (ptr_ != nullptr) { + ptr_->decRefCount(); + } + } + + NapiRefCountedPtr &operator=(std::nullptr_t) noexcept { + if (ptr_ != nullptr) { + ptr_->decRefCount(); + } + ptr_ = nullptr; + return *this; + } + + NapiRefCountedPtr &operator=(const NapiRefCountedPtr &other) noexcept { + if (this != &other) { + NapiRefCountedPtr temp(std::move(*this)); + ptr_ = other.ptr_; + if (ptr_ != nullptr) { + ptr_->incRefCount(); + } + } + return *this; + } + + NapiRefCountedPtr &operator=(NapiRefCountedPtr &&other) noexcept { + if (this != &other) { + NapiRefCountedPtr temp(std::move(*this)); + ptr_ = std::exchange(other.ptr_, nullptr); + } + return *this; + } + + T *operator->() noexcept { + return ptr_; + } + + private: + T *ptr_{}; +}; + +// Stack of elements where the address of items is not changed as we add new +// values. It is achieved by keeping a SmallVector of the ChunkSize arrays +// called chunks. We use it to keep addresses of GC roots associated with the +// call stack and the related handle scopes. The GC roots are the +// vm::PinnedHermesValue instances. Considering our use case, we do not call the +// destructors for items and require that T has a trivial destructor. +template +class NapiStableAddressStack final { + static_assert( + std::is_trivially_destructible_v, + "T must be trivially destructible."); + + public: + NapiStableAddressStack() noexcept { + // There is always at least one chunk in the storage + storage_.emplace_back(new T[ChunkSize]); + } + + template + void emplace(TArgs &&...args) noexcept { + size_t newIndex = size_; + size_t chunkIndex = newIndex / ChunkSize; + size_t chunkOffset = newIndex % ChunkSize; + if (chunkOffset == 0 && chunkIndex == storage_.size()) { + storage_.emplace_back(new T[ChunkSize]); + } + new (std::addressof(storage_[chunkIndex][chunkOffset])) + T(std::forward(args)...); + ++size_; + } + + void pop() noexcept { + CRASH_IF_FALSE(size_ > 0 && "Size must be non zero."); + --size_; + reduceChunkCount(); + } + + void resize(size_t newSize) noexcept { + CRASH_IF_FALSE(newSize <= size_ && "Size cannot be increased by resizing."); + if (newSize < size_) { + size_ = newSize; + reduceChunkCount(); + } + } + + size_t size() const noexcept { + return size_; + } + + bool empty() const noexcept { + return size_ == 0; + } + + T &top() noexcept { + CRASH_IF_FALSE(size_ > 0 && "Size must be non zero."); + size_t lastIndex = size_ - 1; + return storage_[lastIndex / ChunkSize][lastIndex % ChunkSize]; + } + + T &operator[](size_t index) noexcept { + CRASH_IF_FALSE(index < size_ && "Index must be less than size."); + return storage_[index / ChunkSize][index % ChunkSize]; + } + + template + void forEach(const F &f) noexcept { + size_t remaining = size_; + for (std::unique_ptr &chunk : storage_) { + size_t chunkSize = std::min(ChunkSize, remaining); + for (size_t i = 0; i < chunkSize; ++i) { + f(chunk[i]); + } + remaining -= chunkSize; + } + } + + private: + void reduceChunkCount() noexcept { + // There must be at least one chunk. + // To reduce number of allocations/deallocations the last chunk must be half + // full before we delete the next empty chunk. + size_t requiredChunkCount = std::max( + 1, (size_ + ChunkSize / 2 + ChunkSize - 1) / ChunkSize); + if (requiredChunkCount < storage_.size()) { + storage_.resize(requiredChunkCount); + } + } + + private: + // The size of 64 entries per chunk is arbitrary at this point. + // It can be adjusted depending on perf data. + static const size_t ChunkSize = 64; + + llvh::SmallVector, ChunkSize> storage_; + size_t size_{0}; +}; + +// An intrusive double linked list of items. +// Items in the list must inherit from NapiLinkedList::Item. +// We use it instead of std::list to allow item to delete itself in its +// destructor and conveniently move items from list to another. The +// NapiLinkedList is used for References - the GC roots that are allocated in +// heap. The GC roots are the vm::PinnedHermesValue instances. +template +class NapiLinkedList final { + public: + NapiLinkedList() noexcept { + // The list is circular: + // head.next_ points to the first item + // head.prev_ points to the last item + head_.next_ = &head_; + head_.prev_ = &head_; + } + + class Item { + public: + void linkNext(T *item) noexcept { + if (item->isLinked()) { + item->unlink(); + } + item->prev_ = this; + item->next_ = next_; + item->next_->prev_ = item; + next_ = item; + } + + void unlink() noexcept { + if (isLinked()) { + prev_->next_ = next_; + next_->prev_ = prev_; + prev_ = nullptr; + next_ = nullptr; + } + } + + bool isLinked() const noexcept { + return prev_ != nullptr; + } + + friend NapiLinkedList; + + private: + Item *next_{}; + Item *prev_{}; + }; + + void pushFront(T *item) noexcept { + head_.linkNext(item); + } + + void pushBack(T *item) noexcept { + head_.prev_->linkNext(item); + } + + T *begin() noexcept { + return static_cast(head_.next_); + } + + // The end() returns a pointer to an invalid object. + T *end() noexcept { + return static_cast(&head_); + } + + bool isEmpty() noexcept { + return head_.next_ == head_.prev_; + } + + template + void forEach(TLambda lambda) noexcept { + for (T *item = begin(); item != end();) { + // lambda can delete the item - get the next one before calling it. + T *nextItem = static_cast(item->next_); + lambda(item); + item = nextItem; + } + } + + private: + Item head_; +}; + +// The main class representing the NAPI environment. +// All NAPI functions are calling methods from this class. +class NapiEnvironment final { + public: + // Initializes a new instance of NapiEnvironment. + explicit NapiEnvironment( + vm::Runtime &runtime, + bool isInspectable, + std::shared_ptr preparedScript, + const vm::RuntimeConfig &runtimeConfig = {}) noexcept; + + private: + // Only the internal ref count can call the destructor. + ~NapiEnvironment(); + + public: + // Exported function to increment the ref count by one. + napi_status incRefCount() noexcept; + + // Exported function to decrement the ref count by one. + // When the ref count becomes zero, the environment is deleted. + napi_status decRefCount() noexcept; + + // Internal function to get the Hermes runtime. + vm::Runtime &runtime() noexcept; + + // Internal function to get the stack of napi_value. + NapiStableAddressStack &napiValueStack() noexcept; + + //--------------------------------------------------------------------------- + // Native error handling methods + //--------------------------------------------------------------------------- + public: + // Exported function to get the last native error. + napi_status getLastNativeError(const NapiNativeError **result) noexcept; + + // Internal function to se the last native error. + template + napi_status setLastNativeError( + napi_status status, + const char *fileName, + uint32_t line, + TArgs &&...args) noexcept; + + // Internal function to clear the last error function. + napi_status clearLastNativeError() noexcept; + + //----------------------------------------------------------------------------- + // Methods to support JS error handling + //----------------------------------------------------------------------------- + public: + // Internal function to create JS error with the specified prototype. + napi_status createJSError( + const vm::PinnedHermesValue &errorPrototype, + napi_value code, + napi_value message, + napi_value *result) noexcept; + + // Exported function to create JS Error object. + napi_status createJSError( + napi_value code, + napi_value message, + napi_value *result) noexcept; + + // Exported function to create JS TypeError object. + napi_status createJSTypeError( + napi_value code, + napi_value message, + napi_value *result) noexcept; + + // Exported function to create JS RangeError object. + napi_status createJSRangeError( + napi_value code, + napi_value message, + napi_value *result) noexcept; + + // Exported function to check if the object is an instance of Error object. + napi_status isJSError(napi_value value, bool *result) noexcept; + + // Exported function to throw provided error value. + napi_status throwJSError(napi_value error) noexcept; + + // Internal function to create and throw JS error object with the specified + // prototype. + napi_status throwJSError( + const vm::PinnedHermesValue &prototype, + const char *code, + const char *message) noexcept; + + // Exported function to create and throw JS Error object. + napi_status throwJSError(const char *code, const char *message) noexcept; + + // Exported function to create and throw JS TypeError object. + napi_status throwJSTypeError(const char *code, const char *message) noexcept; + + // Exported function to create and throw JS RangeError object. + napi_status throwJSRangeError(const char *code, const char *message) noexcept; + + // Internal function to set code property for the error object. + // Node.js has a predefined set of codes for common errors. + napi_status setJSErrorCode( + vm::Handle error, + napi_value code, + const char *codeCString) noexcept; + + //----------------------------------------------------------------------------- + // Methods to support catching JS exceptions + //----------------------------------------------------------------------------- + public: + // Exported function to check if there is a pending thrown JS error. + napi_status isJSErrorPending(bool *result) noexcept; + + // Internal function to check if there is a pending thrown JS error. + // It returns napi_ok or napi_pending_exception. + napi_status checkPendingJSError() noexcept; + + // Exported function to get and clear pending thrown JS error. + napi_status getAndClearPendingJSError(napi_value *result) noexcept; + + // Internal function to check ExecutionStatus and get the thrown JS error. + napi_status checkJSErrorStatus( + vm::ExecutionStatus hermesStatus, + napi_status status = napi_generic_failure) noexcept; + + // Internal function to check ExecutionStatus of callResult and get the thrown + // JS error. + template + napi_status checkJSErrorStatus( + const vm::CallResult &callResult, + napi_status status = napi_generic_failure) noexcept; + + //----------------------------------------------------------------------------- + // Getters for common singletons + //----------------------------------------------------------------------------- + public: + // Exported function to get the `global` object. + napi_status getGlobal(napi_value *result) noexcept; + + // Exported function to get the `undefined` value. + napi_status getUndefined(napi_value *result) noexcept; + + // Internal function to get the `undefined` value. + const vm::PinnedHermesValue &getUndefined() noexcept; + + // Exported function to get the `null` value. + napi_status getNull(napi_value *result) noexcept; + + //----------------------------------------------------------------------------- + // Method to get value type + //----------------------------------------------------------------------------- + public: + // Exported function to get JS type of object. + napi_status typeOf(napi_value value, napi_valuetype *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Booleans + //----------------------------------------------------------------------------- + public: + // Exported function to get napi_value for `true` or `false`. + napi_status getBoolean(bool value, napi_value *result) noexcept; + + // Exported function to get value of a Boolean value. + napi_status getBooleanValue(napi_value value, bool *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Numbers + //----------------------------------------------------------------------------- + public: + // Exported function to create napi_value for a number. + template , bool> = true> + napi_status createNumber(T value, napi_value *result) noexcept; + + // Exported function to get `double` value from a napi_value number. + napi_status getNumberValue(napi_value value, double *result) noexcept; + + // Exported function to get `int32_t` value from a napi_value number. + napi_status getNumberValue(napi_value value, int32_t *result) noexcept; + + // Exported function to get `uint32_t` value from a napi_value number. + napi_status getNumberValue(napi_value value, uint32_t *result) noexcept; + + // Exported function to get `int64_t` value from a napi_value number. + napi_status getNumberValue(napi_value value, int64_t *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Strings + //----------------------------------------------------------------------------- + public: + // Internal function to create napi_value from an ASCII string. + napi_status createStringASCII( + const char *str, + size_t length, + napi_value *result) noexcept; + + // Exported function to create napi_value from an Latin1 string. + // The Latin1 is the one-byte encoding that can be converted to UTF-16 by an + // unsigned expansion of each characted to 16 bit. + napi_status createStringLatin1( + const char *str, + size_t length, + napi_value *result) noexcept; + + // Exported function to create napi_value from an UTF-8 string. + napi_status + createStringUTF8(const char *str, size_t length, napi_value *result) noexcept; + + // Internal function to create napi_value from an UTF-8 string. + // The str must be zero-terminated. + napi_status createStringUTF8(const char *str, napi_value *result) noexcept; + + // Exported function to create napi_value from an UTF-16 string. + napi_status createStringUTF16( + const char16_t *str, + size_t length, + napi_value *result) noexcept; + + // Exported function to get Latin1 string value from a napi_value string. + napi_status getStringValueLatin1( + napi_value value, + char *buf, + size_t bufSize, + size_t *result) noexcept; + + // Exported function to get UTF-8 string value from a napi_value string. + napi_status getStringValueUTF8( + napi_value value, + char *buf, + size_t bufSize, + size_t *result) noexcept; + + // Exported function to get UTF-16 string value from a napi_value string. + napi_status getStringValueUTF16( + napi_value value, + char16_t *buf, + size_t bufSize, + size_t *result) noexcept; + + // Internal function to convert UTF-8 stirng to UTF-16. + napi_status convertUTF8ToUTF16( + const char *utf8, + size_t length, + std::u16string &out) noexcept; + + // Internal function to get or create unique UTF-8 string SymbolID. + // Note that unique SymbolID is used by Hermes for string-based identifiers, + // and non-unique SymbolID is for the JS Symbols. + napi_status getUniqueSymbolID( + const char *utf8, + size_t length, + vm::MutableHandle *result) noexcept; + + // Internal function to create unique UTF-8 string SymbolID. + napi_status getUniqueSymbolID( + napi_value strValue, + vm::MutableHandle *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Symbols + //----------------------------------------------------------------------------- + public: + // Exported function to create a JS symbol object. + napi_status createSymbol(napi_value description, napi_value *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with BigInt + //----------------------------------------------------------------------------- + public: + napi_status createBigIntFromInt64(int64_t value, napi_value *result); + + napi_status createBigIntFromUint64(uint64_t value, napi_value *result); + + napi_status createBigIntFromWords( + int signBit, + size_t wordCount, + const uint64_t *words, + napi_value *result); + + napi_status + getBigIntValueInt64(napi_value value, int64_t *result, bool *lossless); + + napi_status + getBigIntValueUint64(napi_value value, uint64_t *result, bool *lossless); + + napi_status getBigIntValueWords( + napi_value value, + int *signBit, + size_t *wordCount, + uint64_t *words); + + //----------------------------------------------------------------------------- + // Methods to coerce values using JS coercion rules + //----------------------------------------------------------------------------- + public: + // Exported function to coerce napi_value to a Boolean primitive value. + napi_status coerceToBoolean(napi_value value, napi_value *result) noexcept; + + // Exported function to coerce napi_value to a Number primitive value. + napi_status coerceToNumber(napi_value value, napi_value *result) noexcept; + + // Exported function to coerce napi_value to a String primitive value. + napi_status coerceToString(napi_value value, napi_value *result) noexcept; + + // Exported function to coerce napi_value to an Object. + napi_status coerceToObject(napi_value value, napi_value *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Objects + //----------------------------------------------------------------------------- + public: + // Exported function to create a new Object instance. + napi_status createObject(napi_value *result) noexcept; + + // Exported function to get object's prototype. + napi_status getPrototype(napi_value object, napi_value *result) noexcept; + + // Exported function to get all enumerable string property names as in the + // for..in statement. All indexes are converted to strings. + napi_status getForInPropertyNames( + napi_value object, + napi_value *result) noexcept; + + // Internal function to get all enumerable string property names as in the + // for..in statement. + // The keyConversion specifies if index properties must be converted to + // strings. The function wraps up the Hermes optimized function that caches + // results. + napi_status getForInPropertyNames( + napi_value object, + napi_key_conversion keyConversion, + napi_value *result) noexcept; + + // Exported function to get all property names depending on the criteria. + // The keyMode specifies whether to return only own properties or traverse the + // prototype hierarchy. The keyFilter specifies whether to return enumerable, + // writable, configurable, or all properties. The keyConversion specifies + // whether to convert indexes to strings. + napi_status getAllPropertyNames( + napi_value object, + napi_key_collection_mode keyMode, + napi_key_filter keyFilter, + napi_key_conversion keyConversion, + napi_value *result) noexcept; + + // Internal function to convert temporary key storage represented by a + // array-builder-like BigStorage to a JS Array. + napi_status convertKeyStorageToArray( + vm::Handle keyStorage, + uint32_t startIndex, + uint32_t length, + napi_key_conversion keyConversion, + napi_value *result) noexcept; + + // Internal function to convert all array elements to strings. + // We use it to convert property keys represented as uint32 indexes. + napi_status convertToStringKeys(vm::Handle array) noexcept; + + // Internal function to convert index value to a string. + napi_status convertIndexToString( + double value, + vm::MutableHandle<> *result) noexcept; + + // Exported function to check if object has the property. + napi_status + hasProperty(napi_value object, napi_value key, bool *result) noexcept; + + // Exported function to get property value. + napi_status + getProperty(napi_value object, napi_value key, napi_value *result) noexcept; + + // Exported function to set property value. + napi_status + setProperty(napi_value object, napi_value key, napi_value value) noexcept; + + // Exported function to delete property value. + napi_status + deleteProperty(napi_value object, napi_value key, bool *result) noexcept; + + // Exported function to check if object has the own property. + napi_status + hasOwnProperty(napi_value object, napi_value key, bool *result) noexcept; + + // Exported function to check if object has a property with property name as a + // string. + napi_status hasNamedProperty( + napi_value object, + const char *utf8Name, + bool *result) noexcept; + + // Exported function to get property value with property name as a string. + napi_status getNamedProperty( + napi_value object, + const char *utf8Name, + napi_value *result) noexcept; + + // Exported function to set property value with property name as a string. + napi_status setNamedProperty( + napi_value object, + const char *utf8Name, + napi_value value) noexcept; + + // Exported function to define a set of properties. + napi_status defineProperties( + napi_value object, + size_t propertyCount, + const napi_property_descriptor *properties) noexcept; + + // Internal function to get SymbolID representing property identifier from the + // property descriptor. + napi_status symbolIDFromPropertyDescriptor( + const napi_property_descriptor *descriptor, + vm::MutableHandle *result) noexcept; + + // Exported function to freeze the object. + // The frozen object is an immutable object. + napi_status objectFreeze(napi_value object) noexcept; + + // Exported function to seal the object. + // The sealed object cannot change number of its properties, but any writable + // property value can be changed. + napi_status objectSeal(napi_value object) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Arrays + //----------------------------------------------------------------------------- + public: + // Exported function to create Array object instance. + napi_status createArray(size_t length, napi_value *result) noexcept; + + // Exported function to check if value is an Array object instance. + napi_status isArray(napi_value value, bool *result) noexcept; + + // Exported function to get Array length. + napi_status getArrayLength(napi_value value, uint32_t *result) noexcept; + + // Exported function to check if Array or Object has an element at specified + // index. + napi_status + hasElement(napi_value object, uint32_t index, bool *result) noexcept; + + // Exported function to get Array or Object element by index. + napi_status + getElement(napi_value object, uint32_t index, napi_value *result) noexcept; + + // Exported function to set Array or Object element by index. + napi_status + setElement(napi_value object, uint32_t index, napi_value value) noexcept; + + // Exported function to delete Array or Object element by index. + napi_status + deleteElement(napi_value object, uint32_t index, bool *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Functions + //----------------------------------------------------------------------------- + public: + // Exported function to create JS Function for a native callback. + napi_status createFunction( + const char *utf8Name, + size_t length, + napi_callback callback, + void *callbackData, + napi_value *result) noexcept; + + // Internal function to create JS Function for a native callback. + napi_status createFunction( + vm::SymbolID name, + napi_callback callback, + void *callbackData, + vm::MutableHandle *result) noexcept; + + // Exported function to call JS Function. + napi_status callFunction( + napi_value thisArg, + napi_value func, + size_t argCount, + const napi_value *args, + napi_value *result) noexcept; + + // Exported function to create a new object instance by calling the JS + // Function as a constructor. + napi_status createNewInstance( + napi_value constructor, + size_t argCount, + const napi_value *args, + napi_value *result) noexcept; + + // Exported function to check if the object was created by the specified + // constructor. + napi_status isInstanceOf( + napi_value object, + napi_value constructor, + bool *result) noexcept; + + // Internal function to call into a module. + template + vm::ExecutionStatus callIntoModule(TLambda &&call) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with napi_callbacks + //----------------------------------------------------------------------------- + public: + // Exported function to get callback info from inside of native callback. + napi_status getCallbackInfo( + napi_callback_info callbackInfo, + size_t *argCount, + napi_value *args, + napi_value *thisArg, + void **data) noexcept; + + // Exported function to get the new.target from inside of native callback. + napi_status getNewTarget( + napi_callback_info callbackInfo, + napi_value *result) noexcept; + + //--------------------------------------------------------------------------- + // Property access helpers + //--------------------------------------------------------------------------- + public: + // Internal function to get predefined value by key. + const vm::PinnedHermesValue &getPredefinedValue(NapiPredefined key) noexcept; + + // Internal function to get predefined value as a SymbolID. + vm::SymbolID getPredefinedSymbol(NapiPredefined key) noexcept; + + // Internal function to check if object has property by a predefined key. + template + napi_status hasPredefinedProperty( + TObject object, + NapiPredefined key, + bool *result) noexcept; + + // Internal function to get property value by a predefined key. + template + napi_status getPredefinedProperty( + TObject object, + NapiPredefined key, + napi_value *result) noexcept; + + // Internal function to set property value by predefined key. + template + napi_status setPredefinedProperty( + TObject object, + NapiPredefined key, + TValue &&value, + bool *optResult = nullptr) noexcept; + + // Internal function to check if object has a property with provided name. + template + napi_status + hasNamedProperty(TObject object, vm::SymbolID key, bool *result) noexcept; + + // Internal function to get property by name. + template + napi_status getNamedProperty( + TObject object, + vm::SymbolID key, + napi_value *result) noexcept; + + // Internal function to set property by name. + template + napi_status setNamedProperty( + TObject object, + vm::SymbolID key, + TValue &&value, + bool *optResult = nullptr) noexcept; + + // Internal function to check if property exists by key of any type. + template + napi_status + hasComputedProperty(TObject object, TKey key, bool *result) noexcept; + + // Internal function to get property value by key of any type. + template + napi_status + getComputedProperty(TObject object, TKey key, napi_value *result) noexcept; + + // Internal function to set property value by key of any type. + template + napi_status setComputedProperty( + TObject object, + TKey key, + TValue value, + bool *optResult = nullptr) noexcept; + + // Internal function to delete property by key of any type. + template + napi_status deleteComputedProperty( + TObject object, + TKey key, + bool *optResult = nullptr) noexcept; + + // Internal function to get own descriptor by key of any type. + template + napi_status getOwnComputedPropertyDescriptor( + TObject object, + TKey key, + vm::MutableHandle &tmpSymbolStorage, + vm::ComputedPropertyDescriptor &desc, + bool *result) noexcept; + + // Internal function to define a property. + template + napi_status defineOwnProperty( + TObject object, + vm::SymbolID name, + vm::DefinePropertyFlags dpFlags, + vm::Handle<> valueOrAccessor, + bool *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to compare values + //----------------------------------------------------------------------------- + public: + // Exported function to check if two values are equal without coercing them to + // the same type. It is equivalent to JS `===` operator. + napi_status + strictEquals(napi_value lhs, napi_value rhs, bool *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with external data objects + //----------------------------------------------------------------------------- + public: + // Exported function to define a JS class with instance and static members. + napi_status defineClass( + const char *utf8Name, + size_t length, + napi_callback constructor, + void *callbackData, + size_t propertyCount, + const napi_property_descriptor *properties, + napi_value *result) noexcept; + + // Exported function to wrap up native object instance into a JS object. + napi_status wrapObject( + napi_value object, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_ref *result) noexcept; + + // Exported function to associated a finalizer along with with a JS object. + napi_status addFinalizer( + napi_value object, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_ref *result) noexcept; + + // Exported function to get and/or remove native object from JS object. + template + napi_status unwrapObject(napi_value object, void **result) noexcept; + + // Exported function to associate a 16-byte ID such as UUID with an object. + napi_status typeTagObject( + napi_value object, + const napi_type_tag *typeTag) noexcept; + + // Exported function to check if 16-byte ID such as UUID is associated with an + // object. + napi_status checkObjectTypeTag( + napi_value object, + const napi_type_tag *typeTag, + bool *result) noexcept; + + // Exported function to create external value object. + napi_status createExternal( + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_value *result) noexcept; + + // Internal function to create external value object. + vm::Handle createExternalObject( + void *nativeData, + NapiExternalValue **externalValue) noexcept; + + // Exported function to get native data associated with the external value + // type. + napi_status getValueExternal(napi_value value, void **result) noexcept; + + // Internal function to get NapiExternalValue associated with the external + // value type. + NapiExternalValue *getExternalObjectValue(vm::HermesValue value) noexcept; + + // Internal function to get or create NapiExternalValue associated with the + // external value type. + template + napi_status getExternalPropertyValue( + TObject object, + NapiIfNotFound ifNotFound, + NapiExternalValue **result) noexcept; + + // Internal function to associate a finalizer with an object. + napi_status addObjectFinalizer( + const vm::PinnedHermesValue *value, + NapiFinalizer *finalizer) noexcept; + + // Internal function to call finalizer callback. + void callFinalizer( + napi_finalize finalizeCallback, + void *nativeData, + void *finalizeHint) noexcept; + + // Internal function to add finalizer to the finalizer queue. + void addToFinalizerQueue(NapiFinalizer *finalizer) noexcept; + + // Internal function to call all finalizers in the finalizer queue. + napi_status processFinalizerQueue() noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with references. + //----------------------------------------------------------------------------- + public: + // Exported function to create a Complex reference. + napi_status createReference( + napi_value value, + uint32_t initialRefCount, + napi_ref *result) noexcept; + + // Exported function to delete a Complex reference. + napi_status deleteReference(napi_ref ref) noexcept; + + // Exported function to increment Complex reference ref count. + // If the ref count was zero, then the weak ref is converted to string ref. + napi_status incReference(napi_ref ref, uint32_t *result) noexcept; + + // Exported function to decrement Complex reference ref count. + // If the ref count becomes zero, then the strong reference is converted to + // weak ref. + napi_status decReference(napi_ref ref, uint32_t *result) noexcept; + + // Exported function to get JS value from a complex reference. + napi_status getReferenceValue(napi_ref ref, napi_value *result) noexcept; + + // Internal function to add non-finalizing reference. + void addReference(NapiReference *reference) noexcept; + + // Internal function to add finalizing reference. + void addFinalizingReference(NapiReference *reference) noexcept; + + //----------------------------------------------------------------------------- + // Methods to control napi_value stack. + // napi_value are added on top of the stack. + // Closing napi_value stack scope deletes all napi_values added after + // opening the scope. + //----------------------------------------------------------------------------- + public: + // Exported function to open napi_value stack scope. + napi_status openNapiValueScope(napi_handle_scope *result) noexcept; + + // Exported function to close napi_value stack scope. + napi_status closeNapiValueScope(napi_handle_scope scope) noexcept; + + // Exported function to open napi_value stack scope that allows one value to + // escape to the parent scope. + napi_status openEscapableNapiValueScope( + napi_escapable_handle_scope *result) noexcept; + + // Exported function to close escapable napi_value stack scope. + napi_status closeEscapableNapiValueScope( + napi_escapable_handle_scope scope) noexcept; + + // Exported function to escape a value from current scope to the parent scope. + napi_status escapeNapiValue( + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value *result) noexcept; + + // Internal function to push new napi_value to the napi_value stack and then + // return it. + napi_value pushNewNapiValue(vm::HermesValue value) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with weak roots. + //----------------------------------------------------------------------------- + public: + // Internal function to create weak root. + vm::WeakRoot createWeakRoot(vm::JSObject *object) noexcept; + + void createWeakRoot( + vm::WeakRoot *weakRoot, + vm::JSObject *object) noexcept; + + void clearWeakRoot(vm::WeakRoot *weakRoot) noexcept; + + // Internal function to lock a weak root. + const vm::PinnedHermesValue &lockWeakRoot( + vm::WeakRoot &weakRoot) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with ordered sets. + // We use them as a temporary storage while retrieving property names. + // They are treated as GC roots. + //----------------------------------------------------------------------------- + public: + // Internal function to add ordered set to be tracked by GC. + void pushOrderedSet(NapiOrderedSet &set) noexcept; + + // Internal function to remove ordered set from being tracked by GC. + void popOrderedSet() noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with array buffers and typed arrays + //----------------------------------------------------------------------------- + public: + // Exported function to create JS ArrayBuffer object. + napi_status createArrayBuffer( + size_t byteLength, + void **data, + napi_value *result) noexcept; + + // Exported function to create JS ArrayBuffer object against external data. + napi_status createExternalArrayBuffer( + void *externalData, + size_t byteLength, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_value *result) noexcept; + + // Exported function to check if the value is an ArrayBuffer instance. + napi_status isArrayBuffer(napi_value value, bool *result) noexcept; + + // Exported function to get ArrayBuffer info. + napi_status getArrayBufferInfo( + napi_value arrayBuffer, + void **data, + size_t *byteLength) noexcept; + + // Exported function to detach the native buffer associated with the + // ArrayBuffer instance. + napi_status detachArrayBuffer(napi_value arrayBuffer) noexcept; + + // Exported function to check if ArrayBuffer instance has a detached native + // buffer. + napi_status isDetachedArrayBuffer( + napi_value arrayBuffer, + bool *result) noexcept; + + // Exported function to create JS TypedArray object instance for the + // arrayBuffer. The TypedArray is an array-like view of an underlying binary + // data buffer. + napi_status createTypedArray( + napi_typedarray_type type, + size_t length, + napi_value arrayBuffer, + size_t byteOffset, + napi_value *result) noexcept; + + // Internal function to create TypedArray instance. + template + napi_status createTypedArray( + size_t length, + vm::JSArrayBuffer *buffer, + size_t byteOffset, + vm::MutableHandle *result) noexcept; + + // Internal function to get TypedArray name. + template + static constexpr const char *getTypedArrayName() noexcept; + + // Exported function to check if the value is a TypedArray instance. + napi_status isTypedArray(napi_value value, bool *result) noexcept; + + // Exported function to get TypeArray info. + napi_status getTypedArrayInfo( + napi_value typedArray, + napi_typedarray_type *type, + size_t *length, + void **data, + napi_value *arrayBuffer, + size_t *byteOffset) noexcept; + + // Exported function to create JS DataView object instance for the + // arrayBuffer. + napi_status createDataView( + size_t byteLength, + napi_value arrayBuffer, + size_t byteOffset, + napi_value *result) noexcept; + + // Exported function to check if the value is a DataView. + napi_status isDataView(napi_value value, bool *result) noexcept; + + // Exported function to get DataView instance info. + napi_status getDataViewInfo( + napi_value dataView, + size_t *byteLength, + void **data, + napi_value *arrayBuffer, + size_t *byteOffset) noexcept; + + //----------------------------------------------------------------------------- + // Runtime info + //----------------------------------------------------------------------------- + public: + napi_status getDescription(const char **result) noexcept; + + napi_status isInspectable(bool *result) noexcept; + + //----------------------------------------------------------------------------- + // Version management + //----------------------------------------------------------------------------- + public: + // Exported function to get the version of the implemented Node-API. + napi_status getVersion(uint32_t *result) noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Promises + //----------------------------------------------------------------------------- + public: + // Exported function to create Promise object instance. + napi_status createPromise( + napi_deferred *deferred, + napi_value *result) noexcept; + + // Internal function to create Promise object instance. + napi_status createPromise( + napi_value *promise, + vm::MutableHandle<> *resolveFunction, + vm::MutableHandle<> *rejectFunction) noexcept; + + // Exported function to resolve Promise. + napi_status resolveDeferred( + napi_deferred deferred, + napi_value resolution) noexcept; + + // Exported function to reject Promise. + napi_status rejectDeferred( + napi_deferred deferred, + napi_value resolution) noexcept; + + // Internal function to resolve or reject Promise. + napi_status concludeDeferred( + napi_deferred deferred, + NapiPredefined predefinedProperty, + napi_value resolution) noexcept; + + // Exported function to check if value is a Promise. + napi_status isPromise(napi_value value, bool *result) noexcept; + + // Internal function to enable Promise rejection tracker. + napi_status enablePromiseRejectionTracker() noexcept; + + // Internal callback to handle Promise rejection notifications. + static vm::CallResult handleRejectionNotification( + void *context, + vm::Runtime &runtime, + vm::NativeArgs args, + void (*handler)( + NapiEnvironment *env, + int32_t id, + vm::HermesValue error)) noexcept; + + napi_status openEnvScope(jsr_napi_env_scope *scope) noexcept; + + napi_status closeEnvScope(jsr_napi_env_scope scope) noexcept; + + // Exported function to check if there is an unhandled Promise rejection. + napi_status hasUnhandledPromiseRejection(bool *result) noexcept; + + // Exported function to get an clear last unhandled Promise rejection. + napi_status getAndClearLastUnhandledPromiseRejection( + napi_value *result) noexcept; + + napi_status drainMicrotasks(int32_t maxCountHint, bool *result) noexcept; + + //----------------------------------------------------------------------------- + // Memory management + //----------------------------------------------------------------------------- + public: + // Exported function to adjust external memory size. It is not implemented. + // While it is not clear how to implement it for Hermes. + napi_status adjustExternalMemory( + int64_t change_in_bytes, + int64_t *adjusted_value) noexcept; + + // Exported function to run garbage collection. It must be used only in unit + // tests. + napi_status collectGarbage() noexcept; + + //----------------------------------------------------------------------------- + // Methods to work with Dates + //----------------------------------------------------------------------------- + public: + // Exported function to create JS Date object. + napi_status createDate(double dateTime, napi_value *result) noexcept; + + // Exported function to check if the value is a Date instance. + napi_status isDate(napi_value value, bool *result) noexcept; + + // Exported function to get the internal value of the Date object. + // It is equivalent to the JS Date.prototype.valueOf(). + // the number of milliseconds since midnight 01 January, 1970 UTC. + napi_status getDateValue(napi_value value, double *result) noexcept; + + //----------------------------------------------------------------------------- + // Instance data + //----------------------------------------------------------------------------- + public: + // Exported function to associate external data with the environment. + // Finalizer is not called for the previously associated data. + napi_status setInstanceData( + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint) noexcept; + + // Exported function to get external data associated with the environment. + napi_status getInstanceData(void **nativeData) noexcept; + + //--------------------------------------------------------------------------- + // Script running + //--------------------------------------------------------------------------- + + // Exported function to run script from a string value. + // The sourceURL is used only for error reporting. + napi_status runScript( + napi_value source, + const char *sourceURL, + napi_value *result) noexcept; + + napi_status createPreparedScript( + const uint8_t *scriptData, + size_t scriptLength, + jsr_data_delete_cb scriptDeleteCallback, + void *deleterData, + const char *sourceURL, + jsr_prepared_script *result) noexcept; + + napi_status deletePreparedScript(jsr_prepared_script preparedScript) noexcept; + + napi_status runPreparedScript( + jsr_prepared_script preparedScript, + napi_value *result) noexcept; + + // Internal function to check if buffer contains Hermes VM bytecode. + static bool isHermesBytecode(const uint8_t *data, size_t length) noexcept; + + //--------------------------------------------------------------------------- + // Methods to create Hermes GC handles for stack-based variables. + // + // vm::Handle is a GC root kept on the stack. + // The vm::Handle<> is a shortcut for vm::Handle. + //--------------------------------------------------------------------------- + public: + vm::Handle<> makeHandle(napi_value value) noexcept; + vm::Handle<> makeHandle(const vm::PinnedHermesValue *value) noexcept; + vm::Handle<> makeHandle(vm::HermesValue value) noexcept; + vm::Handle<> makeHandle(vm::Handle<> value) noexcept; + vm::Handle<> makeHandle(uint32_t value) noexcept; + template + vm::Handle makeHandle(napi_value value) noexcept; + template + vm::Handle makeHandle(const vm::PinnedHermesValue *value) noexcept; + template + vm::Handle makeHandle(vm::HermesValue value) noexcept; + template + vm::Handle makeHandle(vm::Handle value) noexcept; + template + vm::Handle makeHandle(vm::PseudoHandle &&value) noexcept; + template + vm::CallResult> makeHandle( + vm::CallResult> &&callResult) noexcept; + template + vm::CallResult> makeMutableHandle( + vm::CallResult> &&callResult) noexcept; + + //--------------------------------------------------------------------------- + // Result setting helpers + // + // These functions help to reduce code responsible for returning results. + //--------------------------------------------------------------------------- + public: + template + napi_status setResult(T &&value, TResult *result) noexcept; + + template + napi_status setOptionalResult(T &&value, TResult *result) noexcept; + + template + napi_status setOptionalResult(T &&value, std::nullptr_t) noexcept; + + napi_status setPredefinedResult( + const vm::PinnedHermesValue *value, + napi_value *result) noexcept; + + template + napi_status setResultUnsafe(T &&value, T *result) noexcept; + + napi_status setResultUnsafe( + vm::HermesValue value, + napi_value *result) noexcept; + + napi_status setResultUnsafe(vm::SymbolID value, napi_value *result) noexcept; + + napi_status setResultUnsafe(bool value, napi_value *result) noexcept; + + template + napi_status setResultUnsafe( + vm::Handle &&handle, + napi_value *result) noexcept; + + template + napi_status setResultUnsafe( + vm::PseudoHandle &&handle, + napi_value *result) noexcept; + + template + napi_status setResultUnsafe( + vm::Handle &&handle, + vm::MutableHandle *result) noexcept; + + napi_status setResultUnsafe( + vm::HermesValue value, + vm::MutableHandle<> *result) noexcept; + + template + napi_status setResultUnsafe( + vm::CallResult &&value, + TResult *result) noexcept; + + template + napi_status setResultUnsafe( + vm::CallResult &&, + napi_status onException, + TResult *result) noexcept; + + private: + // Controls the lifetime of this class instances. + std::atomic refCount_{1}; + + // Used for safe update of finalizer queue. + NapiRefCountedPtr pendingFinalizers_; + + // Reference to the wrapped Hermes runtime. + vm::Runtime &runtime_; + + // Reference to itself for convenient use in macros. + NapiEnvironment &env{*this}; + + // Flags used by byte code compiler. + hbc::CompileFlags compileFlags_{}; + + // Optional prepared script store. + std::shared_ptr scriptCache_{}; + + // Can we run a debugger? + bool isInspectable_{}; + + // Collection of all predefined values. + std::array< + vm::PinnedHermesValue, + static_cast(NapiPredefined::PredefinedCount)> + predefinedValues_{}; + + // Stack of napi_value. + NapiStableAddressStack napiValueStack_; + + // Stack of napi_value scopes. + NapiStableAddressStack napiValueStackScopes_; + + // We store references in two different lists, depending on whether they + // have `napi_finalizer` callbacks, because we must first finalize the + // ones that have such a callback. See `~NapiEnvironment()` for details. + NapiLinkedList references_{}; + NapiLinkedList finalizingReferences_{}; + + // Finalizers must be run outside of GC pass because they could access GC + // objects. Then GC finalizes and object, we put all the associated finalizers + // to this queue and then run them as soon as have an opportunity to do that + // safely. + NapiLinkedList finalizerQueue_{}; + + // To ensure that the finalizerQueue_ is being processed only from a single + // place at a time. + bool isRunningFinalizers_{false}; + + // Helps to change the behaviour of finalizers when the environment is + // shutting down. + bool isShuttingDown_{false}; + + // Temporary GC roots for ordered sets used to collect property names. + llvh::SmallVector *, 16> orderedSets_; + + // List of unique string references. + std::unordered_map + uniqueStrings_; + + // Storage for the last native error message. + std::string lastErrorMessage_; + + // The last native error. + NapiNativeError lastError_{"", 0, 0, napi_ok}; + + // The last JS error. + vm::PinnedHermesValue thrownJSError_{EmptyHermesValue}; + + // ID of last recorded unhandled Promise rejection. + int32_t lastUnhandledRejectionId_{-1}; + + // The last unhandled Promise rejection. + vm::PinnedHermesValue lastUnhandledRejection_{EmptyHermesValue}; + + // External data associated with the environment instance. + NapiInstanceData *instanceData_{}; + + // HermesValue used for uninitialized values. + static constexpr vm::HermesValue EmptyHermesValue{ + vm::HermesValue::encodeEmptyValue()}; + + // The sentinel tag in napiValueStack_ used for escapable values. + // These are the first four ASCII letters of name "Janus" - the god of gates. + static constexpr uint32_t kEscapeableSentinelTag = 0x4a616e75; + static constexpr uint32_t kUsedEscapeableSentinelTag = + kEscapeableSentinelTag + 1; + + // Tag used to indicate external values for DecoratedObject. + // These are the first four ASCII letters of word "External". + static constexpr uint32_t kExternalValueTag = 0x45787465; + static constexpr int32_t kExternalTagSlotIndex = 0; +}; + +// NapiPendingFinalizers is used to update the pending finalizer list in a +// thread safe way when a NapiExternalValue is destroyed from a GC background +// thread. +class NapiPendingFinalizers { + public: + // Create new instance of NapiPendingFinalizers. + static NapiRefCountedPtr create() noexcept { + return NapiRefCountedPtr( + new NapiPendingFinalizers(), attachTag); + } + + // Add pending finalizers from a NapiExternalValue destructor. + // It can be called from JS or GC background threads. + void addPendingFinalizers( + std::unique_ptr> &&finalizers) noexcept { + std::scoped_lock lock{mutex_}; + finalizers_.push_back(std::move(finalizers)); + } + + // Apply pending finalizers to the finalizer queue. + // It must be called from a JS thread. + void applyPendingFinalizers(NapiEnvironment *env) noexcept { + std::vector>> finalizers; + { + std::scoped_lock lock{mutex_}; + if (finalizers_.empty()) { + return; + } + // Move to a local variable to unlock the mutex earlier. + finalizers = std::move(finalizers_); + } + + for (auto &finalizerList : finalizers) { + finalizerList->forEach([env](NapiFinalizer *finalizer) { + env->addToFinalizerQueue(finalizer); + }); + } + } + + private: + friend class NapiRefCountedPtr; + + NapiPendingFinalizers() noexcept = default; + + void incRefCount() noexcept { + int refCount = refCount_.fetch_add(1, std::memory_order_relaxed) + 1; + CRASH_IF_FALSE(refCount > 1 && "The ref count cannot bounce from zero."); + CRASH_IF_FALSE( + refCount < std::numeric_limits::max() && + "The ref count is too big."); + } + + void decRefCount() noexcept { + int refCount = refCount_.fetch_sub(1, std::memory_order_release) - 1; + CRASH_IF_FALSE(refCount >= 0 && "The ref count must not be negative."); + if (refCount == 0) { + std::atomic_thread_fence(std::memory_order_acquire); + delete this; + } + } + + private: + std::atomic refCount_{1}; + std::recursive_mutex mutex_; + std::vector>> finalizers_; +}; + +// RAII class to control scope of napi_value variables and return values. +class NapiHandleScope final { + public: + NapiHandleScope(NapiEnvironment &env, napi_value *result = nullptr) noexcept + : env_(env), + result_(result), + savedScope_(env.napiValueStack().size()), + gcScope_(env.runtime()) {} + + ~NapiHandleScope() noexcept { + env_.napiValueStack().resize(savedScope_); + } + + napi_status setResult(napi_status status) noexcept { + CHECK_NAPI(status); + if (result_ != nullptr) { + if (savedScope_ + 1 < env_.napiValueStack().size()) { + env_.napiValueStack()[savedScope_] = *phv(*result_); + *result_ = napiValue(&env_.napiValueStack()[savedScope_]); + } else { + CRASH_IF_FALSE(savedScope_ < env_.napiValueStack().size()); + CRASH_IF_FALSE(phv(*result_) == &env_.napiValueStack()[savedScope_]); + } + // To make sure that the return value is not removed in the destructor. + ++savedScope_; + } + return env_.processFinalizerQueue(); + } + + template + napi_status setResult(T &&value) noexcept { + return setResult(env_.setResult(std::forward(value), result_)); + } + + template + napi_status setOptionalResult(T &&value) noexcept { + return setResult(env_.setOptionalResult(std::forward(value), result_)); + } + + private: + NapiEnvironment &env_; + napi_value *result_{}; + size_t savedScope_; + vm::GCScope gcScope_; +}; + +// Keep external data with an object. +class NapiExternalValue final : public vm::DecoratedObject::Decoration { + public: + NapiExternalValue(const NapiRefCountedPtr + &pendingFinalizers) noexcept + : pendingFinalizers_(pendingFinalizers) {} + NapiExternalValue( + const NapiRefCountedPtr &pendingFinalizers, + void *nativeData) noexcept + : pendingFinalizers_(pendingFinalizers), nativeData_(nativeData) {} + + NapiExternalValue(const NapiExternalValue &other) = delete; + NapiExternalValue &operator=(const NapiExternalValue &other) = delete; + + // The destructor is called by GC. It can be called either from JS or GC + // threads. We move the finalizers to NapiPendingFinalizers to be accessed + // only from JS thread. + ~NapiExternalValue() override { + pendingFinalizers_->addPendingFinalizers(std::move(finalizers_)); + } + + size_t getMallocSize() const override { + return sizeof(*this); + } + + void addFinalizer(NapiFinalizer *finalizer) noexcept { + finalizers_->pushBack(finalizer); + } + + void *nativeData() noexcept { + return nativeData_; + } + + void setNativeData(void *value) noexcept { + nativeData_ = value; + } + + private: + NapiRefCountedPtr pendingFinalizers_; + void *nativeData_{}; + std::unique_ptr> finalizers_{ + std::make_unique>()}; +}; + +// Keep native data associated with a function. +class NapiHostFunctionContext final { + friend class NapiCallbackInfo; + + public: + NapiHostFunctionContext( + NapiEnvironment &env, + napi_callback hostCallback, + void *nativeData) noexcept + : env_{env}, hostCallback_{hostCallback}, nativeData_{nativeData} {} + + static vm::CallResult + func(void *context, vm::Runtime &runtime, vm::NativeArgs hvArgs); + + static void finalizeState(vm::GC & /*gc*/, vm::NativeState *ns) { + delete reinterpret_cast(ns->context()); + } + + static void finalize(void *context) { + delete reinterpret_cast(context); + } + + void *nativeData() noexcept { + return nativeData_; + } + + private: + NapiEnvironment &env_; + napi_callback hostCallback_; + void *nativeData_; +}; + +class NapiCallbackInfo final { + public: + NapiCallbackInfo( + NapiHostFunctionContext &context, + vm::NativeArgs &nativeArgs) noexcept + : context_(context), nativeArgs_(nativeArgs) {} + + void args(napi_value *buffer, size_t bufferLength) noexcept { + size_t min = + std::min(bufferLength, static_cast(nativeArgs_.getArgCount())); + size_t i{0}; + for (; i < min; ++i) { + buffer[i] = napiValue(&nativeArgs_.begin()[i]); + } + for (; i < bufferLength; ++i) { + buffer[i] = napiValue(&context_.env_.getUndefined()); + } + } + + size_t argCount() noexcept { + return nativeArgs_.getArgCount(); + } + + napi_value thisArg() noexcept { + return napiValue(&nativeArgs_.getThisArg()); + } + + void *nativeData() noexcept { + return context_.nativeData(); + } + + napi_value getNewTarget() noexcept { + const vm::PinnedHermesValue &newTarget = nativeArgs_.getNewTarget(); + return napiValue(newTarget.isUndefined() ? nullptr : &newTarget); + } + + private: + NapiHostFunctionContext &context_; + vm::NativeArgs &nativeArgs_; +}; + +/*static*/ vm::CallResult NapiHostFunctionContext::func( + void *context, + vm::Runtime &runtime, + vm::NativeArgs hvArgs) { + NapiHostFunctionContext *hfc = + reinterpret_cast(context); + NapiEnvironment &env = hfc->env_; + assert(&runtime == &env.runtime()); + + NapiHandleScope scope{env}; + NapiCallbackInfo callbackInfo{*hfc, hvArgs}; + napi_value result{}; + vm::ExecutionStatus status = env.callIntoModule([&](NapiEnvironment *env) { + result = hfc->hostCallback_( + napiEnv(env), reinterpret_cast(&callbackInfo)); + }); + + if (status == vm::ExecutionStatus::EXCEPTION) { + return vm::ExecutionStatus::EXCEPTION; + } + + if (result) { + return *phv(result); + } else { + return env.getUndefined(); + } +} + +// Different types of references: +// 1. Strong reference - it can wrap up object of any type +// a. Ref count maintains the reference lifetime. When it reaches zero it is +// removed. +// 2. Weak reference - it can wrap up only objects +// a. Ref count maintains the lifetime of the reference. When it reaches zero +// it is removed. +// 3. Complex reference - it can wrap up only objects +// a. Ref count only for strong references. Zero converts it to a weak ref. +// Removal is explicit if external code holds a reference. + +// A base class for References that wrap native data and must be finalized. +class NapiFinalizer : public NapiLinkedList::Item { + public: + virtual void finalize(NapiEnvironment &env) noexcept = 0; + + protected: + NapiFinalizer() = default; + + ~NapiFinalizer() noexcept { + unlink(); + } +}; + +// A base class for all references. +class NapiReference : public NapiLinkedList::Item { + public: + enum class ReasonToDelete { + ZeroRefCount, + FinalizerCall, + ExternalCall, + EnvironmentShutdown, + }; + + static napi_status deleteReference( + NapiEnvironment &env, + NapiReference *reference, + ReasonToDelete reason) noexcept { + if (reference && reference->startDeleting(env, reason)) { + delete reference; + } + return env.clearLastNativeError(); + } + + virtual napi_status incRefCount( + NapiEnvironment &env, + uint32_t & /*result*/) noexcept { + return GENERIC_FAILURE("This reference does not support ref count."); + } + + virtual napi_status decRefCount( + NapiEnvironment &env, + uint32_t & /*result*/) noexcept { + return GENERIC_FAILURE("This reference does not support ref count."); + } + + virtual const vm::PinnedHermesValue &value(NapiEnvironment &env) noexcept { + return env.getUndefined(); + } + + virtual void *nativeData() noexcept { + return nullptr; + } + + virtual void *finalizeHint() noexcept { + return nullptr; + } + + virtual vm::PinnedHermesValue *getGCRoot(NapiEnvironment & /*env*/) noexcept { + return nullptr; + } + + virtual vm::WeakRoot *getGCWeakRoot( + NapiEnvironment & /*env*/) noexcept { + return nullptr; + } + + static void getGCRoots( + NapiEnvironment &env, + NapiLinkedList &list, + vm::RootAcceptor &acceptor) noexcept { + list.forEach([&](NapiReference *ref) { + if (vm::PinnedHermesValue *value = ref->getGCRoot(env)) { + acceptor.accept(*value); + } + }); + } + + static void getGCWeakRoots( + NapiEnvironment &env, + NapiLinkedList &list, + vm::WeakRootAcceptor &acceptor) noexcept { + list.forEach([&](NapiReference *ref) { + if (vm::WeakRoot *weakRoot = ref->getGCWeakRoot(env)) { + acceptor.acceptWeak(*weakRoot); + } + }); + } + + virtual napi_status callFinalizeCallback(NapiEnvironment &env) noexcept { + return napi_ok; + } + + virtual void finalize(NapiEnvironment &env) noexcept {} + + template + static void finalizeAll( + NapiEnvironment &env, + NapiLinkedList &list) noexcept { + for (TItem *item = list.begin(); item != list.end(); item = list.begin()) { + item->finalize(env); + } + } + + static void deleteAll( + NapiEnvironment &env, + NapiLinkedList &list, + ReasonToDelete reason) noexcept { + for (NapiReference *ref = list.begin(); ref != list.end(); + ref = list.begin()) { + deleteReference(env, ref, reason); + } + } + + protected: + // Make protected to avoid using operator delete directly. + // Use the deleteReference method instead. + virtual ~NapiReference() noexcept { + unlink(); + } + + virtual bool startDeleting( + NapiEnvironment &env, + ReasonToDelete /*reason*/) noexcept { + return true; + } +}; + +// A reference with a ref count that can be changed from any thread. +// The reference deletion is done as a part of GC root detection to avoid +// deletion in a random thread. +class NapiAtomicRefCountReference : public NapiReference { + public: + napi_status incRefCount(NapiEnvironment &env, uint32_t &result) noexcept + override { + result = refCount_.fetch_add(1, std::memory_order_relaxed) + 1; + CRASH_IF_FALSE(result > 1 && "The ref count cannot bounce from zero."); + CRASH_IF_FALSE(result < MaxRefCount && "The ref count is too big."); + return napi_ok; + } + + napi_status decRefCount(NapiEnvironment &env, uint32_t &result) noexcept + override { + result = refCount_.fetch_sub(1, std::memory_order_release) - 1; + if (result == 0) { + std::atomic_thread_fence(std::memory_order_acquire); + } else if (result > MaxRefCount) { + // Decrement of an unsigned value below zero is getting to a very big + // number. + CRASH_IF_FALSE( + result < MaxRefCount && "The ref count must not be negative."); + } + return napi_ok; + } + + protected: + uint32_t refCount() const noexcept { + return refCount_; + } + + bool startDeleting(NapiEnvironment &env, ReasonToDelete reason) noexcept + override { + return reason != ReasonToDelete::ExternalCall; + } + + private: + std::atomic refCount_{1}; + + static constexpr uint32_t MaxRefCount = + std::numeric_limits::max() / 2; +}; + +// Atomic ref counting for vm::PinnedHermesValue. +class NapiStrongReference : public NapiAtomicRefCountReference { + public: + static napi_status create( + NapiEnvironment &env, + vm::HermesValue value, + NapiStrongReference **result) noexcept { + CHECK_ARG(result); + *result = new NapiStrongReference(value); + env.addReference(*result); + return env.clearLastNativeError(); + } + + const vm::PinnedHermesValue &value(NapiEnvironment &env) noexcept override { + return value_; + } + + vm::PinnedHermesValue *getGCRoot(NapiEnvironment &env) noexcept override { + if (refCount() > 0) { + return &value_; + } else { + deleteReference(env, this, ReasonToDelete::ZeroRefCount); + return nullptr; + } + } + + protected: + NapiStrongReference(vm::HermesValue value) noexcept : value_(value) {} + + private: + vm::PinnedHermesValue value_; +}; + +// Atomic ref counting for a vm::WeakRef. +class NapiWeakReference final : public NapiAtomicRefCountReference { + public: + static napi_status create( + NapiEnvironment &env, + const vm::PinnedHermesValue *value, + NapiWeakReference **result) noexcept { + CHECK_OBJECT_ARG(value); + CHECK_ARG(result); + *result = + new NapiWeakReference(env.createWeakRoot(getObjectUnsafe(*value))); + env.addReference(*result); + return env.clearLastNativeError(); + } + + const vm::PinnedHermesValue &value(NapiEnvironment &env) noexcept override { + return env.lockWeakRoot(weakRoot_); + } + + vm::WeakRoot *getGCWeakRoot( + NapiEnvironment &env) noexcept override { + if (refCount() > 0) { + return &weakRoot_; + } else { + deleteReference(env, this, ReasonToDelete::ZeroRefCount); + return nullptr; + } + } + + protected: + NapiWeakReference(vm::WeakRoot weakRoot) noexcept + : weakRoot_(weakRoot) {} + + private: + vm::WeakRoot weakRoot_; +}; + +// Keep vm::PinnedHermesValue when ref count > 0 or vm::WeakRoot +// when ref count == 0. The ref count is not atomic and must be changed only +// from the JS thread. +class NapiComplexReference : public NapiReference { + public: + static napi_status create( + NapiEnvironment &env, + const vm::PinnedHermesValue *value, + uint32_t initialRefCount, + NapiComplexReference **result) noexcept { + CHECK_OBJECT_ARG(value); + CHECK_ARG(result); + *result = new NapiComplexReference( + initialRefCount, + *value, + initialRefCount == 0 ? env.createWeakRoot(getObjectUnsafe(*value)) + : vm::WeakRoot{}); + env.addReference(*result); + return env.clearLastNativeError(); + } + + napi_status incRefCount(NapiEnvironment &env, uint32_t &result) noexcept + override { + if (refCount_ == 0) { + value_ = env.lockWeakRoot(weakRoot_); + } + CRASH_IF_FALSE(++refCount_ < maxRefCount_ && "The ref count is too big."); + result = refCount_; + return env.clearLastNativeError(); + } + + napi_status decRefCount(NapiEnvironment &env, uint32_t &result) noexcept + override { + if (refCount_ == 0) { + // Ignore this error situation to match NAPI for V8 implementation. + result = 0; + return napi_ok; + } + if (--refCount_ == 0) { + if (value_.isObject()) { + env.createWeakRoot(&weakRoot_, getObjectUnsafe(value_)); + } else { + env.clearWeakRoot(&weakRoot_); + } + } + result = refCount_; + return env.clearLastNativeError(); + } + + const vm::PinnedHermesValue &value(NapiEnvironment &env) noexcept override { + if (refCount_ > 0) { + return value_; + } else { + return env.lockWeakRoot(weakRoot_); + } + } + + vm::PinnedHermesValue *getGCRoot( + NapiEnvironment & /*env*/) noexcept override { + return (refCount_ > 0) ? &value_ : nullptr; + } + + vm::WeakRoot *getGCWeakRoot( + NapiEnvironment & /*env*/) noexcept override { + return (refCount_ == 0 && weakRoot_) ? &weakRoot_ : nullptr; + } + + protected: + NapiComplexReference( + uint32_t initialRefCount, + const vm::PinnedHermesValue &value, + vm::WeakRoot weakRoot) noexcept + : refCount_(initialRefCount), value_(value), weakRoot_(weakRoot) {} + + uint32_t refCount() const noexcept { + return refCount_; + } + + private: + uint32_t refCount_{0}; + vm::PinnedHermesValue value_; + vm::WeakRoot weakRoot_; + + static constexpr uint32_t maxRefCount_ = + std::numeric_limits::max() / 2; +}; + +// Store finalizeHint if it is not null. +template +class NapiFinalizeHintHolder : public TBaseReference { + public: + template + NapiFinalizeHintHolder(void *finalizeHint, TArgs &&...args) noexcept + : TBaseReference(std::forward(args)...), + finalizeHint_(finalizeHint) {} + + void *finalizeHint() noexcept override { + return finalizeHint_; + } + + private: + void *finalizeHint_; +}; + +// Store and call finalizeCallback if it is not null. +template +class NapiFinalizeCallbackHolder : public TBaseReference { + using Super = TBaseReference; + + public: + template + NapiFinalizeCallbackHolder( + napi_finalize finalizeCallback, + TArgs &&...args) noexcept + : TBaseReference(std::forward(args)...), + finalizeCallback_(finalizeCallback) {} + + napi_status callFinalizeCallback(NapiEnvironment &env) noexcept override { + if (finalizeCallback_) { + napi_finalize finalizeCallback = + std::exchange(finalizeCallback_, nullptr); + env.callFinalizer( + finalizeCallback, Super::nativeData(), Super::finalizeHint()); + } + return napi_ok; + } + + private: + napi_finalize finalizeCallback_{}; +}; + +// Store nativeData if it is not null. +template +class NapiNativeDataHolder : public TBaseReference { + public: + template + NapiNativeDataHolder(void *nativeData, TArgs &&...args) noexcept + : TBaseReference(std::forward(args)...), nativeData_(nativeData) {} + + void *nativeData() noexcept override { + return nativeData_; + } + + private: + void *nativeData_; +}; + +// Common code for references inherited from NapiFinalizer. +template +class NapiFinalizingReference final : public TBaseReference { + using Super = TBaseReference; + + public: + template + NapiFinalizingReference(TArgs &&...args) noexcept + : TBaseReference(std::forward(args)...) {} + + void finalize(NapiEnvironment &env) noexcept override { + Super::callFinalizeCallback(env); + NapiReference::deleteReference( + env, this, NapiReference::ReasonToDelete::FinalizerCall); + } +}; + +// Create NapiFinalizingReference with the optimized storage. +template +class NapiFinalizingReferenceFactory final { + public: + template + static TReference *create( + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + TArgs &&...args) noexcept { + int selector = (nativeData ? 0b100 : 0) | (finalizeCallback ? 0b010 : 0) | + (finalizeHint ? 0b001 : 0); + switch (selector) { + default: + case 0b000: + case 0b001: + return new NapiFinalizingReference( + std::forward(args)...); + case 0b010: + return new NapiFinalizingReference< + NapiFinalizeCallbackHolder>( + finalizeCallback, std::forward(args)...); + case 0b011: + return new NapiFinalizingReference< + NapiFinalizeCallbackHolder>>( + finalizeCallback, finalizeHint, std::forward(args)...); + case 0b100: + case 0b101: + return new NapiFinalizingReference>( + nativeData, std::forward(args)...); + case 0b110: + return new NapiFinalizingReference< + NapiNativeDataHolder>>( + nativeData, finalizeCallback, std::forward(args)...); + case 0b111: + return new NapiFinalizingReference>>>( + nativeData, + finalizeCallback, + finalizeHint, + std::forward(args)...); + } + } +}; + +// The reference that is never returned to the user code and only used to hold +// the native data and its finalizer callback. +// It is either deleted from the finalizer queue, on environment shutdown, or +// directly when deleting the object wrap. +class NapiFinalizingAnonymousReference : public NapiReference, + public NapiFinalizer { + public: + static napi_status create( + NapiEnvironment &env, + const vm::PinnedHermesValue *value, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + /*optional*/ NapiFinalizingAnonymousReference **result) noexcept { + NapiFinalizingAnonymousReference *ref = + NapiFinalizingReferenceFactory:: + create(nativeData, finalizeCallback, finalizeHint); + if (value != nullptr) { + CHECK_OBJECT_ARG(value); + env.addObjectFinalizer(value, ref); + } + env.addFinalizingReference(ref); + return env.setOptionalResult(std::move(ref), result); + } +}; + +// Associates data with NapiStrongReference. +class NapiFinalizingStrongReference : public NapiStrongReference, + public NapiFinalizer { + public: + static napi_status create( + NapiEnvironment &env, + const vm::PinnedHermesValue *value, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + NapiFinalizingStrongReference **result) noexcept { + CHECK_ARG(value); + CHECK_ARG(*result); + *result = + NapiFinalizingReferenceFactory::create( + nativeData, finalizeCallback, finalizeHint, *value); + env.addFinalizingReference(*result); + return env.clearLastNativeError(); + } + + protected: + NapiFinalizingStrongReference(const vm::PinnedHermesValue &value) noexcept + : NapiStrongReference(value) {} + + bool startDeleting(NapiEnvironment &env, ReasonToDelete reason) noexcept + override { + if (reason == ReasonToDelete::ZeroRefCount) { + // Let the finalizer to run first. + env.addToFinalizerQueue(this); + return false; + } else if (reason == ReasonToDelete::FinalizerCall) { + if (refCount() != 0) { + // On shutdown the finalizer is called when the ref count is not zero + // yet. Postpone the deletion until all finalizers are finished to run. + NapiFinalizer::unlink(); + env.addReference(this); + return false; + } + } + return true; + } +}; + +// A reference that can be either strong or weak and that holds a finalizer +// callback. +class NapiFinalizingComplexReference : public NapiComplexReference, + public NapiFinalizer { + public: + static napi_status create( + NapiEnvironment &env, + uint32_t initialRefCount, + bool deleteSelf, + const vm::PinnedHermesValue *value, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + NapiFinalizingComplexReference **result) noexcept { + CHECK_OBJECT_ARG(value); + CHECK_ARG(result); + *result = + NapiFinalizingReferenceFactory::create( + nativeData, + finalizeCallback, + finalizeHint, + initialRefCount, + deleteSelf, + *value, + initialRefCount == 0 ? env.createWeakRoot(getObjectUnsafe(*value)) + : vm::WeakRoot{}); + if (initialRefCount == 0) { + env.addObjectFinalizer(value, *result); + } + env.addFinalizingReference(*result); + return env.clearLastNativeError(); + } + + napi_status incRefCount(NapiEnvironment &env, uint32_t &result) noexcept + override { + CHECK_NAPI(NapiComplexReference::incRefCount(env, result)); + if (result == 1) { + NapiLinkedList::Item::unlink(); + } + return env.clearLastNativeError(); + } + + napi_status decRefCount(NapiEnvironment &env, uint32_t &result) noexcept + override { + vm::PinnedHermesValue hv; + bool shouldConvertToWeakRef = refCount() == 1; + if (shouldConvertToWeakRef) { + hv = value(env); + } + CHECK_NAPI(NapiComplexReference::decRefCount(env, result)); + if (shouldConvertToWeakRef && hv.isObject()) { + return env.addObjectFinalizer(&hv, this); + } + return env.clearLastNativeError(); + } + + protected: + NapiFinalizingComplexReference( + uint32_t initialRefCount, + bool deleteSelf, + const vm::PinnedHermesValue &value, + vm::WeakRoot weakRoot) noexcept + : NapiComplexReference{initialRefCount, value, weakRoot}, + deleteSelf_{deleteSelf} {} + + bool startDeleting(NapiEnvironment &env, ReasonToDelete reason) noexcept + override { + if (reason == ReasonToDelete::ExternalCall && + NapiLinkedList::Item::isLinked()) { + // Let the finalizer or the environment shutdown to delete the reference. + deleteSelf_ = true; + return false; + } + if (reason == ReasonToDelete::FinalizerCall && !deleteSelf_) { + // Let the external call or the environment shutdown to delete the + // reference. + NapiFinalizer::unlink(); + env.addReference(this); + return false; + } + return true; + } + + private: + bool deleteSelf_{false}; +}; + +// Hold custom data associated with the NapiEnvironment. +class NapiInstanceData : public NapiReference { + public: + static napi_status create( + NapiEnvironment &env, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + /*optional*/ NapiInstanceData **result) noexcept { + NapiInstanceData *ref = + NapiFinalizingReferenceFactory::create( + nativeData, finalizeCallback, finalizeHint); + if (result) { + *result = ref; + } + return env.clearLastNativeError(); + } +}; + +// Sorted list of unique HermesValues. +template <> +class NapiOrderedSet final { + public: + using Compare = + int32_t(const vm::HermesValue &item1, const vm::HermesValue &item2); + + NapiOrderedSet(NapiEnvironment &env, Compare *compare) noexcept + : env_(env), compare_(compare) { + env_.pushOrderedSet(*this); + } + + ~NapiOrderedSet() { + env_.popOrderedSet(); + } + + bool insert(vm::HermesValue value) noexcept { + auto it = llvh::lower_bound( + items_, + value, + [this](const vm::HermesValue &item1, const vm::HermesValue &item2) { + return (*compare_)(item1, item2) < 0; + }); + if (it != items_.end() && (*compare_)(*it, value) == 0) { + return false; + } + items_.insert(it, value); + return true; + } + + static void getGCRoots( + llvh::iterator_range range, + vm::RootAcceptor &acceptor) noexcept { + for (NapiOrderedSet *set : range) { + for (vm::PinnedHermesValue &value : set->items_) { + acceptor.accept(value); + } + } + } + + private: + NapiEnvironment &env_; + llvh::SmallVector items_; + Compare *compare_{}; +}; + +// Sorted list of unique uint32_t. +template <> +class NapiOrderedSet final { + public: + bool insert(uint32_t value) noexcept { + auto it = llvh::lower_bound(items_, value); + if (it == items_.end() || *it == value) { + return false; + } + items_.insert(it, value); + return true; + } + + private: + llvh::SmallVector items_; +}; + +// Helper class to build a string. +class NapiStringBuilder final { + public: + // To adopt an existing string instead of creating a new one. + class AdoptStringTag {}; + constexpr static AdoptStringTag AdoptString{}; + + NapiStringBuilder(AdoptStringTag, std::string &&str) noexcept + : str_(std::move(str)), stream_(str_) {} + + template + NapiStringBuilder(TArgs &&...args) noexcept : stream_(str_) { + append(std::forward(args)...); + } + + NapiStringBuilder &append() noexcept { + return *this; + } + + template + NapiStringBuilder &append(TArg0 &&arg0, TArgs &&...args) noexcept { + stream_ << arg0; + return append(std::forward(args)...); + } + + std::string &str() noexcept { + stream_.flush(); + return str_; + } + + const char *c_str() noexcept { + return str().c_str(); + } + + napi_status makeHVString( + NapiEnvironment &env, + vm::MutableHandle<> *result) noexcept { + stream_.flush(); + vm::CallResult res = vm::StringPrimitive::createEfficient( + env.runtime(), llvh::makeArrayRef(str_.data(), str_.size())); + return env.setResult(std::move(res), result); + } + + private: + std::string str_; + llvh::raw_string_ostream stream_; +}; + +class NapiExternalBufferCore { + public: + NapiExternalBufferCore( + NapiEnvironment &env, + void *data, + napi_finalize finalizeCallback, + void *finalizeHint) + : env_(&env), + finalizeCallback_(finalizeCallback), + data_(data), + finalizeHint_(finalizeHint) {} + + void setFinalizer(NapiFinalizer *finalizer) { + finalizer_ = finalizer; + } + + void onBufferDeleted() { + if (finalizer_ != nullptr) { + env_->addToFinalizerQueue(finalizer_); + env_ = nullptr; + } else { + delete this; + } + } + + static void + finalize(napi_env env, void * /*finalizeData*/, void *finalizeHint) { + NapiExternalBufferCore *core = + reinterpret_cast(finalizeHint); + if (core->finalizeCallback_ != nullptr) { + core->finalizeCallback_(env, core->data_, core->finalizeHint_); + } + + core->finalizer_ = nullptr; + if (core->env_ == nullptr) { + delete core; + } + } + + NapiExternalBufferCore(const NapiExternalBufferCore &) = delete; + NapiExternalBufferCore &operator=(const NapiExternalBufferCore &) = delete; + + private: + NapiFinalizer *finalizer_{}; + NapiEnvironment *env_; + napi_finalize finalizeCallback_; + void *data_; + void *finalizeHint_; +}; + +// The external buffer that implements hermes::Buffer +class NapiExternalBuffer final : public hermes::Buffer { + public: + static std::unique_ptr make( + napi_env env, + void *bufferData, + size_t bufferSize, + napi_finalize finalizeCallback, + void *finalizeHint) noexcept { + return bufferData ? std::make_unique( + *reinterpret_cast(env), + bufferData, + bufferSize, + finalizeCallback, + finalizeHint) + : nullptr; + } + + NapiExternalBuffer( + NapiEnvironment &env, + void *bufferData, + size_t bufferSize, + napi_finalize finalizeCallback, + void *finalizeHint) noexcept + : Buffer(reinterpret_cast(bufferData), bufferSize), + core_(new NapiExternalBufferCore( + env, + bufferData, + finalizeCallback, + finalizeHint)) { + NapiFinalizingAnonymousReference *ref = + NapiFinalizingReferenceFactory:: + create(nullptr, &NapiExternalBufferCore::finalize, core_); + core_->setFinalizer(ref); + env.addFinalizingReference(ref); + } + + ~NapiExternalBuffer() noexcept override { + core_->onBufferDeleted(); + } + + NapiExternalBuffer(const NapiExternalBuffer &) = delete; + NapiExternalBuffer &operator=(const NapiExternalBuffer &) = delete; + + private: + NapiExternalBufferCore *core_; +}; + +// Wraps script data as hermes::Buffer +class ScriptDataBuffer final : public hermes::Buffer { + public: + ScriptDataBuffer( + const uint8_t *scriptData, + size_t scriptLength, + jsr_data_delete_cb scriptDeleteCallback, + void *deleterData) noexcept + : Buffer(scriptData, scriptLength), + scriptDeleteCallback_(scriptDeleteCallback), + deleterData_(deleterData) {} + + ~ScriptDataBuffer() noexcept override { + if (scriptDeleteCallback_ != nullptr) { + scriptDeleteCallback_(const_cast(data()), deleterData_); + } + } + + ScriptDataBuffer(const ScriptDataBuffer &) = delete; + ScriptDataBuffer &operator=(const ScriptDataBuffer &) = delete; + + private: + jsr_data_delete_cb scriptDeleteCallback_{}; + void *deleterData_{}; +}; + +class JsiBuffer final : public hermes::Buffer { + public: + JsiBuffer(std::shared_ptr buffer) noexcept + : Buffer(buffer->data(), buffer->size()), buffer_(std::move(buffer)) {} + + private: + std::shared_ptr buffer_; +}; + +class JsiSmallVectorBuffer final : public facebook::jsi::Buffer { + public: + JsiSmallVectorBuffer(llvh::SmallVector data) noexcept + : data_(std::move(data)) {} + + size_t size() const override { + return data_.size(); + } + + const uint8_t *data() const override { + return reinterpret_cast(data_.data()); + } + + private: + llvh::SmallVector data_; +}; + +// An implementation of PreparedJavaScript that wraps a BytecodeProvider. +class NapiScriptModel final { + public: + explicit NapiScriptModel( + std::unique_ptr bcProvider, + vm::RuntimeModuleFlags runtimeFlags, + std::string sourceURL, + bool isBytecode) + : bcProvider_(std::move(bcProvider)), + runtimeFlags_(runtimeFlags), + sourceURL_(std::move(sourceURL)), + isBytecode_(isBytecode) {} + + std::shared_ptr bytecodeProvider() const { + return bcProvider_; + } + + vm::RuntimeModuleFlags runtimeFlags() const { + return runtimeFlags_; + } + + const std::string &sourceURL() const { + return sourceURL_; + } + + bool isBytecode() const { + return isBytecode_; + } + + private: + std::shared_ptr bcProvider_; + vm::RuntimeModuleFlags runtimeFlags_; + std::string sourceURL_; + bool isBytecode_{false}; +}; + +// Conversion routines from double to int32, uin32 and int64. +// The code is adapted from V8 source code to match the NAPI for V8 behavior. +// https://github.com/v8/v8/blob/main/src/numbers/conversions-inl.h +// https://github.com/v8/v8/blob/main/src/base/numbers/double.h +class NapiDoubleConversion final { + public: + // Implements most of https://tc39.github.io/ecma262/#sec-toint32. + static int32_t toInt32(double value) noexcept { + if (!std::isnormal(value)) { + return 0; + } + if (value >= std::numeric_limits::min() && + value <= std::numeric_limits::max()) { + // All doubles within these limits are trivially convertable to an int32. + return static_cast(value); + } + uint64_t u64 = toUint64Bits(value); + int exponent = getExponent(u64); + uint64_t bits; + if (exponent < 0) { + if (exponent <= -kSignificandSize) { + return 0; + } + bits = getSignificand(u64) >> -exponent; + } else { + if (exponent > 31) { + return 0; + } + bits = getSignificand(u64) << exponent; + } + return static_cast( + getSign(u64) * static_cast(bits & 0xFFFFFFFFul)); + } + + static uint32_t toUint32(double value) noexcept { + return static_cast(toInt32(value)); + } + + static int64_t toInt64(double value) { + // This code has the NAPI for V8 special behavior. + // The comment from the napi_get_value_int64 code: + // https://github.com/nodejs/node/blob/master/src/js_native_api_v8.cc + // + // 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. + // + if (!std::isnormal(value)) { + return 0; + } + if (value >= static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + if (value <= static_cast(std::numeric_limits::min())) { + return std::numeric_limits::min(); + } + return static_cast(value); + } + + private: + static uint64_t toUint64Bits(double value) noexcept { + uint64_t result; + std::memcpy(&result, &value, sizeof(value)); + return result; + } + + static int getSign(uint64_t u64) noexcept { + return (u64 & kSignMask) == 0 ? 1 : -1; + } + + static int getExponent(uint64_t u64) noexcept { + int biased_e = + static_cast((u64 & kExponentMask) >> kPhysicalSignificandSize); + return biased_e - kExponentBias; + } + + static uint64_t getSignificand(uint64_t u64) noexcept { + return (u64 & kSignificandMask) + kHiddenBit; + } + + static constexpr uint64_t kSignMask = 0x8000'0000'0000'0000; + static constexpr uint64_t kExponentMask = 0x7FF0'0000'0000'0000; + static constexpr uint64_t kSignificandMask = 0x000F'FFFF'FFFF'FFFF; + static constexpr uint64_t kHiddenBit = 0x0010'0000'0000'0000; + static constexpr int kPhysicalSignificandSize = 52; + static constexpr int kSignificandSize = 53; + static constexpr int kExponentBias = 0x3FF + kPhysicalSignificandSize; +}; + +// Max size of the runtime's register stack. +// The runtime register stack needs to be small enough to be allocated on the +// native thread stack in Android (1MiB) and on MacOS's thread stack (512 KiB) +// Calculated by: (thread stack size - size of runtime - +// 8 memory pages for other stuff in the thread) +// constexpr unsigned kMaxNumRegisters = +// (512 * 1024 - sizeof(vm::Runtime) - 4096 * 8) / +// sizeof(vm::PinnedHermesValue); + +template +constexpr std::size_t size(const T (&array)[N]) noexcept { + return N; +} + +template +bool isInEnumRange( + TEnum value, + TEnum lowerBoundInclusive, + TEnum upperBoundInclusive) noexcept { + return lowerBoundInclusive <= value && value <= upperBoundInclusive; +} + +napi_env napiEnv(NapiEnvironment *env) noexcept { + return reinterpret_cast(env); +} + +napi_value napiValue(const vm::PinnedHermesValue *value) noexcept { + return reinterpret_cast( + const_cast(value)); +} + +template +napi_value napiValue(vm::Handle value) noexcept { + return napiValue(value.unsafeGetPinnedHermesValue()); +} + +const vm::PinnedHermesValue *phv(napi_value value) noexcept { + return reinterpret_cast(value); +} + +const vm::PinnedHermesValue *phv(const vm::PinnedHermesValue *value) noexcept { + return value; +} + +NapiReference *asReference(napi_ref ref) noexcept { + return reinterpret_cast(ref); +} + +NapiReference *asReference(void *ref) noexcept { + return reinterpret_cast(ref); +} + +NapiCallbackInfo *asCallbackInfo(napi_callback_info callbackInfo) noexcept { + return reinterpret_cast(callbackInfo); +} + +vm::JSObject *getObjectUnsafe(const vm::HermesValue &value) noexcept { + return reinterpret_cast(value.getObject()); +} + +vm::JSObject *getObjectUnsafe(napi_value value) noexcept { + return getObjectUnsafe(*phv(value)); +} + +size_t copyASCIIToUTF8( + llvh::ArrayRef input, + char *buf, + size_t maxCharacters) noexcept { + size_t size = std::min(input.size(), maxCharacters); + std::char_traits::copy(buf, input.data(), size); + return size; +} + +size_t utf8LengthWithReplacements(llvh::ArrayRef input) { + size_t length{0}; + for (const char16_t *cur = input.begin(), *end = input.end(); cur < end;) { + char16_t c = *cur++; + if (LLVM_LIKELY(c <= 0x7F)) { + ++length; + } else if (c <= 0x7FF) { + length += 2; + } else if (isLowSurrogate(c)) { + // Unpaired low surrogate. + length += 3; // replacement char is 0xFFFD + } else if (isHighSurrogate(c)) { + // Leading high surrogate. See if the next character is a low surrogate. + if (LLVM_UNLIKELY(cur == end || !isLowSurrogate(*cur))) { + // Trailing or unpaired high surrogate. + length += 3; // replacement char is 0xFFFD + } else { + // The surrogate pair encodes a code point in range 0x10000-0x10FFFF + // which is encoded as four UTF-8 characters. + cur++; // to get the low surrogate char + length += 4; + } + } else { + // Not a surrogate. + length += 3; + } + } + + return length; +} + +size_t convertUTF16ToUTF8WithReplacements( + llvh::ArrayRef input, + char *buf, + size_t bufSize) { + char *curBuf = buf; + char *endBuf = buf + bufSize; + for (const char16_t *cur = input.begin(), *end = input.end(); + cur < end && curBuf < endBuf;) { + char16_t c = *cur++; + // ASCII fast-path. + if (LLVM_LIKELY(c <= 0x7F)) { + *curBuf++ = c; + continue; + } + + char32_t c32; + if (LLVM_LIKELY(c <= 0x7FF)) { + c32 = c; + } else if (isLowSurrogate(c)) { + // Unpaired low surrogate. + c32 = UNICODE_REPLACEMENT_CHARACTER; + } else if (isHighSurrogate(c)) { + // Leading high surrogate. See if the next character is a low surrogate. + if (LLVM_UNLIKELY(cur == end || !isLowSurrogate(*cur))) { + // Trailing or unpaired high surrogate. + c32 = UNICODE_REPLACEMENT_CHARACTER; + } else { + // Decode surrogate pair and increment, because we consumed two chars. + c32 = utf16SurrogatePairToCodePoint(c, *cur++); + } + } else { + // Not a surrogate. + c32 = c; + } + + char buff[UTF8CodepointMaxBytes]; + char *ptr = buff; + encodeUTF8(ptr, c32); + size_t u8Length = static_cast(ptr - buff); + if (curBuf + u8Length <= endBuf) { + std::char_traits::copy(curBuf, buff, u8Length); + curBuf += u8Length; + } else { + break; + } + } + + return static_cast(curBuf - buf); +} + +//============================================================================= +// NapiEnvironment implementation +//============================================================================= + +NapiEnvironment::NapiEnvironment( + vm::Runtime &runtime, + bool isInspectable, + std::shared_ptr scriptCache, + const vm::RuntimeConfig &runtimeConfig) noexcept + : pendingFinalizers_(NapiPendingFinalizers::create()), + runtime_(runtime), + scriptCache_(std::move(scriptCache)), + isInspectable_(isInspectable) { + switch (runtimeConfig.getCompilationMode()) { + case vm::SmartCompilation: + compileFlags_.lazy = true; + // (Leaves thresholds at default values) + break; + case vm::ForceEagerCompilation: + compileFlags_.lazy = false; + break; + case vm::ForceLazyCompilation: + compileFlags_.lazy = true; + compileFlags_.preemptiveFileCompilationThreshold = 0; + compileFlags_.preemptiveFunctionCompilationThreshold = 0; + break; + } + + compileFlags_.enableGenerator = runtimeConfig.getEnableGenerator(); + compileFlags_.emitAsyncBreakCheck = runtimeConfig.getAsyncBreakCheckInEval(); + + runtime_.addCustomRootsFunction([this](vm::GC *, vm::RootAcceptor &acceptor) { + napiValueStack_.forEach([&](const vm::PinnedHermesValue &value) { + acceptor.accept(const_cast(value)); + }); + NapiReference::getGCRoots(*this, references_, acceptor); + NapiReference::getGCRoots(*this, finalizingReferences_, acceptor); + if (!thrownJSError_.isEmpty()) { + acceptor.accept(thrownJSError_); + } + if (!lastUnhandledRejection_.isEmpty()) { + acceptor.accept(lastUnhandledRejection_); + } + for (vm::PinnedHermesValue &value : predefinedValues_) { + acceptor.accept(value); + } + NapiOrderedSet::getGCRoots(orderedSets_, acceptor); + for (auto &entry : uniqueStrings_) { + if (vm::PinnedHermesValue *root = entry.second->getGCRoot(*this)) { + acceptor.accept(*root); + } + } + }); + runtime_.addCustomWeakRootsFunction( + [this](vm::GC *, vm::WeakRootAcceptor &acceptor) { + NapiReference::getGCWeakRoots(*this, references_, acceptor); + NapiReference::getGCWeakRoots(*this, finalizingReferences_, acceptor); + }); + + vm::GCScope gcScope{runtime_}; + auto setPredefinedProperty = + [this](NapiPredefined key, vm::HermesValue value) noexcept { + predefinedValues_[static_cast(key)] = value; + }; + setPredefinedProperty( + NapiPredefined::Promise, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("Promise")))); + setPredefinedProperty( + NapiPredefined::allRejections, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("allRejections")))); + setPredefinedProperty( + NapiPredefined::code, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("code")))); + setPredefinedProperty( + NapiPredefined::hostFunction, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("hostFunction")))); + setPredefinedProperty( + NapiPredefined::napi_externalValue, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().createNotUniquedLazySymbol( + vm::createASCIIRef( + "napi.externalValue.735e14c9-354f-489b-9f27-02acbc090975")))); + setPredefinedProperty( + NapiPredefined::napi_typeTag, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().createNotUniquedLazySymbol( + vm::createASCIIRef( + "napi.typeTag.026ae0ec-b391-49da-a935-0cab733ab615")))); + setPredefinedProperty( + NapiPredefined::onHandled, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("onHandled")))); + setPredefinedProperty( + NapiPredefined::onUnhandled, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("onUnhandled")))); + setPredefinedProperty( + NapiPredefined::reject, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("reject")))); + setPredefinedProperty( + NapiPredefined::resolve, + vm::HermesValue::encodeSymbolValue( + runtime_.getIdentifierTable().registerLazyIdentifier( + vm::createASCIIRef("resolve")))); + + CRASH_IF_FALSE(enablePromiseRejectionTracker() == napi_ok); +} + +NapiEnvironment::~NapiEnvironment() { + pendingFinalizers_->applyPendingFinalizers(this); + pendingFinalizers_ = nullptr; + + isShuttingDown_ = true; + if (instanceData_) { + instanceData_->finalize(*this); + instanceData_ = nullptr; + } + + // First we must finalize those references that have `napi_finalizer` + // callbacks. The reason is that addons might store other references which + // they delete during their `napi_finalizer` callbacks. If we deleted such + // references here first, they would be doubly deleted when the + // `napi_finalizer` deleted them subsequently. + NapiReference::finalizeAll(*this, finalizerQueue_); + NapiReference::finalizeAll(*this, finalizingReferences_); + NapiReference::deleteAll( + *this, references_, NapiReference::ReasonToDelete::EnvironmentShutdown); + + CRASH_IF_FALSE(finalizerQueue_.isEmpty()); + CRASH_IF_FALSE(finalizingReferences_.isEmpty()); + CRASH_IF_FALSE(references_.isEmpty()); +} + +napi_status NapiEnvironment::incRefCount() noexcept { + refCount_++; + return napi_status::napi_ok; +} + +napi_status NapiEnvironment::decRefCount() noexcept { + if (--refCount_ == 0) { + delete this; + } + return napi_status::napi_ok; +} + +vm::Runtime &NapiEnvironment::runtime() noexcept { + return runtime_; +} + +NapiStableAddressStack + &NapiEnvironment::napiValueStack() noexcept { + return napiValueStack_; +} + +//--------------------------------------------------------------------------- +// Native error handling methods +//--------------------------------------------------------------------------- + +napi_status NapiEnvironment::getLastNativeError( + const NapiNativeError **result) noexcept { + CHECK_ARG(result); + if (lastError_.error_code == napi_ok) { + lastError_ = {nullptr, 0, 0, napi_ok}; + } + *result = &lastError_; + return napi_ok; +} + +template +napi_status NapiEnvironment::setLastNativeError( + napi_status status, + const char *fileName, + uint32_t line, + TArgs &&...args) noexcept { + // Warning: Keep in-sync with napi_status enum + static constexpr const char *errorMessages[] = { + "", + "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", + }; + + // 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 lastStatus = napi_would_deadlock; + static_assert( + size(errorMessages) == lastStatus + 1, + "Count of error messages must match count of error values"); + + if (status < napi_ok || status >= lastStatus) { + status = napi_generic_failure; + } + + lastErrorMessage_.clear(); + NapiStringBuilder sb{ + NapiStringBuilder::AdoptString, std::move(lastErrorMessage_)}; + sb.append(errorMessages[status]); + if (sizeof...(args) > 0) { + sb.append(": ", std::forward(args)...); + } + sb.append("\nFile: ", fileName); + sb.append("\nLine: ", line); + lastErrorMessage_ = std::move(sb.str()); + // TODO: Find a better way to provide the extended error message + lastError_ = {errorMessages[status], 0, 0, status}; + +#if defined(_WIN32) && !defined(NDEBUG) +// DebugBreak(); +#endif + + return status; +} + +napi_status NapiEnvironment::clearLastNativeError() noexcept { + return lastError_.error_code = napi_ok; +} + +//----------------------------------------------------------------------------- +// Methods to support JS error handling +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createJSError( + const vm::PinnedHermesValue &errorPrototype, + napi_value code, + napi_value message, + napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + CHECK_STRING_ARG(message); + vm::Handle errorHandle = makeHandle( + vm::JSError::create(runtime_, makeHandle(&errorPrototype))); + CHECK_NAPI(checkJSErrorStatus( + vm::JSError::setMessage(errorHandle, runtime_, makeHandle(message)))); + CHECK_NAPI(setJSErrorCode(errorHandle, code, nullptr)); + return scope.setResult(std::move(errorHandle)); +} + +napi_status NapiEnvironment::createJSError( + napi_value code, + napi_value message, + napi_value *result) noexcept { + return createJSError(runtime_.ErrorPrototype, code, message, result); +} + +napi_status NapiEnvironment::createJSTypeError( + napi_value code, + napi_value message, + napi_value *result) noexcept { + return createJSError(runtime_.TypeErrorPrototype, code, message, result); +} + +napi_status NapiEnvironment::createJSRangeError( + napi_value code, + napi_value message, + napi_value *result) noexcept { + return createJSError(runtime_.RangeErrorPrototype, code, message, result); +} + +napi_status NapiEnvironment::isJSError( + napi_value value, + bool *result) noexcept { + CHECK_ARG(value); + return setResult(vm::vmisa(*phv(value)), result); +} + +napi_status NapiEnvironment::throwJSError(napi_value error) noexcept { + CHECK_ARG(error); + runtime_.setThrownValue(*phv(error)); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return clearLastNativeError(); +} + +napi_status NapiEnvironment::throwJSError( + const vm::PinnedHermesValue &prototype, + const char *code, + const char *message) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + napi_value messageValue; + CHECK_NAPI(createStringUTF8(message, &messageValue)); + + vm::Handle errorHandle = makeHandle( + vm::JSError::create(runtime_, makeHandle(&prototype))); + CHECK_NAPI( + checkJSErrorStatus(vm::JSError::recordStackTrace(errorHandle, runtime_))); + CHECK_NAPI( + checkJSErrorStatus(vm::JSError::setupStack(errorHandle, runtime_))); + CHECK_NAPI(checkJSErrorStatus(vm::JSError::setMessage( + errorHandle, runtime_, makeHandle(messageValue)))); + CHECK_NAPI(setJSErrorCode(errorHandle, nullptr, code)); + + runtime_.setThrownValue(errorHandle.getHermesValue()); + + // any VM calls after this point and before returning + // to the javascript invoker will fail + return clearLastNativeError(); +} + +napi_status NapiEnvironment::throwJSError( + const char *code, + const char *message) noexcept { + return throwJSError(runtime_.ErrorPrototype, code, message); +} + +napi_status NapiEnvironment::throwJSTypeError( + const char *code, + const char *message) noexcept { + return throwJSError(runtime_.TypeErrorPrototype, code, message); +} + +napi_status NapiEnvironment::throwJSRangeError( + const char *code, + const char *message) noexcept { + return throwJSError(runtime_.RangeErrorPrototype, code, message); +} + +napi_status NapiEnvironment::setJSErrorCode( + vm::Handle error, + napi_value code, + const char *codeCString) noexcept { + if (code || codeCString) { + if (code) { + CHECK_STRING_ARG(code); + } else { + CHECK_NAPI(createStringUTF8(codeCString, &code)); + } + return setPredefinedProperty(error, NapiPredefined::code, code, nullptr); + } + return napi_ok; +} + +//----------------------------------------------------------------------------- +// Methods to support catching JS exceptions +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::isJSErrorPending(bool *result) noexcept { + return setResult(!thrownJSError_.isEmpty(), result); +} + +napi_status NapiEnvironment::checkPendingJSError() noexcept { + RETURN_STATUS_IF_FALSE(thrownJSError_.isEmpty(), napi_pending_exception); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::getAndClearPendingJSError( + napi_value *result) noexcept { + if (thrownJSError_.isEmpty()) { + return getUndefined(result); + } + return setResult(std::exchange(thrownJSError_, EmptyHermesValue), result); +} + +napi_status NapiEnvironment::checkJSErrorStatus( + vm::ExecutionStatus hermesStatus, + napi_status status) noexcept { + if (LLVM_LIKELY(hermesStatus != vm::ExecutionStatus::EXCEPTION)) { + return napi_ok; + } + + thrownJSError_ = runtime_.getThrownValue(); + runtime_.clearThrownValue(); + return status; +} + +template +napi_status NapiEnvironment::checkJSErrorStatus( + const vm::CallResult &callResult, + napi_status status) noexcept { + return checkJSErrorStatus(callResult.getStatus(), status); +} + +//----------------------------------------------------------------------------- +// Getters for common singletons +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::getGlobal(napi_value *result) noexcept { + return setPredefinedResult( + runtime_.getGlobal().unsafeGetPinnedHermesValue(), result); +} + +napi_status NapiEnvironment::getUndefined(napi_value *result) noexcept { + return setPredefinedResult( + runtime_.getUndefinedValue().unsafeGetPinnedHermesValue(), result); +} + +const vm::PinnedHermesValue &NapiEnvironment::getUndefined() noexcept { + return *runtime_.getUndefinedValue().unsafeGetPinnedHermesValue(); +} + +napi_status NapiEnvironment::getNull(napi_value *result) noexcept { + return setPredefinedResult( + runtime_.getNullValue().unsafeGetPinnedHermesValue(), result); +} + +//----------------------------------------------------------------------------- +// Method to get value type +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::typeOf( + napi_value value, + napi_valuetype *result) noexcept { + CHECK_ARG(value); + CHECK_ARG(result); + + const vm::PinnedHermesValue *hv = phv(value); + + if (hv->isNumber()) { + *result = napi_number; + } else if (hv->isString()) { + *result = napi_string; + } else if (hv->isObject()) { + if (vm::vmisa(*hv)) { + *result = napi_function; + } else if (getExternalObjectValue(*hv)) { + *result = napi_external; + } else { + *result = napi_object; + } + } else if (hv->isBool()) { + *result = napi_boolean; + } else if (hv->isUndefined() || hv->isEmpty()) { + *result = napi_undefined; + } else if (hv->isSymbol()) { + *result = napi_symbol; + } else if (hv->isNull()) { + *result = napi_null; + } else if (hv->isBigInt()) { + *result = napi_bigint; + } else { + // Should not get here unless Hermes has added some new kind of value. + return ERROR_STATUS(napi_invalid_arg, "Unknown value type"); + } + + return clearLastNativeError(); +} + +//----------------------------------------------------------------------------- +// Methods to work with Booleans +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::getBoolean( + bool value, + napi_value *result) noexcept { + return setPredefinedResult( + runtime_.getBoolValue(value).unsafeGetPinnedHermesValue(), result); +} + +napi_status NapiEnvironment::getBooleanValue( + napi_value value, + bool *result) noexcept { + CHECK_ARG(value); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE(phv(value)->isBool(), napi_boolean_expected); + return setResult(phv(value)->getBool(), result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Numbers +//----------------------------------------------------------------------------- + +template , bool>> +napi_status NapiEnvironment::createNumber( + T value, + napi_value *result) noexcept { + return setResult( + vm::HermesValue::encodeUntrustedNumberValue(static_cast(value)), + result); +} + +napi_status NapiEnvironment::getNumberValue( + napi_value value, + double *result) noexcept { + CHECK_ARG(value); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE(phv(value)->isNumber(), napi_number_expected); + return setResult(phv(value)->getDouble(), result); +} + +napi_status NapiEnvironment::getNumberValue( + napi_value value, + int32_t *result) noexcept { + CHECK_ARG(value); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE(phv(value)->isNumber(), napi_number_expected); + return setResult( + NapiDoubleConversion::toInt32(phv(value)->getDouble()), result); +} + +napi_status NapiEnvironment::getNumberValue( + napi_value value, + uint32_t *result) noexcept { + CHECK_ARG(value); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE(phv(value)->isNumber(), napi_number_expected); + return setResult( + NapiDoubleConversion::toUint32(phv(value)->getDouble()), result); +} + +napi_status NapiEnvironment::getNumberValue( + napi_value value, + int64_t *result) noexcept { + CHECK_ARG(value); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE(phv(value)->isNumber(), napi_number_expected); + return setResult( + NapiDoubleConversion::toInt64(phv(value)->getDouble()), result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Strings +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createStringASCII( + const char *str, + size_t length, + napi_value *result) noexcept { + return setResult( + vm::StringPrimitive::createEfficient( + runtime_, llvh::makeArrayRef(str, length)), + result); +} + +napi_status NapiEnvironment::createStringLatin1( + const char *str, + size_t length, + napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + CHECK_ARG(str); + if (length == NAPI_AUTO_LENGTH) { + length = std::char_traits::length(str); + } + RETURN_STATUS_IF_FALSE( + length <= static_cast(std::numeric_limits::max()), + napi_invalid_arg); + + if (isAllASCII(str, str + length)) { + return scope.setResult(createStringASCII(str, length, result)); + } + + // Latin1 has the same codes as Unicode. + // We just need to expand char to char16_t. + std::u16string u16str(length, u'\0'); + // Cast to unsigned to avoid signed value expansion to 16 bit. + const uint8_t *ustr = reinterpret_cast(str); + std::copy(ustr, ustr + length, &u16str[0]); + + return scope.setResult( + vm::StringPrimitive::createEfficient(runtime_, std::move(u16str))); +} + +napi_status NapiEnvironment::createStringUTF8( + const char *str, + size_t length, + napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + CHECK_ARG(str); + if (length == NAPI_AUTO_LENGTH) { + length = std::char_traits::length(str); + } + RETURN_STATUS_IF_FALSE( + length <= static_cast(std::numeric_limits::max()), + napi_invalid_arg); + + if (isAllASCII(str, str + length)) { + return scope.setResult(createStringASCII(str, length, result)); + } + + std::u16string u16str; + CHECK_NAPI(convertUTF8ToUTF16(str, length, u16str)); + return scope.setResult( + vm::StringPrimitive::createEfficient(runtime_, std::move(u16str))); +} + +napi_status NapiEnvironment::createStringUTF8( + const char *str, + napi_value *result) noexcept { + return createStringUTF8(str, NAPI_AUTO_LENGTH, result); +} + +napi_status NapiEnvironment::createStringUTF16( + const char16_t *str, + size_t length, + napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + CHECK_ARG(str); + if (length == NAPI_AUTO_LENGTH) { + length = std::char_traits::length(str); + } + RETURN_STATUS_IF_FALSE( + length <= static_cast(std::numeric_limits::max()), + napi_invalid_arg); + + return scope.setResult(vm::StringPrimitive::createEfficient( + runtime_, llvh::makeArrayRef(str, length))); +} + +// 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 nullptr, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is nullptr. +napi_status NapiEnvironment::getStringValueLatin1( + napi_value value, + char *buf, + size_t bufSize, + size_t *result) noexcept { + NapiHandleScope scope{*this}; + CHECK_STRING_ARG(value); + vm::StringView view = vm::StringPrimitive::createStringView( + runtime_, makeHandle(value)); + + if (buf == nullptr) { + return setResult(view.length(), result); + } else if (bufSize != 0) { + size_t copied = std::min(bufSize - 1, view.length()); + for (auto cur = view.begin(), end = view.begin() + copied; cur < end; + ++cur) { + *buf++ = static_cast(*cur); + } + *buf = '\0'; + return setOptionalResult(std::move(copied), result); + } else { + return setOptionalResult(static_cast(0), result); + } +} + +// 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 nullptr, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is nullptr. +napi_status NapiEnvironment::getStringValueUTF8( + napi_value value, + char *buf, + size_t bufSize, + size_t *result) noexcept { + NapiHandleScope scope{*this}; + CHECK_STRING_ARG(value); + vm::StringView view = vm::StringPrimitive::createStringView( + runtime_, makeHandle(value)); + + if (buf == nullptr) { + return setResult( + view.isASCII() || view.length() == 0 + ? view.length() + : utf8LengthWithReplacements( + vm::UTF16Ref(view.castToChar16Ptr(), view.length())), + result); + } else if (bufSize != 0) { + size_t copied = view.length() > 0 ? view.isASCII() + ? copyASCIIToUTF8( + vm::ASCIIRef(view.castToCharPtr(), view.length()), + buf, + bufSize - 1) + : convertUTF16ToUTF8WithReplacements( + vm::UTF16Ref(view.castToChar16Ptr(), view.length()), + buf, + bufSize - 1) + : 0; + buf[copied] = '\0'; + return setOptionalResult(std::move(copied), result); + } else { + return setOptionalResult(static_cast(0), result); + } +} + +// 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 nullptr, 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 nullptr. +napi_status NapiEnvironment::getStringValueUTF16( + napi_value value, + char16_t *buf, + size_t bufSize, + size_t *result) noexcept { + NapiHandleScope scope{*this}; + CHECK_STRING_ARG(value); + vm::StringView view = vm::StringPrimitive::createStringView( + runtime_, makeHandle(value)); + + if (buf == nullptr) { + return setResult(view.length(), result); + } else if (bufSize != 0) { + size_t copied = std::min(bufSize - 1, view.length()); + std::copy(view.begin(), view.begin() + copied, buf); + buf[copied] = '\0'; + return setOptionalResult(std::move(copied), result); + } else { + return setOptionalResult(static_cast(0), result); + } +} + +napi_status NapiEnvironment::convertUTF8ToUTF16( + const char *utf8, + size_t length, + std::u16string &out) noexcept { + // length is the number of input bytes + out.resize(length); + const llvh::UTF8 *sourceStart = reinterpret_cast(utf8); + const llvh::UTF8 *sourceEnd = sourceStart + length; + llvh::UTF16 *targetStart = reinterpret_cast(&out[0]); + llvh::UTF16 *targetEnd = targetStart + out.size(); + llvh::ConversionResult convRes = ConvertUTF8toUTF16( + &sourceStart, + sourceEnd, + &targetStart, + targetEnd, + llvh::lenientConversion); + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( + convRes != llvh::ConversionResult::targetExhausted, + napi_generic_failure, + "not enough space allocated for UTF16 conversion"); + out.resize(reinterpret_cast(targetStart) - &out[0]); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::getUniqueSymbolID( + const char *utf8, + size_t length, + vm::MutableHandle *result) noexcept { + napi_value strValue; + CHECK_NAPI(createStringUTF8(utf8, length, &strValue)); + return getUniqueSymbolID(strValue, result); +} + +napi_status NapiEnvironment::getUniqueSymbolID( + napi_value strValue, + vm::MutableHandle *result) noexcept { + CHECK_STRING_ARG(strValue); + return setResult( + vm::stringToSymbolID( + runtime_, vm::createPseudoHandle(phv(strValue)->getString())), + result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Symbols +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createSymbol( + napi_value description, + napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + vm::MutableHandle descString{runtime_}; + if (description != nullptr) { + CHECK_STRING_ARG(description); + descString = phv(description)->getString(); + } else { + // If description is undefined, the descString will eventually be "". + descString = runtime_.getPredefinedString(vm::Predefined::emptyString); + } + return scope.setResult(runtime_.getIdentifierTable().createNotUniquedSymbol( + runtime_, descString)); +} + +//----------------------------------------------------------------------------- +// Methods to work with BigInt +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createBigIntFromInt64( + int64_t value, + napi_value *result) { + NapiHandleScope scope{*this, result}; + return scope.setResult(vm::BigIntPrimitive::fromSigned(runtime_, value)); +} + +napi_status NapiEnvironment::createBigIntFromUint64( + uint64_t value, + napi_value *result) { + NapiHandleScope scope{*this, result}; + return scope.setResult(vm::BigIntPrimitive::fromUnsigned(runtime_, value)); +} + +napi_status NapiEnvironment::createBigIntFromWords( + int signBit, + size_t wordCount, + const uint64_t *words, + napi_value *result) { + NapiHandleScope scope{*this, result}; + CHECK_ARG(words); + RETURN_STATUS_IF_FALSE(wordCount <= INT_MAX, napi_invalid_arg); + + if (signBit) { + // Use 2's complement algorithm to represent negative numbers. + llvh::SmallVector negativeValue{words, words + wordCount}; + + // a. flip all bits + for (uint64_t &entry : negativeValue) { + entry = ~entry; + } + // b. add 1 + for (size_t i = 0; i < negativeValue.size(); ++i) { + if (++negativeValue[i] >= 1) { + break; // No need to carry so exit early. + } + } + words = negativeValue.data(); + } + + const uint8_t *ptr = reinterpret_cast(words); + const uint32_t size = static_cast(wordCount * sizeof(uint64_t)); + return scope.setResult( + vm::BigIntPrimitive::fromBytes(runtime_, llvh::makeArrayRef(ptr, size))); +} + +napi_status NapiEnvironment::getBigIntValueInt64( + napi_value value, + int64_t *result, + bool *lossless) { + CHECK_ARG(value); + CHECK_ARG(result); + CHECK_ARG(lossless); + RETURN_STATUS_IF_FALSE(phv(value)->isBigInt(), napi_bigint_expected); + vm::BigIntPrimitive *bigInt = phv(value)->getBigInt(); + *lossless = + bigInt->isTruncationToSingleDigitLossless(/*signedTruncation:*/ true); + *result = static_cast(bigInt->truncateToSingleDigit()); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::getBigIntValueUint64( + napi_value value, + uint64_t *result, + bool *lossless) { + CHECK_ARG(value); + CHECK_ARG(result); + CHECK_ARG(lossless); + RETURN_STATUS_IF_FALSE(phv(value)->isBigInt(), napi_bigint_expected); + vm::BigIntPrimitive *bigInt = phv(value)->getBigInt(); + *lossless = + bigInt->isTruncationToSingleDigitLossless(/*signedTruncation:*/ false); + *result = bigInt->truncateToSingleDigit(); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::getBigIntValueWords( + napi_value value, + int *signBit, + size_t *wordCount, + uint64_t *words) { + CHECK_ARG(value); + CHECK_ARG(wordCount); + RETURN_STATUS_IF_FALSE(phv(value)->isBigInt(), napi_bigint_expected); + vm::BigIntPrimitive *bigInt = phv(value)->getBigInt(); + + if (signBit == nullptr && words == nullptr) { + *wordCount = bigInt->getDigits().size(); + } else { + CHECK_ARG(signBit); + CHECK_ARG(words); + llvh::ArrayRef digits = bigInt->getDigits(); + *wordCount = std::min(*wordCount, digits.size()); + std::memcpy(words, digits.begin(), *wordCount * sizeof(uint64_t)); + *signBit = bigInt->sign() ? 1 : 0; + if (*signBit) { + // negate negative numbers, and then add a "-" to the output. + // a. flip all bits + for (size_t i = 0; i < *wordCount; ++i) { + words[i] = ~words[i]; + } + // b. add 1 + for (size_t i = 0; i < *wordCount; ++i) { + if (++words[i] >= 1) { + break; // No need to carry so exit early. + } + } + } + } + + return clearLastNativeError(); +} + +//----------------------------------------------------------------------------- +// Methods to coerce values using JS coercion rules +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::coerceToBoolean( + napi_value value, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(value); + return scope.setResult(vm::toBoolean(*phv(value))); +} + +napi_status NapiEnvironment::coerceToNumber( + napi_value value, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(value); + return scope.setResult(vm::toNumber_RJS(runtime_, makeHandle(value))); +} + +napi_status NapiEnvironment::coerceToObject( + napi_value value, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(value); + return scope.setResult(vm::toObject(runtime_, makeHandle(value))); +} + +napi_status NapiEnvironment::coerceToString( + napi_value value, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(value); + return scope.setResult(vm::toString_RJS(runtime_, makeHandle(value))); +} + +//----------------------------------------------------------------------------- +// Methods to work with Objects +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createObject(napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + return scope.setResult(vm::JSObject::create(runtime_)); +} + +napi_status NapiEnvironment::getPrototype( + napi_value object, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + napi_value objValue{}; + CHECK_NAPI(coerceToObject(object, &objValue)); + return scope.setResult(vm::JSObject::getPrototypeOf( + vm::createPseudoHandle(getObjectUnsafe(objValue)), runtime_)); +} + +napi_status NapiEnvironment::getForInPropertyNames( + napi_value object, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return scope.setResult( + getForInPropertyNames(objValue, napi_key_numbers_to_strings, result)); +} + +napi_status NapiEnvironment::getForInPropertyNames( + napi_value object, + napi_key_conversion keyConversion, + napi_value *result) noexcept { + // Hermes optimizes retrieving property names for the 'for..in' implementation + // by caching its results. This function takes the advantage from using it. + uint32_t beginIndex; + uint32_t endIndex; + vm::CallResult> keyStorage = + vm::getForInPropertyNames( + runtime_, makeHandle(object), beginIndex, endIndex); + CHECK_NAPI(checkJSErrorStatus(keyStorage)); + return convertKeyStorageToArray( + *keyStorage, beginIndex, endIndex - beginIndex, keyConversion, result); +} + +napi_status NapiEnvironment::getAllPropertyNames( + napi_value object, + napi_key_collection_mode keyMode, + napi_key_filter keyFilter, + napi_key_conversion keyConversion, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + RETURN_STATUS_IF_FALSE( + isInEnumRange(keyMode, napi_key_include_prototypes, napi_key_own_only), + napi_invalid_arg); + RETURN_STATUS_IF_FALSE( + isInEnumRange( + keyConversion, napi_key_keep_numbers, napi_key_numbers_to_strings), + napi_invalid_arg); + + // We can use optimized code if object has no parent. + bool hasParent; + { + napi_value parent; + CHECK_NAPI(getPrototype(object, &parent)); + hasParent = phv(parent)->isObject(); + } + + // The fast path used for the 'for..in' implementation. + if (keyFilter == (napi_key_enumerable | napi_key_skip_symbols) && + (keyMode == napi_key_include_prototypes || !hasParent)) { + return scope.setResult( + getForInPropertyNames(objValue, keyConversion, result)); + } + + // Flags to request own keys + // Include non-enumerable for proper shadow checks. + vm::OwnKeysFlags ownKeyFlags = + vm::OwnKeysFlags() + .setIncludeNonSymbols((keyFilter & napi_key_skip_strings) == 0) + .setIncludeSymbols((keyFilter & napi_key_skip_symbols) == 0) + .plusIncludeNonEnumerable(); + + // Use the simple path for own properties without extra filters. + if ((keyMode == napi_key_own_only || !hasParent) && + (keyFilter & (napi_key_writable | napi_key_configurable)) == 0) { + vm::CallResult> ownKeysRes = + vm::JSObject::getOwnPropertyKeys( + makeHandle(objValue), + runtime_, + ownKeyFlags.setIncludeNonEnumerable( + (keyFilter & napi_key_enumerable) == 0)); + CHECK_NAPI(checkJSErrorStatus(ownKeysRes)); + if (keyConversion == napi_key_numbers_to_strings) { + CHECK_NAPI(convertToStringKeys(*ownKeysRes)); + } + return scope.setResult(std::move(*ownKeysRes)); + } + + // Collect all properties into the keyStorage. + vm::CallResult> keyStorageRes = + makeMutableHandle(vm::BigStorage::create(runtime_, 16)); + CHECK_NAPI(checkJSErrorStatus(keyStorageRes)); + uint32_t size{0}; + + // Make sure that we do not include into the result properties that were + // shadowed by the derived objects. + bool useShadowTracking = keyMode == napi_key_include_prototypes && hasParent; + NapiOrderedSet shadowIndexes; + NapiOrderedSet shadowStrings( + *this, [](const vm::HermesValue &item1, const vm::HermesValue &item2) { + return item1.getString()->compare(item2.getString()); + }); + NapiOrderedSet shadowSymbols( + *this, [](const vm::HermesValue &item1, const vm::HermesValue &item2) { + vm::SymbolID::RawType rawItem1 = item1.getSymbol().unsafeGetRaw(); + vm::SymbolID::RawType rawItem2 = item2.getSymbol().unsafeGetRaw(); + return rawItem1 < rawItem2 ? -1 : rawItem1 > rawItem2 ? 1 : 0; + }); + + // Should we apply the filter? + bool useFilter = + (keyFilter & + (napi_key_writable | napi_key_enumerable | napi_key_configurable)) != 0; + + // Keep the mutable variables outside of loop for efficiency + vm::MutableHandle currentObj( + runtime_, getObjectUnsafe(objValue)); + vm::MutableHandle<> prop{runtime_}; + OptValue propIndexOpt; + vm::MutableHandle propString{runtime_}; + + while (currentObj.get()) { + vm::GCScope gcScope{runtime_}; + + vm::CallResult> props = + vm::JSObject::getOwnPropertyKeys(currentObj, runtime_, ownKeyFlags); + CHECK_NAPI(checkJSErrorStatus(props)); + + vm::GCScope::Marker marker = gcScope.createMarker(); + for (uint32_t i = 0, end = props.getValue()->getEndIndex(); i < end; ++i) { + gcScope.flushToMarker(marker); + prop = props.getValue()->at(runtime_, i).unboxToHV(runtime_); + + // Do not add a property if it is overriden in the derived object. + if (useShadowTracking) { + if (prop->isString()) { + propString = vm::Handle::vmcast(prop); + // See if the property name is an index + propIndexOpt = vm::toArrayIndex( + vm::StringPrimitive::createStringView(runtime_, propString)); + if (propIndexOpt) { + if (!shadowIndexes.insert(propIndexOpt.getValue())) { + continue; + } + } else { + if (!shadowStrings.insert(prop.getHermesValue())) { + continue; + } + } + } else if (prop->isNumber()) { + propIndexOpt = doubleToArrayIndex(prop->getNumber()); + assert(propIndexOpt && "Invalid property index"); + if (!shadowIndexes.insert(propIndexOpt.getValue())) { + continue; + } + } else if (prop->isSymbol()) { + if (!shadowSymbols.insert(prop.getHermesValue())) { + continue; + } + } + } + + // Apply filter for the property descriptor flags + if (useFilter) { + vm::MutableHandle tmpSymbolStorage{runtime_}; + vm::ComputedPropertyDescriptor desc; + vm::CallResult hasDescriptorRes = + vm::JSObject::getOwnComputedPrimitiveDescriptor( + currentObj, + runtime_, + prop, + vm::JSObject::IgnoreProxy::No, + tmpSymbolStorage, + desc); + CHECK_NAPI(checkJSErrorStatus(hasDescriptorRes)); + if (*hasDescriptorRes) { + if ((keyFilter & napi_key_writable) != 0 && !desc.flags.writable) { + continue; + } + if ((keyFilter & napi_key_enumerable) != 0 && + !desc.flags.enumerable) { + continue; + } + if ((keyFilter & napi_key_configurable) != 0 && + !desc.flags.configurable) { + continue; + } + } + } + + CHECK_NAPI(checkJSErrorStatus( + vm::BigStorage::push_back(*keyStorageRes, runtime_, prop))); + ++size; + } + + // Continue to follow the prototype chain. + vm::CallResult> parentRes = + vm::JSObject::getPrototypeOf(currentObj, runtime_); + CHECK_NAPI(checkJSErrorStatus(parentRes)); + currentObj = std::move(*parentRes); + } + + return scope.setResult( + convertKeyStorageToArray(*keyStorageRes, 0, size, keyConversion, result)); +} + +napi_status NapiEnvironment::convertKeyStorageToArray( + vm::Handle keyStorage, + uint32_t startIndex, + uint32_t length, + napi_key_conversion keyConversion, + napi_value *result) noexcept { + vm::CallResult> res = + vm::JSArray::create(runtime_, length, length); + CHECK_NAPI(checkJSErrorStatus(res)); + vm::Handle array = *res; + if (keyConversion == napi_key_numbers_to_strings) { + vm::GCScopeMarkerRAII marker{runtime_}; + vm::MutableHandle<> key{runtime_}; + for (size_t i = 0; i < length; ++i) { + key = makeHandle(keyStorage->at(runtime_, startIndex + i)); + if (key->isNumber()) { + CHECK_NAPI(convertIndexToString(key->getNumber(), &key)); + } + vm::JSArray::setElementAt(array, runtime_, i, key); + marker.flush(); + } + } else { + vm::JSArray::setStorageEndIndex(array, runtime_, length); + vm::NoAllocScope noAlloc{runtime_}; + vm::JSArray *arrPtr = array.get(); + for (uint32_t i = 0; i < length; ++i) { + vm::JSArray::unsafeSetExistingElementAt( + arrPtr, + runtime_, + i, + vm::SmallHermesValue::encodeHermesValue( + keyStorage->at(runtime_, startIndex + i), runtime_)); + } + } + return setResult(array.getHermesValue(), result); +} + +napi_status NapiEnvironment::convertToStringKeys( + vm::Handle array) noexcept { + vm::GCScopeMarkerRAII marker{runtime_}; + size_t length = vm::JSArray::getLength(array.get(), runtime_); + for (size_t i = 0; i < length; ++i) { + vm::HermesValue key = array->at(runtime_, i).unboxToHV(runtime_); + if (LLVM_UNLIKELY(key.isNumber())) { + vm::MutableHandle<> strKey{runtime_}; + CHECK_NAPI(convertIndexToString(key.getNumber(), &strKey)); + vm::JSArray::setElementAt(array, runtime_, i, strKey); + marker.flush(); + } + } + return clearLastNativeError(); +} + +napi_status NapiEnvironment::convertIndexToString( + double value, + vm::MutableHandle<> *result) noexcept { + OptValue index = doubleToArrayIndex(value); + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( + index.hasValue(), napi_generic_failure, "Index property is out of range"); + return NapiStringBuilder(*index).makeHVString(*this, result); +} + +napi_status NapiEnvironment::hasProperty( + napi_value object, + napi_value key, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_ARG(key); + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return hasComputedProperty(objValue, key, result); +} + +napi_status NapiEnvironment::getProperty( + napi_value object, + napi_value key, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(key); + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return scope.setResult(getComputedProperty(objValue, key, result)); +} + +napi_status NapiEnvironment::setProperty( + napi_value object, + napi_value key, + napi_value value) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_ARG(key); + CHECK_ARG(value); + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return setComputedProperty(objValue, key, value); +} + +napi_status NapiEnvironment::deleteProperty( + napi_value object, + napi_value key, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_ARG(key); + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return deleteComputedProperty(objValue, key, result); +} + +napi_status NapiEnvironment::hasOwnProperty( + napi_value object, + napi_value key, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + CHECK_ARG(key); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE( + phv(key)->isString() || phv(key)->isSymbol(), napi_name_expected); + + NapiHandleScope scope{*this}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + vm::MutableHandle tmpSymbolStorage{runtime_}; + vm::ComputedPropertyDescriptor desc; + return getOwnComputedPropertyDescriptor( + objValue, key, tmpSymbolStorage, desc, result); +} + +napi_status NapiEnvironment::hasNamedProperty( + napi_value object, + const char *utf8Name, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_ARG(utf8Name); + napi_value objValue, name; + CHECK_NAPI(coerceToObject(object, &objValue)); + CHECK_NAPI(createStringUTF8(utf8Name, &name)); + return hasComputedProperty(objValue, name, result); +} + +napi_status NapiEnvironment::getNamedProperty( + napi_value object, + const char *utf8Name, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(utf8Name); + napi_value objValue, name; + CHECK_NAPI(coerceToObject(object, &objValue)); + CHECK_NAPI(createStringUTF8(utf8Name, &name)); + return scope.setResult(getComputedProperty(objValue, name, result)); +} + +napi_status NapiEnvironment::setNamedProperty( + napi_value object, + const char *utf8Name, + napi_value value) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_ARG(utf8Name); + CHECK_ARG(value); + napi_value objValue, name; + CHECK_NAPI(coerceToObject(object, &objValue)); + CHECK_NAPI(createStringUTF8(utf8Name, &name)); + return setComputedProperty(objValue, name, value); +} + +napi_status NapiEnvironment::defineProperties( + napi_value object, + size_t propertyCount, + const napi_property_descriptor *properties) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_OBJECT_ARG(object); + if (propertyCount > 0) { + CHECK_ARG(properties); + } + + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + vm::Handle objHandle = makeHandle(objValue); + vm::MutableHandle name{runtime_}; + vm::GCScopeMarkerRAII marker{runtime_}; + for (size_t i = 0; i < propertyCount; ++i) { + marker.flush(); + const napi_property_descriptor *p = &properties[i]; + CHECK_NAPI(symbolIDFromPropertyDescriptor(p, &name)); + + vm::DefinePropertyFlags dpFlags{}; + dpFlags.setEnumerable = 1; + dpFlags.setConfigurable = 1; + dpFlags.enumerable = (p->attributes & napi_enumerable) == 0 ? 0 : 1; + dpFlags.configurable = (p->attributes & napi_configurable) == 0 ? 0 : 1; + + if ((p->getter != nullptr) || (p->setter != nullptr)) { + vm::MutableHandle localGetter{runtime_}; + vm::MutableHandle localSetter{runtime_}; + + if (p->getter != nullptr) { + dpFlags.setGetter = 1; + CHECK_NAPI(createFunction( + vm::Predefined::getSymbolID(vm::Predefined::get), + p->getter, + p->data, + &localGetter)); + } + if (p->setter != nullptr) { + dpFlags.setSetter = 1; + CHECK_NAPI(createFunction( + vm::Predefined::getSymbolID(vm::Predefined::set), + p->setter, + p->data, + &localSetter)); + } + + vm::CallResult propRes = + vm::PropertyAccessor::create(runtime_, localGetter, localSetter); + CHECK_NAPI(checkJSErrorStatus(propRes)); + CHECK_NAPI(defineOwnProperty( + objHandle, *name, dpFlags, makeHandle(*propRes), nullptr)); + } else { + dpFlags.setValue = 1; + dpFlags.setWritable = 1; + dpFlags.writable = (p->attributes & napi_writable) == 0 ? 0 : 1; + if (p->method != nullptr) { + vm::MutableHandle method{runtime_}; + CHECK_NAPI(createFunction(name.get(), p->method, p->data, &method)); + CHECK_NAPI( + defineOwnProperty(objHandle, *name, dpFlags, method, nullptr)); + } else { + CHECK_NAPI(defineOwnProperty( + objHandle, *name, dpFlags, makeHandle(p->value), nullptr)); + } + } + } + + return processFinalizerQueue(); +} + +napi_status NapiEnvironment::symbolIDFromPropertyDescriptor( + const napi_property_descriptor *descriptor, + vm::MutableHandle *result) noexcept { + if (descriptor->utf8name != nullptr) { + return getUniqueSymbolID(descriptor->utf8name, NAPI_AUTO_LENGTH, result); + } else { + RETURN_STATUS_IF_FALSE(descriptor->name != nullptr, napi_name_expected); + const vm::PinnedHermesValue &name = *phv(descriptor->name); + if (name.isString()) { + return getUniqueSymbolID(descriptor->name, result); + } else if (name.isSymbol()) { + *result = name.getSymbol(); + return clearLastNativeError(); + } else { + return ERROR_STATUS( + napi_name_expected, "p->name must be String or Symbol"); + } + } +} + +napi_status NapiEnvironment::objectFreeze(napi_value object) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return checkJSErrorStatus( + vm::JSObject::freeze(makeHandle(objValue), runtime_)); +} + +napi_status NapiEnvironment::objectSeal(napi_value object) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return checkJSErrorStatus( + vm::JSObject::seal(makeHandle(objValue), runtime_)); +} + +//----------------------------------------------------------------------------- +// Methods to work with Arrays +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createArray( + size_t length, + napi_value *result) noexcept { + NapiHandleScope scope{*this, result}; + return scope.setResult( + vm::JSArray::create(runtime_, /*capacity:*/ length, /*length:*/ length)); +} + +napi_status NapiEnvironment::isArray(napi_value value, bool *result) noexcept { + CHECK_ARG(value); + return setResult(vm::vmisa(*phv(value)), result); +} + +napi_status NapiEnvironment::getArrayLength( + napi_value value, + uint32_t *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + CHECK_ARG(value); + RETURN_STATUS_IF_FALSE( + vm::vmisa(*phv(value)), napi_array_expected); + napi_value res; + CHECK_NAPI(getNamedProperty( + value, vm::Predefined::getSymbolID(vm::Predefined::length), &res)); + RETURN_STATUS_IF_FALSE(phv(res)->isNumber(), napi_number_expected); + return setResult( + NapiDoubleConversion::toUint32(phv(res)->getDouble()), result); +} + +napi_status NapiEnvironment::hasElement( + napi_value object, + uint32_t index, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return hasComputedProperty(objValue, index, result); +} + +napi_status NapiEnvironment::getElement( + napi_value object, + uint32_t index, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return scope.setResult(getComputedProperty(objValue, index, result)); +} + +napi_status NapiEnvironment::setElement( + napi_value object, + uint32_t index, + napi_value value) noexcept { + CHECK_NAPI(checkPendingJSError()); + CHECK_ARG(value); + NapiHandleScope scope{*this}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return setComputedProperty(objValue, index, value); +} + +napi_status NapiEnvironment::deleteElement( + napi_value object, + uint32_t index, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + return deleteComputedProperty(objValue, index, result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Functions +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createFunction( + const char *utf8Name, + size_t length, + napi_callback callback, + void *callbackData, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(callback); + vm::MutableHandle nameSymbolID{runtime_}; + if (utf8Name != nullptr) { + CHECK_NAPI(getUniqueSymbolID(utf8Name, length, &nameSymbolID)); + } else { + nameSymbolID = getPredefinedSymbol(NapiPredefined::hostFunction); + } + vm::MutableHandle func{runtime_}; + CHECK_NAPI(createFunction(nameSymbolID.get(), callback, callbackData, &func)); + return scope.setResult(func.getHermesValue()); +} + +napi_status NapiEnvironment::createFunction( + vm::SymbolID name, + napi_callback callback, + void *callbackData, + vm::MutableHandle *result) noexcept { + std::unique_ptr context = + std::make_unique(*this, callback, callbackData); + vm::CallResult funcRes = + vm::FinalizableNativeFunction::createWithoutPrototype( + runtime_, + context.get(), + &NapiHostFunctionContext::func, + &NapiHostFunctionContext::finalize, + name, + /*paramCount:*/ 0); + CHECK_NAPI(checkJSErrorStatus(funcRes)); + context.release(); // the context is now owned by the func. + return setResult(makeHandle(*funcRes), result); +} + +napi_status NapiEnvironment::callFunction( + napi_value thisArg, + napi_value func, + size_t argCount, + const napi_value *args, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + + CHECK_ARG(thisArg); + CHECK_ARG(func); + if (argCount > 0) { + CHECK_ARG(args); + } + RETURN_STATUS_IF_FALSE(vm::vmisa(*phv(func)), napi_invalid_arg); + vm::Handle funcHandle = makeHandle(func); + + if (argCount >= std::numeric_limits::max() || + !runtime_.checkAvailableStack(static_cast(argCount))) { + return GENERIC_FAILURE("Unable to call function: stack overflow"); + } + + vm::ScopedNativeCallFrame newFrame{ + runtime_, + static_cast(argCount), + funcHandle.getHermesValue(), + /*newTarget:*/ getUndefined(), + *phv(thisArg)}; + if (LLVM_UNLIKELY(newFrame.overflowed())) { + CHECK_NAPI(checkJSErrorStatus(runtime_.raiseStackOverflow( + vm::Runtime::StackOverflowKind::NativeStack))); + } + + for (uint32_t i = 0; i < argCount; ++i) { + newFrame->getArgRef(static_cast(i)) = *phv(args[i]); + } + vm::CallResult> callRes = + vm::Callable::call(funcHandle, runtime_); + CHECK_NAPI(checkJSErrorStatus(callRes, napi_pending_exception)); + + if (result) { + RETURN_FAILURE_IF_FALSE(!callRes->get().isEmpty()); + return scope.setResult(callRes->get()); + } + return clearLastNativeError(); +} + +napi_status NapiEnvironment::createNewInstance( + napi_value constructor, + size_t argCount, + const napi_value *args, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + + CHECK_ARG(constructor); + if (argCount > 0) { + CHECK_ARG(args); + } + + RETURN_STATUS_IF_FALSE( + vm::vmisa(*phv(constructor)), napi_invalid_arg); + vm::Handle ctorHandle = makeHandle(constructor); + + if (argCount >= std::numeric_limits::max() || + !runtime_.checkAvailableStack(static_cast(argCount))) { + return GENERIC_FAILURE("Unable to call constructor: stack overflow"); + } + + // We follow es5 13.2.2 [[Construct]] here. Below F == func. + // 13.2.2.5: + // Let proto be the value of calling the [[Get]] internal property of + // F with argument "prototype" + // 13.2.2.6: + // If Type(proto) is Object, set the [[Prototype]] internal property + // of obj to proto + // 13.2.2.7: + // If Type(proto) is not Object, set the [[Prototype]] internal property + // of obj to the standard built-in Object prototype object as described + // in 15.2.4 + // + // Note that 13.2.2.1-4 are also handled by the call to newObject. + vm::CallResult> thisRes = + vm::Callable::createThisForConstruct_RJS(ctorHandle, runtime_); + CHECK_NAPI(checkJSErrorStatus(thisRes)); + // We need to capture this in case the ctor doesn't return an object, + // we need to return this object. + vm::Handle thisHandle = makeHandle(std::move(*thisRes)); + + // 13.2.2.8: + // Let result be the result of calling the [[Call]] internal property of + // F, providing obj as the this value and providing the argument list + // passed into [[Construct]] as args. + // + // For us result == res. + + vm::ScopedNativeCallFrame newFrame{ + runtime_, + static_cast(argCount), + ctorHandle.getHermesValue(), + ctorHandle.getHermesValue(), + thisHandle.getHermesValue()}; + if (LLVM_UNLIKELY(newFrame.overflowed())) { + CHECK_NAPI(checkJSErrorStatus(runtime_.raiseStackOverflow( + vm::Runtime::StackOverflowKind::NativeStack))); + } + for (size_t i = 0; i < argCount; ++i) { + newFrame->getArgRef(static_cast(i)) = *phv(args[i]); + } + // The last parameter indicates that this call should construct an object. + vm::CallResult> callRes = + vm::Callable::call(ctorHandle, runtime_); + CHECK_NAPI(checkJSErrorStatus(callRes, napi_pending_exception)); + + // 13.2.2.9: + // If Type(result) is Object then return result + // 13.2.2.10: + // Return obj + vm::HermesValue resultValue = callRes->get(); + return scope.setResult( + resultValue.isObject() ? std::move(resultValue) + : thisHandle.getHermesValue()); +} + +napi_status NapiEnvironment::isInstanceOf( + napi_value object, + napi_value constructor, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + CHECK_ARG(object); + CHECK_ARG(constructor); + napi_value ctorValue; + CHECK_NAPI(coerceToObject(constructor, &ctorValue)); + RETURN_STATUS_IF_FALSE( + vm::vmisa(*phv(ctorValue)), napi_function_expected); + return setResult( + vm::instanceOfOperator_RJS( + runtime_, makeHandle(object), makeHandle(constructor)), + result); +} + +template +vm::ExecutionStatus NapiEnvironment::callIntoModule(TLambda &&call) noexcept { + size_t openHandleScopesBefore = napiValueStackScopes_.size(); + clearLastNativeError(); + call(this); + CRASH_IF_FALSE(openHandleScopesBefore == napiValueStackScopes_.size()); + if (!thrownJSError_.isEmpty()) { + runtime_.setThrownValue(thrownJSError_); + thrownJSError_ = EmptyHermesValue; + } + return runtime_.getThrownValue().isEmpty() ? vm::ExecutionStatus::RETURNED + : vm::ExecutionStatus::EXCEPTION; +} + +//----------------------------------------------------------------------------- +// Methods to work with napi_callbacks +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::getCallbackInfo( + napi_callback_info callbackInfo, + size_t *argCount, + napi_value *args, + napi_value *thisArg, + void **data) noexcept { + CHECK_ARG(callbackInfo); + NapiCallbackInfo *cbInfo = asCallbackInfo(callbackInfo); + if (args != nullptr) { + CHECK_ARG(argCount); + cbInfo->args(args, *argCount); + } + if (argCount != nullptr) { + *argCount = cbInfo->argCount(); + } + if (thisArg != nullptr) { + *thisArg = cbInfo->thisArg(); + } + if (data != nullptr) { + *data = cbInfo->nativeData(); + } + + return clearLastNativeError(); +} + +napi_status NapiEnvironment::getNewTarget( + napi_callback_info callbackInfo, + napi_value *result) noexcept { + CHECK_ARG(callbackInfo); + return setResult( + reinterpret_cast(callbackInfo)->getNewTarget(), + result); +} + +//----------------------------------------------------------------------------- +// Property access helpers +//----------------------------------------------------------------------------- + +const vm::PinnedHermesValue &NapiEnvironment::getPredefinedValue( + NapiPredefined key) noexcept { + return predefinedValues_[static_cast(key)]; +} + +vm::SymbolID NapiEnvironment::getPredefinedSymbol(NapiPredefined key) noexcept { + return getPredefinedValue(key).getSymbol(); +} + +template +napi_status NapiEnvironment::hasPredefinedProperty( + TObject object, + NapiPredefined key, + bool *result) noexcept { + return hasNamedProperty(object, getPredefinedSymbol(key), result); +} + +template +napi_status NapiEnvironment::getPredefinedProperty( + TObject object, + NapiPredefined key, + napi_value *result) noexcept { + return getNamedProperty(object, getPredefinedSymbol(key), result); +} + +template +napi_status NapiEnvironment::setPredefinedProperty( + TObject object, + NapiPredefined key, + TValue &&value, + bool *optResult) noexcept { + return setNamedProperty( + object, getPredefinedSymbol(key), std::forward(value), optResult); +} + +template +napi_status NapiEnvironment::hasNamedProperty( + TObject object, + vm::SymbolID name, + bool *result) noexcept { + vm::CallResult res = + vm::JSObject::hasNamed(makeHandle(object), runtime_, name); + return setResult(std::move(res), result); +} + +template +napi_status NapiEnvironment::getNamedProperty( + TObject object, + vm::SymbolID name, + napi_value *result) noexcept { + vm::CallResult> res = vm::JSObject::getNamed_RJS( + makeHandle(object), + runtime_, + name, + vm::PropOpFlags().plusThrowOnError()); + return setResult(std::move(res), result); +} + +template +napi_status NapiEnvironment::setNamedProperty( + TObject object, + vm::SymbolID name, + TValue &&value, + bool *optResult) noexcept { + vm::CallResult res = vm::JSObject::putNamed_RJS( + makeHandle(object), + runtime_, + name, + makeHandle(std::forward(value)), + vm::PropOpFlags().plusThrowOnError()); + return setOptionalResult(std::move(res), optResult); +} + +template +napi_status NapiEnvironment::hasComputedProperty( + TObject object, + TKey key, + bool *result) noexcept { + vm::CallResult res = vm::JSObject::hasComputed( + makeHandle(object), runtime_, makeHandle(key)); + return setResult(std::move(res), result); +} + +template +napi_status NapiEnvironment::getComputedProperty( + TObject object, + TKey key, + napi_value *result) noexcept { + vm::CallResult> res = vm::JSObject::getComputed_RJS( + makeHandle(object), runtime_, makeHandle(key)); + return setResult(std::move(res), result); +} + +template +napi_status NapiEnvironment::setComputedProperty( + TObject object, + TKey key, + TValue value, + bool *optResult) noexcept { + vm::CallResult res = vm::JSObject::putComputed_RJS( + makeHandle(object), + runtime_, + makeHandle(key), + makeHandle(value), + vm::PropOpFlags().plusThrowOnError()); + return setOptionalResult(std::move(res), optResult); +} + +template +napi_status NapiEnvironment::deleteComputedProperty( + TObject object, + TKey key, + bool *optResult) noexcept { + vm::CallResult res = vm::JSObject::deleteComputed( + makeHandle(object), + runtime_, + makeHandle(key), + vm::PropOpFlags()); + return setOptionalResult(std::move(res), optResult); +} + +template +napi_status NapiEnvironment::getOwnComputedPropertyDescriptor( + TObject object, + TKey key, + vm::MutableHandle &tmpSymbolStorage, + vm::ComputedPropertyDescriptor &desc, + bool *result) noexcept { + vm::CallResult res = vm::JSObject::getOwnComputedDescriptor( + makeHandle(object), + runtime_, + makeHandle(key), + tmpSymbolStorage, + desc); + return setOptionalResult(std::move(res), result); +} + +template +napi_status NapiEnvironment::defineOwnProperty( + TObject object, + vm::SymbolID name, + vm::DefinePropertyFlags dpFlags, + vm::Handle<> valueOrAccessor, + bool *result) noexcept { + vm::CallResult res = vm::JSObject::defineOwnProperty( + makeHandle(object), + runtime_, + name, + dpFlags, + valueOrAccessor, + vm::PropOpFlags().plusThrowOnError()); + return setOptionalResult(std::move(res), result); +} + +//----------------------------------------------------------------------------- +// Methods to compare values +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::strictEquals( + napi_value lhs, + napi_value rhs, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + CHECK_ARG(lhs); + CHECK_ARG(rhs); + vm::HermesValue::Tag lhsTag = phv(lhs)->getTag(); + if (lhsTag != phv(rhs)->getTag()) { + return setResult(false, result); + } else if (lhsTag == vm::HermesValue::Tag::Str) { + return setResult( + phv(lhs)->getString()->equals(phv(rhs)->getString()), result); + } else if (lhsTag == vm::HermesValue::Tag::BoolSymbol) { + vm::HermesValue::ETag lhsETag = phv(lhs)->getETag(); + if (lhsETag != phv(rhs)->getETag()) { + return setResult(false, result); + } + if (lhsETag == vm::HermesValue::ETag::Symbol) { + return setResult(phv(lhs)->getSymbol() == phv(rhs)->getSymbol(), result); + } else { + return setResult(phv(lhs)->getBool() == phv(rhs)->getBool(), result); + } + } else if (lhsTag == vm::HermesValue::Tag::BigInt) { + return setResult( + phv(lhs)->getBigInt()->compare(phv(rhs)->getBigInt()) == 0, result); + } else { + return setResult(phv(lhs)->getRaw() == phv(rhs)->getRaw(), result); + } +} + +//----------------------------------------------------------------------------- +// Methods to work with external data objects +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::defineClass( + const char *utf8Name, + size_t length, + napi_callback constructor, + void *callbackData, + size_t propertyCount, + const napi_property_descriptor *properties, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + + CHECK_ARG(constructor); + if (propertyCount > 0) { + CHECK_ARG(properties); + } + + vm::MutableHandle nameHandle{runtime_}; + CHECK_NAPI(getUniqueSymbolID(utf8Name, length, &nameHandle)); + + vm::Handle parentHandle = + vm::Handle::vmcast(&runtime_.functionPrototype); + + std::unique_ptr context = + std::make_unique( + *this, constructor, callbackData); + vm::PseudoHandle ctorRes = + vm::NativeConstructor::create( + runtime_, + parentHandle, + context.get(), + &NapiHostFunctionContext::func, + /*paramCount:*/ 0, + vm::NativeConstructor::creatorFunction, + vm::CellKind::JSObjectKind); + vm::Handle classHandle = + makeHandle(std::move(ctorRes)); + + vm::NativeState *ns = vm::NativeState::create( + runtime_, context.release(), &NapiHostFunctionContext::finalizeState); + + vm::CallResult res = vm::JSObject::defineOwnProperty( + classHandle, + runtime_, + vm::Predefined::getSymbolID( + vm::Predefined::InternalPropertyArrayBufferExternalFinalizer), + vm::DefinePropertyFlags::getDefaultNewPropertyFlags(), + runtime_.makeHandle(ns)); + CHECK_NAPI(checkJSErrorStatus(res)); + RETURN_STATUS_IF_FALSE_WITH_MESSAGE( + *res, napi_generic_failure, "Cannot set external finalizer for a class"); + + vm::Handle prototypeHandle{ + makeHandle(vm::JSObject::create(runtime_))}; + vm::ExecutionStatus st = vm::Callable::defineNameLengthAndPrototype( + vm::Handle::vmcast(classHandle), + runtime_, + nameHandle.get(), + /*paramCount:*/ 0, + prototypeHandle, + vm::Callable::WritablePrototype::Yes, + /*strictMode*/ false); + CHECK_NAPI(checkJSErrorStatus(st)); + + for (size_t i = 0; i < propertyCount; ++i) { + const napi_property_descriptor *p = properties + i; + if ((p->attributes & napi_static) != 0) { + CHECK_NAPI(defineProperties(napiValue(classHandle), 1, p)); + } else { + CHECK_NAPI(defineProperties(napiValue(prototypeHandle), 1, p)); + } + } + + return scope.setResult(std::move(classHandle)); +} + +napi_status NapiEnvironment::wrapObject( + napi_value object, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_ref *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + CHECK_OBJECT_ARG(object); + 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 that, then the finalize callback will never be invoked.) + // Therefore a finalize callback is required when returning a reference. + CHECK_ARG(finalizeCallback); + } + + // If we've already wrapped this object, we error out. + NapiExternalValue *externalValue; + CHECK_NAPI(getExternalPropertyValue( + object, NapiIfNotFound::ThenCreate, &externalValue)); + RETURN_STATUS_IF_FALSE(!externalValue->nativeData(), napi_invalid_arg); + + NapiReference *reference; + CHECK_NAPI(NapiFinalizingComplexReference::create( + *this, + 0, + /*deleteSelf*/ result == nullptr, + phv(object), + nativeData, + finalizeCallback, + finalizeHint, + reinterpret_cast(&reference))); + externalValue->setNativeData(reference); + return setOptionalResult(reinterpret_cast(reference), result); +} + +napi_status NapiEnvironment::addFinalizer( + napi_value object, + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_ref *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + CHECK_OBJECT_ARG(object); + CHECK_ARG(finalizeCallback); + if (result != nullptr) { + return NapiFinalizingComplexReference::create( + *this, + 0, + /*deleteSelf:*/ false, + phv(object), + nativeData, + finalizeCallback, + finalizeHint, + reinterpret_cast(result)); + } else { + return NapiFinalizingAnonymousReference::create( + *this, + phv(object), + nativeData, + finalizeCallback, + finalizeHint, + nullptr); + } +} + +template +napi_status NapiEnvironment::unwrapObject( + napi_value object, + void **result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + CHECK_OBJECT_ARG(object); + if /*constexpr*/ (action == NapiUnwrapAction::KeepWrap) { + CHECK_ARG(result); + } + + NapiExternalValue *externalValue = getExternalObjectValue(*phv(object)); + if (!externalValue) { + CHECK_NAPI(getExternalPropertyValue( + object, NapiIfNotFound::ThenReturnNull, &externalValue)); + RETURN_STATUS_IF_FALSE(externalValue, napi_invalid_arg); + } + + NapiReference *reference = asReference(externalValue->nativeData()); + RETURN_STATUS_IF_FALSE(reference, napi_invalid_arg); + if (result) { + *result = reference->nativeData(); + } + + if /*constexpr*/ (action == NapiUnwrapAction::RemoveWrap) { + externalValue->setNativeData(nullptr); + NapiReference::deleteReference( + *this, reference, NapiReference::ReasonToDelete::ZeroRefCount); + } + + return clearLastNativeError(); +} + +napi_status NapiEnvironment::typeTagObject( + napi_value object, + const napi_type_tag *typeTag) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + CHECK_ARG(typeTag); + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + + // Fail if the tag already exists + bool hasTag{}; + CHECK_NAPI( + hasPredefinedProperty(objValue, NapiPredefined::napi_typeTag, &hasTag)); + RETURN_STATUS_IF_FALSE(!hasTag, napi_invalid_arg); + + napi_value tagBuffer; + void *tagBufferData; + CHECK_NAPI( + createArrayBuffer(sizeof(napi_type_tag), &tagBufferData, &tagBuffer)); + + const uint8_t *source = reinterpret_cast(typeTag); + uint8_t *dest = reinterpret_cast(tagBufferData); + std::copy(source, source + sizeof(napi_type_tag), dest); + + return defineOwnProperty( + objValue, + getPredefinedSymbol(NapiPredefined::napi_typeTag), + vm::DefinePropertyFlags::getNewNonEnumerableFlags(), + makeHandle(tagBuffer), + nullptr); +} + +napi_status NapiEnvironment::checkObjectTypeTag( + napi_value object, + const napi_type_tag *typeTag, + bool *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + CHECK_ARG(typeTag); + napi_value objValue; + CHECK_NAPI(coerceToObject(object, &objValue)); + + napi_value tagBufferValue; + CHECK_NAPI(getPredefinedProperty( + objValue, NapiPredefined::napi_typeTag, &tagBufferValue)); + vm::JSArrayBuffer *tagBuffer = + vm::dyn_vmcast_or_null(*phv(tagBufferValue)); + if (tagBuffer == nullptr) { + return setResult(false, result); + } + + const uint8_t *source = reinterpret_cast(typeTag); + const uint8_t *tagBufferData = tagBuffer->getDataBlock(runtime_); + return setResult( + std::equal( + source, + source + sizeof(napi_type_tag), + tagBufferData, + tagBufferData + sizeof(napi_type_tag)), + result); +} + +napi_status NapiEnvironment::createExternal( + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + + CHECK_ARG(result); + vm::Handle decoratedObj = + createExternalObject(nativeData, nullptr); + if (finalizeCallback) { + CHECK_NAPI(NapiFinalizingAnonymousReference::create( + *this, + decoratedObj.unsafeGetPinnedHermesValue(), + nativeData, + finalizeCallback, + finalizeHint, + nullptr)); + } + return scope.setResult(std::move(decoratedObj)); +} + +// Create the ExternalObject as a DecoratedObject with a special tag to +// distinguish it from other DecoratedObject instances. +vm::Handle NapiEnvironment::createExternalObject( + void *nativeData, + NapiExternalValue **externalValue) noexcept { + vm::Handle decoratedObj = + makeHandle(vm::DecoratedObject::create( + runtime_, + makeHandle(&runtime_.objectPrototype), + std::make_unique(pendingFinalizers_, nativeData), + /*additionalSlotCount:*/ 1)); + + // Add a special tag to differentiate from other decorated objects. + vm::DecoratedObject::setAdditionalSlotValue( + decoratedObj.get(), + runtime_, + kExternalTagSlotIndex, + vm::SmallHermesValue::encodeNumberValue(kExternalValueTag, runtime_)); + + if (externalValue) { + *externalValue = + static_cast(decoratedObj->getDecoration()); + } + + return decoratedObj; +} + +// Get the external value associated with the ExternalObject. +napi_status NapiEnvironment::getValueExternal( + napi_value value, + void **result) noexcept { + NapiHandleScope scope{*this}; + CHECK_ARG(value); + NapiExternalValue *externalValue = getExternalObjectValue(*phv(value)); + RETURN_STATUS_IF_FALSE(externalValue != nullptr, napi_invalid_arg); + return setResult(externalValue->nativeData(), result); +} + +// Get the NapiExternalValue from value if it is a DecoratedObject created by +// the createExternalObject method. Otherwise, return nullptr. +NapiExternalValue *NapiEnvironment::getExternalObjectValue( + vm::HermesValue value) noexcept { + if (vm::DecoratedObject *decoratedObj = + vm::dyn_vmcast_or_null(value)) { + vm::SmallHermesValue tag = vm::DecoratedObject::getAdditionalSlotValue( + decoratedObj, runtime_, kExternalTagSlotIndex); + if (tag.isNumber() && tag.getNumber(runtime_) == kExternalValueTag) { + return static_cast(decoratedObj->getDecoration()); + } + } + return nullptr; +} + +// Get the NapiExternalValue from the object's property. +// If it is not found and the ifNotFound parameter is +// NapiIfNotFound::ThenCreate, then create the property with the new +// NapiExternalValue and return it. +template +napi_status NapiEnvironment::getExternalPropertyValue( + TObject object, + NapiIfNotFound ifNotFound, + NapiExternalValue **result) noexcept { + NapiExternalValue *externalValue{}; + napi_value napiExternalValue; + napi_status status = getPredefinedProperty( + object, NapiPredefined::napi_externalValue, &napiExternalValue); + if (status == napi_ok && + vm::vmisa(*phv(napiExternalValue))) { + externalValue = getExternalObjectValue(*phv(napiExternalValue)); + RETURN_FAILURE_IF_FALSE(externalValue != nullptr); + } else if (ifNotFound == NapiIfNotFound::ThenCreate) { + vm::Handle decoratedObj = + createExternalObject(nullptr, &externalValue); + CHECK_NAPI(defineOwnProperty( + object, + getPredefinedSymbol(NapiPredefined::napi_externalValue), + vm::DefinePropertyFlags::getNewNonEnumerableFlags(), + makeHandle(decoratedObj), + nullptr)); + } + return setResult(std::move(externalValue), result); +} + +napi_status NapiEnvironment::addObjectFinalizer( + const vm::PinnedHermesValue *value, + NapiFinalizer *finalizer) noexcept { + NapiExternalValue *externalValue = getExternalObjectValue(*value); + if (!externalValue) { + CHECK_NAPI(getExternalPropertyValue( + value, NapiIfNotFound::ThenCreate, &externalValue)); + } + externalValue->addFinalizer(finalizer); + return clearLastNativeError(); +} + +void NapiEnvironment::callFinalizer( + napi_finalize finalizeCallback, + void *nativeData, + void *finalizeHint) noexcept { + callIntoModule([&](NapiEnvironment *env) { + finalizeCallback(napiEnv(env), nativeData, finalizeHint); + }); +} + +void NapiEnvironment::addToFinalizerQueue(NapiFinalizer *finalizer) noexcept { + finalizerQueue_.pushBack(finalizer); +} + +napi_status NapiEnvironment::processFinalizerQueue() noexcept { + if (!isRunningFinalizers_) { + isRunningFinalizers_ = true; + pendingFinalizers_->applyPendingFinalizers(this); + NapiReference::finalizeAll(*this, finalizerQueue_); + isRunningFinalizers_ = false; + } + return napi_ok; +} + +//----------------------------------------------------------------------------- +// Methods to control object lifespan +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createReference( + napi_value value, + uint32_t initialRefCount, + napi_ref *result) noexcept { + return NapiComplexReference::create( + *this, + phv(value), + initialRefCount, + reinterpret_cast(result)); +} + +napi_status NapiEnvironment::deleteReference(napi_ref ref) noexcept { + CHECK_ARG(ref); + if (isShuttingDown_) { + // During shutdown all references are going to be deleted by finalizers. + return clearLastNativeError(); + } + return NapiReference::deleteReference( + *this, asReference(ref), NapiReference::ReasonToDelete::ExternalCall); +} + +napi_status NapiEnvironment::incReference( + napi_ref ref, + uint32_t *result) noexcept { + CHECK_ARG(ref); + uint32_t refCount{}; + CHECK_NAPI(asReference(ref)->incRefCount(*this, refCount)); + return setOptionalResult(std::move(refCount), result); +} + +napi_status NapiEnvironment::decReference( + napi_ref ref, + uint32_t *result) noexcept { + CHECK_ARG(ref); + uint32_t refCount{}; + CHECK_NAPI(asReference(ref)->decRefCount(*this, refCount)); + return setOptionalResult(std::move(refCount), result); +} + +napi_status NapiEnvironment::getReferenceValue( + napi_ref ref, + napi_value *result) noexcept { + CHECK_ARG(ref); + const vm::PinnedHermesValue &value = asReference(ref)->value(env); + *result = !value.isUndefined() ? pushNewNapiValue(value) : nullptr; + return clearLastNativeError(); +} + +void NapiEnvironment::addReference(NapiReference *reference) noexcept { + references_.pushBack(reference); +} + +void NapiEnvironment::addFinalizingReference( + NapiReference *reference) noexcept { + finalizingReferences_.pushBack(reference); +} + +//----------------------------------------------------------------------------- +// Methods to control napi_value stack. +// napi_value are added on top of the stack. +// Closing napi_value stack scope deletes all napi_values added after +// opening the scope. +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::openNapiValueScope( + napi_handle_scope *result) noexcept { + size_t scope = napiValueStack_.size(); + napiValueStackScopes_.emplace(scope); + return setResult( + reinterpret_cast(&napiValueStackScopes_.top()), + result); +} + +napi_status NapiEnvironment::closeNapiValueScope( + napi_handle_scope scope) noexcept { + CHECK_ARG(scope); + RETURN_STATUS_IF_FALSE( + !napiValueStackScopes_.empty(), napi_handle_scope_mismatch); + + size_t *topScope = &napiValueStackScopes_.top(); + RETURN_STATUS_IF_FALSE( + reinterpret_cast(scope) == topScope, + napi_handle_scope_mismatch); + + napiValueStack_.resize(*topScope); + napiValueStackScopes_.pop(); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::openEscapableNapiValueScope( + napi_escapable_handle_scope *result) noexcept { + CHECK_ARG(result); + + // Escapable handle scope must have a parent scope + RETURN_STATUS_IF_FALSE( + !napiValueStackScopes_.empty(), napi_handle_scope_mismatch); + + napiValueStack_.emplace(); // value to escape to parent scope + napiValueStack_.emplace( + vm::HermesValue::encodeNativeUInt32(kEscapeableSentinelTag)); + + return openNapiValueScope(reinterpret_cast(result)); +} + +napi_status NapiEnvironment::closeEscapableNapiValueScope( + napi_escapable_handle_scope scope) noexcept { + CHECK_NAPI(closeNapiValueScope(reinterpret_cast(scope))); + + RETURN_STATUS_IF_FALSE( + napiValueStack_.size() > 1, napi_handle_scope_mismatch); + vm::PinnedHermesValue &sentinelTag = napiValueStack_.top(); + RETURN_STATUS_IF_FALSE( + sentinelTag.isNativeValue(), napi_handle_scope_mismatch); + uint32_t sentinelTagValue = sentinelTag.getNativeUInt32(); + RETURN_STATUS_IF_FALSE( + sentinelTagValue == kEscapeableSentinelTag || + sentinelTagValue == kUsedEscapeableSentinelTag, + napi_handle_scope_mismatch); + + napiValueStack_.pop(); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::escapeNapiValue( + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value *result) noexcept { + CHECK_ARG(scope); + CHECK_ARG(escapee); + + size_t *stackScope = reinterpret_cast(scope); + RETURN_STATUS_IF_FALSE(*stackScope > 1, napi_invalid_arg); + RETURN_STATUS_IF_FALSE( + *stackScope <= napiValueStack_.size(), napi_invalid_arg); + + vm::PinnedHermesValue &sentinelTag = napiValueStack_[*stackScope - 1]; + RETURN_STATUS_IF_FALSE(sentinelTag.isNativeValue(), napi_invalid_arg); + uint32_t sentinelTagValue = sentinelTag.getNativeUInt32(); + RETURN_STATUS_IF_FALSE( + sentinelTagValue != kUsedEscapeableSentinelTag, napi_escape_called_twice); + RETURN_STATUS_IF_FALSE( + sentinelTagValue == kEscapeableSentinelTag, napi_invalid_arg); + + vm::PinnedHermesValue &escapedValue = napiValueStack_[*stackScope - 2]; + escapedValue = *phv(escapee); + sentinelTag = vm::HermesValue::encodeNativeUInt32(kUsedEscapeableSentinelTag); + + return setResult(napiValue(&escapedValue), result); +} + +napi_value NapiEnvironment::pushNewNapiValue(vm::HermesValue value) noexcept { + napiValueStack_.emplace(value); + return napiValue(&napiValueStack_.top()); +} + +//----------------------------------------------------------------------------- +// Methods to work with weak roots. +//----------------------------------------------------------------------------- + +vm::WeakRoot NapiEnvironment::createWeakRoot( + vm::JSObject *object) noexcept { + return vm::WeakRoot(object, runtime_); +} + +// We must work aound the missing assignment operator for WeakRoot. +void NapiEnvironment::createWeakRoot( + vm::WeakRoot *weakRoot, + vm::JSObject *object) noexcept { + weakRoot->~WeakRoot(); + ::new (weakRoot) vm::WeakRoot(object, runtime_); +} + +void NapiEnvironment::clearWeakRoot( + vm::WeakRoot *weakRoot) noexcept { + weakRoot->~WeakRoot(); + ::new (weakRoot) vm::WeakRoot(); +} + +const vm::PinnedHermesValue &NapiEnvironment::lockWeakRoot( + vm::WeakRoot &weakRoot) noexcept { + if (vm::JSObject *ptr = weakRoot.get(runtime_, runtime_.getHeap())) { + return *phv(pushNewNapiValue(vm::HermesValue::encodeObjectValue(ptr))); + } + return getUndefined(); +} + +//----------------------------------------------------------------------------- +// Methods to work with ordered sets. +// We use them as a temporary storage while retrieving property names. +// They are treated as GC roots. +//----------------------------------------------------------------------------- + +void NapiEnvironment::pushOrderedSet( + NapiOrderedSet &set) noexcept { + orderedSets_.push_back(&set); +} + +void NapiEnvironment::popOrderedSet() noexcept { + orderedSets_.pop_back(); +} + +//----------------------------------------------------------------------------- +// Methods to work with array buffers and typed arrays +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createArrayBuffer( + size_t byteLength, + void **data, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + vm::Handle buffer = makeHandle(vm::JSArrayBuffer::create( + runtime_, makeHandle(runtime_.arrayBufferPrototype))); + CHECK_NAPI(checkJSErrorStatus( + vm::JSArrayBuffer::createDataBlock(runtime_, buffer, byteLength, true))); + if (data != nullptr) { + *data = buffer->getDataBlock(runtime_); + } + return scope.setResult(std::move(buffer)); +} + +napi_status NapiEnvironment::createExternalArrayBuffer( + void *externalData, + size_t byteLength, + napi_finalize finalizeCallback, + void *finalizeHint, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + vm::Handle buffer = makeHandle(vm::JSArrayBuffer::create( + runtime_, makeHandle(&runtime_.arrayBufferPrototype))); + if (externalData != nullptr) { + std::unique_ptr externalBuffer = + std::make_unique( + env, externalData, byteLength, finalizeCallback, finalizeHint); + vm::JSArrayBuffer::setExternalDataBlock( + runtime_, + buffer, + reinterpret_cast(externalData), + byteLength, + externalBuffer.release(), + [](vm::GC & /*gc*/, vm::NativeState *ns) { + std::unique_ptr externalBuffer( + reinterpret_cast(ns->context())); + }); + } + return scope.setResult(std::move(buffer)); +} + +napi_status NapiEnvironment::isArrayBuffer( + napi_value value, + bool *result) noexcept { + CHECK_ARG(value); + return setResult(vm::vmisa(*phv(value)), result); +} + +napi_status NapiEnvironment::getArrayBufferInfo( + napi_value arrayBuffer, + void **data, + size_t *byteLength) noexcept { + CHECK_ARG(arrayBuffer); + RETURN_STATUS_IF_FALSE( + vm::vmisa(*phv(arrayBuffer)), napi_invalid_arg); + + vm::JSArrayBuffer *buffer = vm::vmcast(*phv(arrayBuffer)); + if (data != nullptr) { + *data = buffer->attached() ? buffer->getDataBlock(runtime_) : nullptr; + } + + if (byteLength != nullptr) { + *byteLength = buffer->attached() ? buffer->size() : 0; + } + + return clearLastNativeError(); +} + +napi_status NapiEnvironment::detachArrayBuffer( + napi_value arrayBuffer) noexcept { + CHECK_ARG(arrayBuffer); + vm::Handle buffer = + makeHandle(arrayBuffer); + RETURN_STATUS_IF_FALSE(buffer, napi_arraybuffer_expected); + return checkJSErrorStatus(vm::JSArrayBuffer::detach(runtime_, buffer)); +} + +napi_status NapiEnvironment::isDetachedArrayBuffer( + napi_value arrayBuffer, + bool *result) noexcept { + CHECK_ARG(arrayBuffer); + vm::JSArrayBuffer *buffer = + vm::dyn_vmcast_or_null(*phv(arrayBuffer)); + RETURN_STATUS_IF_FALSE(buffer, napi_arraybuffer_expected); + return setResult(!buffer->attached(), result); +} + +template +napi_status NapiEnvironment::createTypedArray( + size_t length, + vm::JSArrayBuffer *buffer, + size_t byteOffset, + vm::MutableHandle *result) noexcept { + constexpr size_t elementSize = sizeof(TElement); + if (elementSize > 1) { + if (byteOffset % elementSize != 0) { + NapiStringBuilder sb( + "start offset of ", + getTypedArrayName(), + " should be a multiple of ", + elementSize); + return env.throwJSRangeError( + "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", sb.c_str()); + } + } + if (length * elementSize + byteOffset > buffer->size()) { + return env.throwJSRangeError( + "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", "Invalid typed array length"); + } + using TypedArray = vm::JSTypedArray; + *result = TypedArray::create(runtime_, TypedArray::getPrototype(runtime_)); + vm::JSTypedArrayBase::setBuffer( + runtime_, + result->get(), + buffer, + byteOffset, + length * elementSize, + static_cast(elementSize)); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::createTypedArray( + napi_typedarray_type type, + size_t length, + napi_value arrayBuffer, + size_t byteOffset, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(arrayBuffer); + + vm::JSArrayBuffer *buffer = + vm::dyn_vmcast_or_null(*phv(arrayBuffer)); + RETURN_STATUS_IF_FALSE(buffer != nullptr, napi_invalid_arg); + + vm::MutableHandle typedArray{runtime_}; + switch (type) { + case napi_int8_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_uint8_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_uint8_clamped_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_int16_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_uint16_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_int32_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_uint32_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_float32_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_float64_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_bigint64_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + case napi_biguint64_array: + CHECK_NAPI(createTypedArray( + length, buffer, byteOffset, &typedArray)); + break; + default: + return ERROR_STATUS( + napi_invalid_arg, "Unsupported TypedArray type: ", type); + } + + return scope.setResult(std::move(typedArray)); +} + +static constexpr const char *typedArrayNames[] = { +#define TYPED_ARRAY(name, type) #name "Array", +#include "hermes/VM/TypedArrays.def" +}; + +template +/*static*/ constexpr const char *NapiEnvironment::getTypedArrayName() noexcept { + return typedArrayNames + [static_cast(CellKind) - + static_cast(vm::CellKind::TypedArrayBaseKind_first)]; +} + +napi_status NapiEnvironment::isTypedArray( + napi_value value, + bool *result) noexcept { + CHECK_ARG(value); + return setResult(vm::vmisa(*phv(value)), result); +} + +napi_status NapiEnvironment::getTypedArrayInfo( + napi_value typedArray, + napi_typedarray_type *type, + size_t *length, + void **data, + napi_value *arrayBuffer, + size_t *byteOffset) noexcept { + CHECK_ARG(typedArray); + + vm::JSTypedArrayBase *array = + vm::dyn_vmcast_or_null(*phv(typedArray)); + RETURN_STATUS_IF_FALSE(array != nullptr, napi_invalid_arg); + + if (type != nullptr) { + if (vm::vmisa(array)) { + *type = napi_int8_array; + } else if (vm::vmisa(array)) { + *type = napi_uint8_array; + } else if (vm::vmisa(array)) { + *type = napi_uint8_clamped_array; + } else if (vm::vmisa(array)) { + *type = napi_int16_array; + } else if (vm::vmisa(array)) { + *type = napi_uint16_array; + } else if (vm::vmisa(array)) { + *type = napi_int32_array; + } else if (vm::vmisa(array)) { + *type = napi_uint32_array; + } else if (vm::vmisa(array)) { + *type = napi_float32_array; + } else if (vm::vmisa(array)) { + *type = napi_float64_array; + } else if (vm::vmisa(array)) { + *type = napi_bigint64_array; + } else if (vm::vmisa(array)) { + *type = napi_biguint64_array; + } else { + return GENERIC_FAILURE("Unknown TypedArray type"); + } + } + + if (length != nullptr) { + *length = array->getLength(); + } + + if (data != nullptr) { + *data = array->attached(runtime_) + ? array->getBuffer(runtime_)->getDataBlock(runtime_) + + array->getByteOffset() + : nullptr; + } + + if (arrayBuffer != nullptr) { + *arrayBuffer = array->attached(runtime_) + ? pushNewNapiValue( + vm::HermesValue::encodeObjectValue(array->getBuffer(runtime_))) + : napiValue(&getUndefined()); + } + + if (byteOffset != nullptr) { + *byteOffset = array->getByteOffset(); + } + + return clearLastNativeError(); +} + +napi_status NapiEnvironment::createDataView( + size_t byteLength, + napi_value arrayBuffer, + size_t byteOffset, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(arrayBuffer); + + vm::JSArrayBuffer *buffer = + vm::dyn_vmcast_or_null(*phv(arrayBuffer)); + RETURN_STATUS_IF_FALSE(buffer != nullptr, napi_invalid_arg); + + if (byteLength + byteOffset > buffer->size()) { + return env.throwJSRangeError( + "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"); + } + vm::Handle viewHandle = makeHandle(vm::JSDataView::create( + runtime_, makeHandle(runtime_.dataViewPrototype))); + viewHandle->setBuffer(runtime_, buffer, byteOffset, byteLength); + return scope.setResult(std::move(viewHandle)); +} + +napi_status NapiEnvironment::isDataView( + napi_value value, + bool *result) noexcept { + CHECK_ARG(value); + return setResult(vm::vmisa(*phv(value)), result); +} + +napi_status NapiEnvironment::getDataViewInfo( + napi_value dataView, + size_t *byteLength, + void **data, + napi_value *arrayBuffer, + size_t *byteOffset) noexcept { + CHECK_ARG(dataView); + + vm::JSDataView *view = vm::dyn_vmcast_or_null(*phv(dataView)); + RETURN_STATUS_IF_FALSE(view, napi_invalid_arg); + + if (byteLength != nullptr) { + *byteLength = view->byteLength(); + } + + if (data != nullptr) { + *data = view->attached(runtime_) + ? view->getBuffer(runtime_)->getDataBlock(runtime_) + view->byteOffset() + : nullptr; + } + + if (arrayBuffer != nullptr) { + *arrayBuffer = view->attached(runtime_) + ? pushNewNapiValue(view->getBuffer(runtime_).getHermesValue()) + : napiValue(&getUndefined()); + } + + if (byteOffset != nullptr) { + *byteOffset = view->byteOffset(); + } + + return clearLastNativeError(); +} + +//----------------------------------------------------------------------------- +// Runtime info +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::getDescription(const char **result) noexcept { + CHECK_ARG(result); + *result = "Hermes"; + return napi_ok; +} + +napi_status NapiEnvironment::isInspectable(bool *result) noexcept { + CHECK_ARG(result); + *result = isInspectable_; + return napi_ok; +} + +//----------------------------------------------------------------------------- +// Version management +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::getVersion(uint32_t *result) noexcept { + return setResult(static_cast(NAPI_VERSION), result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Promises +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createPromise( + napi_deferred *deferred, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(deferred); + + napi_value jsPromise, jsDeferred; + vm::MutableHandle<> jsResolve{runtime_}; + vm::MutableHandle<> jsReject{runtime_}; + CHECK_NAPI(createPromise(&jsPromise, &jsResolve, &jsReject)); + + CHECK_NAPI(createObject(&jsDeferred)); + CHECK_NAPI( + setPredefinedProperty(jsDeferred, NapiPredefined::resolve, jsResolve)); + CHECK_NAPI( + setPredefinedProperty(jsDeferred, NapiPredefined::reject, jsReject)); + + CHECK_NAPI(NapiStrongReference::create( + *this, + *phv(jsDeferred), + reinterpret_cast(deferred))); + return scope.setResult(std::move(jsPromise)); +} + +napi_status NapiEnvironment::createPromise( + napi_value *promise, + vm::MutableHandle<> *resolveFunction, + vm::MutableHandle<> *rejectFunction) noexcept { + napi_value global, promiseConstructor; + CHECK_NAPI(getGlobal(&global)); + CHECK_NAPI(getPredefinedProperty( + global, NapiPredefined::Promise, &promiseConstructor)); + + // The executor function is to be executed by the constructor during the + // process of constructing the new Promise object. The executor is custom code + // that ties an outcome to a promise. We return the resolveFunction and + // rejectFunction given to the executor. Since the execution is synchronous, + // we allocate executorData on the callstack. + struct ExecutorData { + static vm::CallResult + callback(void *context, vm::Runtime & /*runtime*/, vm::NativeArgs args) { + return (reinterpret_cast(context))->callback(args); + } + + vm::CallResult callback(const vm::NativeArgs &args) { + *resolve = args.getArg(0); + *reject = args.getArg(1); + return env_->getUndefined(); + } + + NapiEnvironment *env_{}; + vm::MutableHandle<> *resolve{}; + vm::MutableHandle<> *reject{}; + } executorData{this, resolveFunction, rejectFunction}; + + vm::Handle executorFunction = + vm::NativeFunction::createWithoutPrototype( + runtime_, + &executorData, + &ExecutorData::callback, + getPredefinedSymbol(NapiPredefined::Promise), + 2); + napi_value func = pushNewNapiValue(executorFunction.getHermesValue()); + return createNewInstance(promiseConstructor, 1, &func, promise); +} + +napi_status NapiEnvironment::resolveDeferred( + napi_deferred deferred, + napi_value resolution) noexcept { + return concludeDeferred(deferred, NapiPredefined::resolve, resolution); +} + +napi_status NapiEnvironment::rejectDeferred( + napi_deferred deferred, + napi_value resolution) noexcept { + return concludeDeferred(deferred, NapiPredefined::reject, resolution); +} + +napi_status NapiEnvironment::concludeDeferred( + napi_deferred deferred, + NapiPredefined predefinedProperty, + napi_value resolution) noexcept { + CHECK_ARG(deferred); + CHECK_ARG(resolution); + + NapiReference *ref = asReference(deferred); + + const vm::PinnedHermesValue &jsDeferred = ref->value(*this); + napi_value resolver, callResult; + CHECK_NAPI(getPredefinedProperty(&jsDeferred, predefinedProperty, &resolver)); + CHECK_NAPI(callFunction( + napi_value(&getUndefined()), resolver, 1, &resolution, &callResult)); + return NapiReference::deleteReference( + *this, ref, NapiReference::ReasonToDelete::ZeroRefCount); +} + +napi_status NapiEnvironment::isPromise( + napi_value value, + bool *result) noexcept { + CHECK_ARG(value); + + napi_value global, promiseConstructor; + CHECK_NAPI(getGlobal(&global)); + CHECK_NAPI(getPredefinedProperty( + global, NapiPredefined::Promise, &promiseConstructor)); + + return isInstanceOf(value, promiseConstructor, result); +} + +napi_status NapiEnvironment::enablePromiseRejectionTracker() noexcept { + NapiHandleScope scope{*this}; + + vm::Handle onUnhandled = + vm::NativeFunction::createWithoutPrototype( + runtime_, + this, + [](void *context, + vm::Runtime &runtime, + vm::NativeArgs args) -> vm::CallResult { + return handleRejectionNotification( + context, + runtime, + args, + [](NapiEnvironment *env, int32_t id, vm::HermesValue error) { + env->lastUnhandledRejectionId_ = id; + env->lastUnhandledRejection_ = error; + }); + }, + getPredefinedValue(NapiPredefined::onUnhandled).getSymbol(), + /*paramCount:*/ 2); + vm::Handle onHandled = + vm::NativeFunction::createWithoutPrototype( + runtime_, + this, + [](void *context, + vm::Runtime &runtime, + vm::NativeArgs args) -> vm::CallResult { + return handleRejectionNotification( + context, + runtime, + args, + [](NapiEnvironment *env, int32_t id, vm::HermesValue error) { + if (env->lastUnhandledRejectionId_ == id) { + env->lastUnhandledRejectionId_ = -1; + env->lastUnhandledRejection_ = EmptyHermesValue; + } + }); + }, + getPredefinedValue(NapiPredefined::onHandled).getSymbol(), + /*paramCount:*/ 2); + + napi_value options; + CHECK_NAPI(createObject(&options)); + CHECK_NAPI(setPredefinedProperty( + options, NapiPredefined::allRejections, vm::Runtime::getBoolValue(true))); + CHECK_NAPI( + setPredefinedProperty(options, NapiPredefined::onUnhandled, onUnhandled)); + CHECK_NAPI( + setPredefinedProperty(options, NapiPredefined::onHandled, onHandled)); + + vm::Handle hookFunc = vm::Handle::dyn_vmcast( + makeHandle(&runtime_.promiseRejectionTrackingHook_)); + RETURN_FAILURE_IF_FALSE(hookFunc); + return checkJSErrorStatus(vm::Callable::executeCall1( + hookFunc, runtime_, vm::Runtime::getUndefinedValue(), *phv(options))); +} + +/*static*/ vm::CallResult +NapiEnvironment::handleRejectionNotification( + void *context, + vm::Runtime &runtime, + vm::NativeArgs args, + void (*handler)( + NapiEnvironment *env, + int32_t id, + vm::HermesValue error)) noexcept { + // Args: id, error + RAISE_ERROR_IF_FALSE(args.getArgCount() >= 2, u"Expected two arguments."); + vm::HermesValue idArg = args.getArg(0); + RAISE_ERROR_IF_FALSE(idArg.isNumber(), "id arg must be a Number."); + int32_t id = NapiDoubleConversion::toInt32(idArg.getDouble()); + + RAISE_ERROR_IF_FALSE(context != nullptr, u"Context must not be null."); + NapiEnvironment *env = reinterpret_cast(context); + + (*handler)(env, id, args.getArg(1)); + return env->getUndefined(); +} + +napi_status NapiEnvironment::openEnvScope(jsr_napi_env_scope *scope) noexcept { + CHECK_ARG(scope); + *scope = reinterpret_cast(new int(0)); + return napi_ok; +} + +napi_status NapiEnvironment::closeEnvScope(jsr_napi_env_scope scope) noexcept { + CHECK_ARG(scope); + delete reinterpret_cast(scope); + return napi_ok; +} + +napi_status NapiEnvironment::hasUnhandledPromiseRejection( + bool *result) noexcept { + return setResult(lastUnhandledRejectionId_ != -1, result); +} + +napi_status NapiEnvironment::getAndClearLastUnhandledPromiseRejection( + napi_value *result) noexcept { + lastUnhandledRejectionId_ = -1; + return setResult( + std::exchange(lastUnhandledRejection_, EmptyHermesValue), result); +} + +napi_status NapiEnvironment::drainMicrotasks( + int32_t maxCountHint, + bool *result) noexcept { + CHECK_ARG(result); + if (runtime_.hasMicrotaskQueue()) { + CHECK_NAPI(checkJSErrorStatus(runtime_.drainJobs())); + } + + runtime_.clearKeptObjects(); + *result = true; + return napi_ok; +} + +//----------------------------------------------------------------------------- +// Memory management +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::adjustExternalMemory( + int64_t change_in_bytes, + int64_t *adjusted_value) noexcept { + return GENERIC_FAILURE("Not implemented"); +} + +napi_status NapiEnvironment::collectGarbage() noexcept { + runtime_.collect("test"); + CHECK_NAPI(processFinalizerQueue()); + return clearLastNativeError(); +} + +//----------------------------------------------------------------------------- +// Methods to work with Dates +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::createDate( + double dateTime, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + vm::PseudoHandle dateHandle = vm::JSDate::create( + runtime_, dateTime, makeHandle(&runtime_.datePrototype)); + return scope.setResult(std::move(dateHandle)); +} + +napi_status NapiEnvironment::isDate(napi_value value, bool *result) noexcept { + CHECK_ARG(value); + return setResult(vm::vmisa(*phv(value)), result); +} + +napi_status NapiEnvironment::getDateValue( + napi_value value, + double *result) noexcept { + CHECK_ARG(value); + vm::JSDate *date = vm::dyn_vmcast_or_null(*phv(value)); + RETURN_STATUS_IF_FALSE(date != nullptr, napi_date_expected); + return setResult(date->getPrimitiveValue(), result); +} + +//----------------------------------------------------------------------------- +// Instance data +//----------------------------------------------------------------------------- + +napi_status NapiEnvironment::setInstanceData( + void *nativeData, + napi_finalize finalizeCallback, + void *finalizeHint) noexcept { + if (instanceData_ != nullptr) { + // Our contract so far has been to not finalize any old data there may be. + // So we simply delete it. + delete instanceData_; + instanceData_ = nullptr; + } + return NapiInstanceData::create( + *this, nativeData, finalizeCallback, finalizeHint, &instanceData_); +} + +napi_status NapiEnvironment::getInstanceData(void **nativeData) noexcept { + return setResult( + instanceData_ ? instanceData_->nativeData() : nullptr, nativeData); +} + +//--------------------------------------------------------------------------- +// Script running +//--------------------------------------------------------------------------- + +napi_status NapiEnvironment::runScript( + napi_value source, + const char *sourceURL, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + + size_t sourceSize{}; + CHECK_NAPI(getStringValueUTF8(source, nullptr, 0, &sourceSize)); + std::unique_ptr buffer = + std::unique_ptr(new char[sourceSize + 1]); + CHECK_NAPI(getStringValueUTF8(source, buffer.get(), sourceSize + 1, nullptr)); + + jsr_prepared_script preparedScript{}; + CHECK_NAPI(createPreparedScript( + reinterpret_cast(buffer.release()), + sourceSize, + [](void *data, void * /*deleterData*/) { + std::unique_ptr buf(reinterpret_cast(data)); + }, + nullptr, + sourceURL, + &preparedScript)); + // To delete prepared script after execution. + std::unique_ptr scriptModel{ + reinterpret_cast(preparedScript)}; + return scope.setResult(runPreparedScript(preparedScript, result)); +} + +napi_status NapiEnvironment::createPreparedScript( + const uint8_t *scriptData, + size_t scriptLength, + jsr_data_delete_cb scriptDeleteCallback, + void *deleterData, + const char *sourceURL, + jsr_prepared_script *result) noexcept { + std::unique_ptr buffer = std::make_unique( + scriptData, scriptLength, scriptDeleteCallback, deleterData); + + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this}; + + std::pair, std::string> bcErr{}; + vm::RuntimeModuleFlags runtimeFlags{}; + runtimeFlags.persistent = true; + + bool isBytecode = isHermesBytecode(buffer->data(), buffer->size()); + // Save the first few bytes of the buffer so that we can later append them + // to any error message. + uint8_t bufPrefix[16]; + const size_t bufSize = buffer->size(); + std::memcpy(bufPrefix, buffer->data(), std::min(sizeof(bufPrefix), bufSize)); + + // Construct the BC provider either from buffer or source. + if (isBytecode) { + bcErr = hbc::BCProviderFromBuffer::createBCProviderFromBuffer( + std::move(buffer)); + } else { +#if defined(HERMESVM_LEAN) + bcErr.second = "prepareJavaScript source compilation not supported"; +#else + + facebook::jsi::ScriptSignature scriptSignature; + facebook::jsi::JSRuntimeSignature runtimeSignature; + const char *prepareTag = "perf"; + + if (scriptCache_) { + uint64_t hash{}; + murmurhash(buffer->data(), buffer->size(), /*ref*/ hash); + facebook::jsi::JSRuntimeVersion_t runtimeVersion = + HermesBuildVersion.version; + scriptSignature = {std::string(sourceURL ? sourceURL : ""), hash}; + runtimeSignature = {"Hermes", runtimeVersion}; + } + + std::shared_ptr cache; + if (scriptCache_) { + cache = scriptCache_->tryGetPreparedScript( + scriptSignature, runtimeSignature, prepareTag); + bcErr = hbc::BCProviderFromBuffer::createBCProviderFromBuffer( + std::make_unique(std::move(cache))); + } + + hbc::BCProviderFromSrc *bytecodeProviderFromSrc{}; + if (!bcErr.first) { + std::pair, std::string> + bcFromSrcErr = hbc::BCProviderFromSrc::createBCProviderFromSrc( + std::move(buffer), + std::string(sourceURL ? sourceURL : ""), + nullptr, + compileFlags_); + bytecodeProviderFromSrc = bcFromSrcErr.first.get(); + bcErr = std::move(bcFromSrcErr); + } + + if (scriptCache_ && bytecodeProviderFromSrc) { + hbc::BytecodeModule *bcModule = + bytecodeProviderFromSrc->getBytecodeModule(); + + // Serialize/deserialize can't handle lazy compilation as of now. Do a + // check to make sure there is no lazy BytecodeFunction in module_. + for (uint32_t i = 0; i < bcModule->getNumFunctions(); i++) { + if (bytecodeProviderFromSrc->isFunctionLazy(i)) { + goto CannotSerialize; + } + } + + // Serialize the bytecode. Call BytecodeSerializer to do the heavy + // lifting. Write to a SmallVector first, so we can know the total bytes + // and write it first and make life easier for Deserializer. This is going + // to be slower than writing to Serializer directly but it's OK to slow + // down serialization if it speeds up Deserializer. + BytecodeGenerationOptions bytecodeGenOpts = + BytecodeGenerationOptions::defaults(); + llvh::SmallVector bytecodeVector; + llvh::raw_svector_ostream outStream(bytecodeVector); + hbc::BytecodeSerializer bcSerializer{outStream, bytecodeGenOpts}; + bcSerializer.serialize( + *bcModule, bytecodeProviderFromSrc->getSourceHash()); + + scriptCache_->persistPreparedScript( + std::shared_ptr( + new JsiSmallVectorBuffer(std::move(bytecodeVector))), + scriptSignature, + runtimeSignature, + prepareTag); + } +#endif + } + if (!bcErr.first) { + NapiStringBuilder sb(" Buffer size: ", bufSize, ", starts with: "); + for (size_t i = 0; i < sizeof(bufPrefix) && i < bufSize; ++i) { + sb.append(llvh::format_hex_no_prefix(bufPrefix[i], 2)); + } + return GENERIC_FAILURE("Compiling JS failed: ", bcErr.second, sb.str()); + } + +#if !defined(HERMESVM_LEAN) +CannotSerialize: +#endif + *result = reinterpret_cast(new NapiScriptModel( + std::move(bcErr.first), + runtimeFlags, + sourceURL ? sourceURL : "", + isBytecode)); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::deletePreparedScript( + jsr_prepared_script preparedScript) noexcept { + CHECK_ARG(preparedScript); + delete reinterpret_cast(preparedScript); + return napi_ok; +} + +napi_status NapiEnvironment::runPreparedScript( + jsr_prepared_script preparedScript, + napi_value *result) noexcept { + CHECK_NAPI(checkPendingJSError()); + NapiHandleScope scope{*this, result}; + CHECK_ARG(preparedScript); + const NapiScriptModel *hermesPrep = + reinterpret_cast(preparedScript); + vm::CallResult res = runtime_.runBytecode( + hermesPrep->bytecodeProvider(), + hermesPrep->runtimeFlags(), + hermesPrep->sourceURL(), + vm::Runtime::makeNullHandle()); + return scope.setResult(std::move(res)); +} + +/*static*/ bool NapiEnvironment::isHermesBytecode( + const uint8_t *data, + size_t len) noexcept { + return hbc::BCProviderFromBuffer::isBytecodeStream( + llvh::ArrayRef(data, len)); +} + +//--------------------------------------------------------------------------- +// Methods to create Hermes GC handles for stack-based variables. +//--------------------------------------------------------------------------- + +vm::Handle<> NapiEnvironment::makeHandle(napi_value value) noexcept { + return makeHandle(phv(value)); +} + +vm::Handle<> NapiEnvironment::makeHandle( + const vm::PinnedHermesValue *value) noexcept { + return vm::Handle<>(value); +} + +vm::Handle<> NapiEnvironment::makeHandle(vm::HermesValue value) noexcept { + return vm::Handle<>(runtime_, value); +} + +vm::Handle<> NapiEnvironment::makeHandle(vm::Handle<> value) noexcept { + return value; +} + +// Useful for converting index to a name/index handle. +vm::Handle<> NapiEnvironment::makeHandle(uint32_t value) noexcept { + return makeHandle(vm::HermesValue::encodeUntrustedNumberValue(value)); +} + +template +vm::Handle NapiEnvironment::makeHandle(napi_value value) noexcept { + return vm::Handle::vmcast(phv(value)); +} + +template +vm::Handle NapiEnvironment::makeHandle( + const vm::PinnedHermesValue *value) noexcept { + return vm::Handle::vmcast(value); +} + +template +vm::Handle NapiEnvironment::makeHandle(vm::HermesValue value) noexcept { + return vm::Handle::vmcast(runtime_, value); +} + +template +vm::Handle NapiEnvironment::makeHandle(vm::Handle value) noexcept { + return vm::Handle::vmcast(value); +} + +template +vm::Handle NapiEnvironment::makeHandle( + vm::PseudoHandle &&value) noexcept { + return runtime_.makeHandle(std::move(value)); +} + +template +vm::CallResult> NapiEnvironment::makeHandle( + vm::CallResult> &&callResult) noexcept { + if (callResult.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return vm::ExecutionStatus::EXCEPTION; + } + return runtime_.makeHandle(std::move(*callResult)); +} + +template +vm::CallResult> NapiEnvironment::makeMutableHandle( + vm::CallResult> &&callResult) noexcept { + vm::CallResult> handleResult = + makeHandle(std::move(callResult)); + if (handleResult.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return vm::ExecutionStatus::EXCEPTION; + } + vm::MutableHandle result{runtime_}; + result = *handleResult; + return result; +} + +//--------------------------------------------------------------------------- +// Result setting helpers +//--------------------------------------------------------------------------- + +template +napi_status NapiEnvironment::setResult(T &&value, TResult *result) noexcept { + CHECK_ARG(result); + return setResultUnsafe(std::forward(value), result); +} + +template +napi_status NapiEnvironment::setOptionalResult( + T &&value, + TResult *result) noexcept { + if (result) { + return setResultUnsafe(std::forward(value), result); + } + return clearLastNativeError(); +} + +template +napi_status NapiEnvironment::setOptionalResult( + T && /*value*/, + std::nullptr_t) noexcept { + return clearLastNativeError(); +} + +napi_status NapiEnvironment::setPredefinedResult( + const vm::PinnedHermesValue *value, + napi_value *result) noexcept { + CHECK_ARG(result); + *result = napiValue(value); + return clearLastNativeError(); +} + +template +napi_status NapiEnvironment::setResultUnsafe(T &&value, T *result) noexcept { + *result = std::forward(value); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::setResultUnsafe( + vm::HermesValue value, + napi_value *result) noexcept { + *result = pushNewNapiValue(value); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::setResultUnsafe( + vm::SymbolID value, + napi_value *result) noexcept { + return setResultUnsafe(vm::HermesValue::encodeSymbolValue(value), result); +} + +napi_status NapiEnvironment::setResultUnsafe( + bool value, + napi_value *result) noexcept { + return setResultUnsafe(vm::HermesValue::encodeBoolValue(value), result); +} + +template +napi_status NapiEnvironment::setResultUnsafe( + vm::Handle &&handle, + napi_value *result) noexcept { + return setResultUnsafe(handle.getHermesValue(), result); +} + +template +napi_status NapiEnvironment::setResultUnsafe( + vm::PseudoHandle &&handle, + napi_value *result) noexcept { + return setResultUnsafe(handle.getHermesValue(), result); +} + +template +napi_status NapiEnvironment::setResultUnsafe( + vm::Handle &&handle, + vm::MutableHandle *result) noexcept { + *result = std::move(handle); + return clearLastNativeError(); +} + +napi_status NapiEnvironment::setResultUnsafe( + vm::HermesValue value, + vm::MutableHandle<> *result) noexcept { + *result = value; + return clearLastNativeError(); +} + +template +napi_status NapiEnvironment::setResultUnsafe( + vm::CallResult &&value, + TResult *result) noexcept { + return setResultUnsafe(std::move(value), napi_generic_failure, result); +} + +template +napi_status NapiEnvironment::setResultUnsafe( + vm::CallResult &&value, + napi_status onException, + TResult *result) noexcept { + CHECK_NAPI(checkJSErrorStatus(value, onException)); + return setResultUnsafe(std::move(*value), result); +} + +} // namespace napi +} // namespace hermes + +//============================================================================= +// NAPI implementation +//============================================================================= + +//----------------------------------------------------------------------------- +// Native error handling functions +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_get_last_error_info( + napi_env env, + const napi_extended_error_info **result) { + return CHECKED_ENV(env)->getLastNativeError(result); +} + +//----------------------------------------------------------------------------- +// Getters for defined singletons +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_get_undefined(napi_env env, napi_value *result) { + return CHECKED_ENV(env)->getUndefined(result); +} + +napi_status NAPI_CDECL napi_get_null(napi_env env, napi_value *result) { + return CHECKED_ENV(env)->getNull(result); +} + +napi_status NAPI_CDECL napi_get_global(napi_env env, napi_value *result) { + return CHECKED_ENV(env)->getGlobal(result); +} + +napi_status NAPI_CDECL +napi_get_boolean(napi_env env, bool value, napi_value *result) { + return CHECKED_ENV(env)->getBoolean(value, result); +} + +//----------------------------------------------------------------------------- +// Methods to create Primitive types/Objects +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value *result) { + return CHECKED_ENV(env)->createObject(result); +} + +napi_status NAPI_CDECL napi_create_array(napi_env env, napi_value *result) { + return CHECKED_ENV(env)->createArray(/*length:*/ 0, result); +} + +napi_status NAPI_CDECL +napi_create_array_with_length(napi_env env, size_t length, napi_value *result) { + return CHECKED_ENV(env)->createArray(length, result); +} + +napi_status NAPI_CDECL +napi_create_double(napi_env env, double value, napi_value *result) { + return CHECKED_ENV(env)->createNumber(value, result); +} + +napi_status NAPI_CDECL +napi_create_int32(napi_env env, int32_t value, napi_value *result) { + return CHECKED_ENV(env)->createNumber(value, result); +} + +napi_status NAPI_CDECL +napi_create_uint32(napi_env env, uint32_t value, napi_value *result) { + return CHECKED_ENV(env)->createNumber(value, result); +} + +napi_status NAPI_CDECL +napi_create_int64(napi_env env, int64_t value, napi_value *result) { + return CHECKED_ENV(env)->createNumber(value, result); +} + +napi_status NAPI_CDECL napi_create_string_latin1( + napi_env env, + const char *str, + size_t length, + napi_value *result) { + return CHECKED_ENV(env)->createStringLatin1(str, length, result); +} + +napi_status NAPI_CDECL napi_create_string_utf8( + napi_env env, + const char *str, + size_t length, + napi_value *result) { + return CHECKED_ENV(env)->createStringUTF8(str, length, result); +} + +napi_status NAPI_CDECL napi_create_string_utf16( + napi_env env, + const char16_t *str, + size_t length, + napi_value *result) { + return CHECKED_ENV(env)->createStringUTF16(str, length, result); +} + +napi_status NAPI_CDECL +napi_create_symbol(napi_env env, napi_value description, napi_value *result) { + return CHECKED_ENV(env)->createSymbol(description, result); +} + +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) { + return CHECKED_ENV(env)->createFunction( + utf8name, length, cb, callback_data, result); +} + +napi_status NAPI_CDECL napi_create_error( + napi_env env, + napi_value code, + napi_value msg, + napi_value *result) { + return CHECKED_ENV(env)->createJSError(code, msg, result); +} + +napi_status NAPI_CDECL napi_create_type_error( + napi_env env, + napi_value code, + napi_value msg, + napi_value *result) { + return CHECKED_ENV(env)->createJSTypeError(code, msg, result); +} + +napi_status NAPI_CDECL napi_create_range_error( + napi_env env, + napi_value code, + napi_value msg, + napi_value *result) { + return CHECKED_ENV(env)->createJSRangeError(code, msg, result); +} + +//----------------------------------------------------------------------------- +// Methods to get the native napi_value from Primitive type +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_typeof(napi_env env, napi_value value, napi_valuetype *result) { + return CHECKED_ENV(env)->typeOf(value, result); +} + +napi_status NAPI_CDECL +napi_get_value_double(napi_env env, napi_value value, double *result) { + return CHECKED_ENV(env)->getNumberValue(value, result); +} + +napi_status NAPI_CDECL +napi_get_value_int32(napi_env env, napi_value value, int32_t *result) { + return CHECKED_ENV(env)->getNumberValue(value, result); +} + +napi_status NAPI_CDECL +napi_get_value_uint32(napi_env env, napi_value value, uint32_t *result) { + return CHECKED_ENV(env)->getNumberValue(value, result); +} + +napi_status NAPI_CDECL +napi_get_value_int64(napi_env env, napi_value value, int64_t *result) { + return CHECKED_ENV(env)->getNumberValue(value, result); +} + +napi_status NAPI_CDECL +napi_get_value_bool(napi_env env, napi_value value, bool *result) { + return CHECKED_ENV(env)->getBooleanValue(value, result); +} + +// 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) { + return CHECKED_ENV(env)->getStringValueLatin1(value, buf, bufsize, result); +} + +// 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) { + return CHECKED_ENV(env)->getStringValueUTF8(value, buf, bufsize, result); +} + +// 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) { + return CHECKED_ENV(env)->getStringValueUTF16(value, buf, bufsize, result); +} + +//----------------------------------------------------------------------------- +// Methods to coerce values +// These APIs may execute user scripts +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_coerce_to_bool(napi_env env, napi_value value, napi_value *result) { + return CHECKED_ENV(env)->coerceToBoolean(value, result); +} + +napi_status NAPI_CDECL +napi_coerce_to_number(napi_env env, napi_value value, napi_value *result) { + return CHECKED_ENV(env)->coerceToNumber(value, result); +} + +napi_status NAPI_CDECL +napi_coerce_to_object(napi_env env, napi_value value, napi_value *result) { + return CHECKED_ENV(env)->coerceToObject(value, result); +} + +napi_status NAPI_CDECL +napi_coerce_to_string(napi_env env, napi_value value, napi_value *result) { + return CHECKED_ENV(env)->coerceToString(value, result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Objects +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_get_prototype(napi_env env, napi_value object, napi_value *result) { + return CHECKED_ENV(env)->getPrototype(object, result); +} + +napi_status NAPI_CDECL +napi_get_property_names(napi_env env, napi_value object, napi_value *result) { + return CHECKED_ENV(env)->getForInPropertyNames(object, result); +} + +napi_status NAPI_CDECL napi_has_property( + napi_env env, + napi_value object, + napi_value key, + bool *result) { + return CHECKED_ENV(env)->hasProperty(object, key, result); +} + +napi_status NAPI_CDECL napi_get_property( + napi_env env, + napi_value object, + napi_value key, + napi_value *result) { + return CHECKED_ENV(env)->getProperty(object, key, result); +} + +napi_status NAPI_CDECL napi_set_property( + napi_env env, + napi_value object, + napi_value key, + napi_value value) { + return CHECKED_ENV(env)->setProperty(object, key, value); +} + +napi_status NAPI_CDECL napi_delete_property( + napi_env env, + napi_value object, + napi_value key, + bool *result) { + return CHECKED_ENV(env)->deleteProperty(object, key, result); +} + +napi_status NAPI_CDECL napi_has_named_property( + napi_env env, + napi_value object, + const char *utf8name, + bool *result) { + return CHECKED_ENV(env)->hasNamedProperty(object, utf8name, result); +} + +napi_status NAPI_CDECL napi_get_named_property( + napi_env env, + napi_value object, + const char *utf8name, + napi_value *result) { + return CHECKED_ENV(env)->getNamedProperty(object, utf8name, result); +} + +napi_status NAPI_CDECL napi_set_named_property( + napi_env env, + napi_value object, + const char *utf8name, + napi_value value) { + return CHECKED_ENV(env)->setNamedProperty(object, utf8name, value); +} + +napi_status NAPI_CDECL napi_has_element( + napi_env env, + napi_value object, + uint32_t index, + bool *result) { + return CHECKED_ENV(env)->hasElement(object, index, result); +} + +napi_status NAPI_CDECL napi_get_element( + napi_env env, + napi_value object, + uint32_t index, + napi_value *result) { + return CHECKED_ENV(env)->getElement(object, index, result); +} + +napi_status NAPI_CDECL napi_set_element( + napi_env env, + napi_value object, + uint32_t index, + napi_value value) { + return CHECKED_ENV(env)->setElement(object, index, value); +} + +napi_status NAPI_CDECL napi_delete_element( + napi_env env, + napi_value object, + uint32_t index, + bool *result) { + return CHECKED_ENV(env)->deleteElement(object, index, result); +} + +napi_status NAPI_CDECL napi_has_own_property( + napi_env env, + napi_value object, + napi_value key, + bool *result) { + return CHECKED_ENV(env)->hasOwnProperty(object, key, result); +} + +napi_status NAPI_CDECL napi_define_properties( + napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor *properties) { + return CHECKED_ENV(env)->defineProperties(object, property_count, properties); +} + +//----------------------------------------------------------------------------- +// Methods to work with Arrays +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_is_array(napi_env env, napi_value value, bool *result) { + return CHECKED_ENV(env)->isArray(value, result); +} + +napi_status NAPI_CDECL +napi_get_array_length(napi_env env, napi_value value, uint32_t *result) { + return CHECKED_ENV(env)->getArrayLength(value, result); +} + +//----------------------------------------------------------------------------- +// Methods to compare values +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, bool *result) { + return CHECKED_ENV(env)->strictEquals(lhs, rhs, result); +} + +//----------------------------------------------------------------------------- +// Methods to work with Functions +//----------------------------------------------------------------------------- + +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) { + return CHECKED_ENV(env)->callFunction(recv, func, argc, argv, result); +} + +napi_status NAPI_CDECL napi_new_instance( + napi_env env, + napi_value constructor, + size_t argc, + const napi_value *argv, + napi_value *result) { + return CHECKED_ENV(env)->createNewInstance(constructor, argc, argv, result); +} + +napi_status NAPI_CDECL napi_instanceof( + napi_env env, + napi_value object, + napi_value constructor, + bool *result) { + return CHECKED_ENV(env)->isInstanceOf(object, constructor, result); +} + +//----------------------------------------------------------------------------- +// Methods to work with napi_callbacks +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_get_cb_info( + napi_env env, + napi_callback_info cbinfo, + size_t *argc, + napi_value *argv, + napi_value *this_arg, + void **data) { + return CHECKED_ENV(env)->getCallbackInfo(cbinfo, argc, argv, this_arg, data); +} + +napi_status NAPI_CDECL napi_get_new_target( + napi_env env, + napi_callback_info cbinfo, + napi_value *result) { + return CHECKED_ENV(env)->getNewTarget(cbinfo, result); +} + +//----------------------------------------------------------------------------- +// Methods to work with external data objects +//----------------------------------------------------------------------------- + +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) { + return CHECKED_ENV(env)->defineClass( + utf8name, + length, + constructor, + callback_data, + property_count, + properties, + result); +} + +napi_status NAPI_CDECL napi_wrap( + napi_env env, + napi_value js_object, + void *native_object, + napi_finalize finalize_cb, + void *finalize_hint, + napi_ref *result) { + return CHECKED_ENV(env)->wrapObject( + js_object, native_object, finalize_cb, finalize_hint, result); +} + +napi_status NAPI_CDECL +napi_unwrap(napi_env env, napi_value obj, void **result) { + return CHECKED_ENV(env) + ->unwrapObject(obj, result); +} + +napi_status NAPI_CDECL +napi_remove_wrap(napi_env env, napi_value obj, void **result) { + return CHECKED_ENV(env) + ->unwrapObject(obj, result); +} + +napi_status NAPI_CDECL napi_create_external( + napi_env env, + void *data, + napi_finalize finalize_cb, + void *finalize_hint, + napi_value *result) { + return CHECKED_ENV(env)->createExternal( + data, finalize_cb, finalize_hint, result); +} + +napi_status NAPI_CDECL +napi_get_value_external(napi_env env, napi_value value, void **result) { + return CHECKED_ENV(env)->getValueExternal(value, result); +} + +//----------------------------------------------------------------------------- +// Methods to control object lifespan +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_create_reference( + napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref *result) { + return CHECKED_ENV(env)->createReference(value, initial_refcount, result); +} + +napi_status NAPI_CDECL napi_delete_reference(napi_env env, napi_ref ref) { + return CHECKED_ENV(env)->deleteReference(ref); +} + +napi_status NAPI_CDECL +napi_reference_ref(napi_env env, napi_ref ref, uint32_t *result) { + return CHECKED_ENV(env)->incReference(ref, result); +} + +napi_status NAPI_CDECL +napi_reference_unref(napi_env env, napi_ref ref, uint32_t *result) { + return CHECKED_ENV(env)->decReference(ref, result); +} + +napi_status NAPI_CDECL +napi_get_reference_value(napi_env env, napi_ref ref, napi_value *result) { + return CHECKED_ENV(env)->getReferenceValue(ref, result); +} + +napi_status NAPI_CDECL +napi_open_handle_scope(napi_env env, napi_handle_scope *result) { + return CHECKED_ENV(env)->openNapiValueScope(result); +} + +napi_status NAPI_CDECL +napi_close_handle_scope(napi_env env, napi_handle_scope scope) { + return CHECKED_ENV(env)->closeNapiValueScope(scope); +} + +napi_status NAPI_CDECL napi_open_escapable_handle_scope( + napi_env env, + napi_escapable_handle_scope *result) { + return CHECKED_ENV(env)->openEscapableNapiValueScope(result); +} + +napi_status NAPI_CDECL napi_close_escapable_handle_scope( + napi_env env, + napi_escapable_handle_scope scope) { + return CHECKED_ENV(env)->closeEscapableNapiValueScope(scope); +} + +napi_status NAPI_CDECL napi_escape_handle( + napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value *result) { + return CHECKED_ENV(env)->escapeNapiValue(scope, escapee, result); +} + +//----------------------------------------------------------------------------- +// Methods to support JS error handling +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error) { + return CHECKED_ENV(env)->throwJSError(error); +} + +napi_status NAPI_CDECL +napi_throw_error(napi_env env, const char *code, const char *msg) { + return CHECKED_ENV(env)->throwJSError(code, msg); +} + +napi_status NAPI_CDECL +napi_throw_type_error(napi_env env, const char *code, const char *msg) { + return CHECKED_ENV(env)->throwJSTypeError(code, msg); +} + +napi_status NAPI_CDECL +napi_throw_range_error(napi_env env, const char *code, const char *msg) { + return CHECKED_ENV(env)->throwJSRangeError(code, msg); +} + +napi_status NAPI_CDECL +napi_is_error(napi_env env, napi_value value, bool *result) { + return CHECKED_ENV(env)->isJSError(value, result); +} + +//----------------------------------------------------------------------------- +// Methods to support catching exceptions +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, bool *result) { + return CHECKED_ENV(env)->isJSErrorPending(result); +} + +napi_status NAPI_CDECL +napi_get_and_clear_last_exception(napi_env env, napi_value *result) { + return CHECKED_ENV(env)->getAndClearPendingJSError(result); +} + +//----------------------------------------------------------------------------- +// Methods to work with array buffers and typed arrays +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_is_arraybuffer(napi_env env, napi_value value, bool *result) { + return CHECKED_ENV(env)->isArrayBuffer(value, result); +} + +napi_status NAPI_CDECL napi_create_arraybuffer( + napi_env env, + size_t byte_length, + void **data, + napi_value *result) { + return CHECKED_ENV(env)->createArrayBuffer(byte_length, data, result); +} + +napi_status NAPI_CDECL napi_create_external_arraybuffer( + napi_env env, + void *external_data, + size_t byte_length, + napi_finalize finalize_cb, + void *finalize_hint, + napi_value *result) { + return CHECKED_ENV(env)->createExternalArrayBuffer( + external_data, byte_length, finalize_cb, finalize_hint, result); +} + +napi_status NAPI_CDECL napi_get_arraybuffer_info( + napi_env env, + napi_value arraybuffer, + void **data, + size_t *byte_length) { + return CHECKED_ENV(env)->getArrayBufferInfo(arraybuffer, data, byte_length); +} + +napi_status NAPI_CDECL +napi_is_typedarray(napi_env env, napi_value value, bool *result) { + return CHECKED_ENV(env)->isTypedArray(value, result); +} + +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) { + return CHECKED_ENV(env)->createTypedArray( + type, length, arraybuffer, byte_offset, result); +} + +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) { + return CHECKED_ENV(env)->getTypedArrayInfo( + typedarray, type, length, data, arraybuffer, byte_offset); +} + +napi_status NAPI_CDECL napi_create_dataview( + napi_env env, + size_t byte_length, + napi_value arraybuffer, + size_t byte_offset, + napi_value *result) { + return CHECKED_ENV(env)->createDataView( + byte_length, arraybuffer, byte_offset, result); +} + +napi_status NAPI_CDECL +napi_is_dataview(napi_env env, napi_value value, bool *result) { + return CHECKED_ENV(env)->isDataView(value, result); +} + +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) { + return CHECKED_ENV(env)->getDataViewInfo( + dataview, byte_length, data, arraybuffer, byte_offset); +} + +//----------------------------------------------------------------------------- +// Version management +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_get_version(napi_env env, uint32_t *result) { + return CHECKED_ENV(env)->getVersion(result); +} + +//----------------------------------------------------------------------------- +// Promises +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_create_promise( + napi_env env, + napi_deferred *deferred, + napi_value *promise) { + return CHECKED_ENV(env)->createPromise(deferred, promise); +} + +napi_status NAPI_CDECL napi_resolve_deferred( + napi_env env, + napi_deferred deferred, + napi_value resolution) { + return CHECKED_ENV(env)->resolveDeferred(deferred, resolution); +} + +napi_status NAPI_CDECL napi_reject_deferred( + napi_env env, + napi_deferred deferred, + napi_value resolution) { + return CHECKED_ENV(env)->rejectDeferred(deferred, resolution); +} + +napi_status NAPI_CDECL +napi_is_promise(napi_env env, napi_value value, bool *is_promise) { + return CHECKED_ENV(env)->isPromise(value, is_promise); +} + +//----------------------------------------------------------------------------- +// Running a script +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_run_script(napi_env env, napi_value script, napi_value *result) { + return CHECKED_ENV(env)->runScript(script, nullptr, result); +} + +//----------------------------------------------------------------------------- +// Memory management +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_adjust_external_memory( + napi_env env, + int64_t change_in_bytes, + int64_t *adjusted_value) { + return CHECKED_ENV(env)->adjustExternalMemory( + change_in_bytes, adjusted_value); +} + +#if NAPI_VERSION >= 5 + +//----------------------------------------------------------------------------- +// Dates +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_create_date(napi_env env, double time, napi_value *result) { + return CHECKED_ENV(env)->createDate(time, result); +} + +napi_status NAPI_CDECL +napi_is_date(napi_env env, napi_value value, bool *is_date) { + return CHECKED_ENV(env)->isDate(value, is_date); +} + +napi_status NAPI_CDECL +napi_get_date_value(napi_env env, napi_value value, double *result) { + return CHECKED_ENV(env)->getDateValue(value, result); +} + +//----------------------------------------------------------------------------- +// Add finalizer for pointer +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_add_finalizer( + napi_env env, + napi_value js_object, + void *native_object, + napi_finalize finalize_cb, + void *finalize_hint, + napi_ref *result) { + return CHECKED_ENV(env)->addFinalizer( + js_object, native_object, finalize_cb, finalize_hint, result); +} + +#endif // NAPI_VERSION >= 5 + +#if NAPI_VERSION >= 6 + +//----------------------------------------------------------------------------- +// BigInt +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_create_bigint_int64(napi_env env, int64_t value, napi_value *result) { + return CHECKED_ENV(env)->createBigIntFromInt64(value, result); +} + +napi_status NAPI_CDECL +napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value *result) { + return CHECKED_ENV(env)->createBigIntFromUint64(value, result); +} + +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) { + return CHECKED_ENV(env)->createBigIntFromWords( + sign_bit, word_count, words, result); +} + +napi_status NAPI_CDECL napi_get_value_bigint_int64( + napi_env env, + napi_value value, + int64_t *result, + bool *lossless) { + return CHECKED_ENV(env)->getBigIntValueInt64(value, result, lossless); +} + +napi_status NAPI_CDECL napi_get_value_bigint_uint64( + napi_env env, + napi_value value, + uint64_t *result, + bool *lossless) { + return CHECKED_ENV(env)->getBigIntValueUint64(value, result, lossless); +} + +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) { + return CHECKED_ENV(env)->getBigIntValueWords( + value, sign_bit, word_count, words); +} + +//----------------------------------------------------------------------------- +// Object +//----------------------------------------------------------------------------- + +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) { + return CHECKED_ENV(env)->getAllPropertyNames( + object, key_mode, key_filter, key_conversion, result); +} + +//----------------------------------------------------------------------------- +// Instance data +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_set_instance_data( + napi_env env, + void *data, + napi_finalize finalize_cb, + void *finalize_hint) { + return CHECKED_ENV(env)->setInstanceData(data, finalize_cb, finalize_hint); +} + +napi_status NAPI_CDECL napi_get_instance_data(napi_env env, void **data) { + return CHECKED_ENV(env)->getInstanceData(data); +} + +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 7 + +//----------------------------------------------------------------------------- +// ArrayBuffer detaching +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL +napi_detach_arraybuffer(napi_env env, napi_value arraybuffer) { + return CHECKED_ENV(env)->detachArrayBuffer(arraybuffer); +} + +napi_status NAPI_CDECL napi_is_detached_arraybuffer( + napi_env env, + napi_value arraybuffer, + bool *result) { + return CHECKED_ENV(env)->isDetachedArrayBuffer(arraybuffer, result); +} + +#endif // NAPI_VERSION >= 7 + +#if NAPI_VERSION >= 8 + +//----------------------------------------------------------------------------- +// Type tagging +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL napi_type_tag_object( + napi_env env, + napi_value object, + const napi_type_tag *type_tag) { + return CHECKED_ENV(env)->typeTagObject(object, type_tag); +} + +napi_status NAPI_CDECL napi_check_object_type_tag( + napi_env env, + napi_value object, + const napi_type_tag *type_tag, + bool *result) { + return CHECKED_ENV(env)->checkObjectTypeTag(object, type_tag, result); +} + +napi_status NAPI_CDECL napi_object_freeze(napi_env env, napi_value object) { + return CHECKED_ENV(env)->objectFreeze(object); +} + +napi_status NAPI_CDECL napi_object_seal(napi_env env, napi_value object) { + return CHECKED_ENV(env)->objectSeal(object); +} + +#endif // NAPI_VERSION >= 8 + +//============================================================================= +// Hermes specific API +//============================================================================= + +napi_status hermes_create_napi_env( + ::hermes::vm::Runtime &runtime, + bool isInspectable, + std::shared_ptr preparedScript, + const ::hermes::vm::RuntimeConfig &runtimeConfig, + napi_env *env) { + if (!env) { + return napi_status::napi_invalid_arg; + } + *env = hermes::napi::napiEnv(new hermes::napi::NapiEnvironment( + runtime, isInspectable, std::move(preparedScript), runtimeConfig)); + return napi_status::napi_ok; +} + +//============================================================================= +// Node-API extensions to host JS engine and to implement JSI +//============================================================================= + +napi_status NAPI_CDECL jsr_env_ref(napi_env env) { + return CHECKED_ENV(env)->incRefCount(); +} + +napi_status NAPI_CDECL jsr_env_unref(napi_env env) { + return CHECKED_ENV(env)->decRefCount(); +} + +napi_status NAPI_CDECL jsr_collect_garbage(napi_env env) { + return CHECKED_ENV(env)->collectGarbage(); +} + +napi_status NAPI_CDECL +jsr_has_unhandled_promise_rejection(napi_env env, bool *result) { + return CHECKED_ENV(env)->hasUnhandledPromiseRejection(result); +} + +napi_status NAPI_CDECL jsr_get_and_clear_last_unhandled_promise_rejection( + napi_env env, + napi_value *result) { + return CHECKED_ENV(env)->getAndClearLastUnhandledPromiseRejection(result); +} + +napi_status NAPI_CDECL jsr_get_description(napi_env env, const char **result) { + return CHECKED_ENV(env)->getDescription(result); +} + +napi_status NAPI_CDECL +jsr_drain_microtasks(napi_env env, int32_t max_count_hint, bool *result) { + return CHECKED_ENV(env)->drainMicrotasks(max_count_hint, result); +} + +napi_status NAPI_CDECL jsr_is_inspectable(napi_env env, bool *result) { + return CHECKED_ENV(env)->isInspectable(result); +} + +JSR_API jsr_open_napi_env_scope(napi_env env, jsr_napi_env_scope *scope) { + return CHECKED_ENV(env)->openEnvScope(scope); +} + +JSR_API jsr_close_napi_env_scope(napi_env env, jsr_napi_env_scope scope) { + return CHECKED_ENV(env)->closeEnvScope(scope); +} + +//----------------------------------------------------------------------------- +// Script preparing and running. +// +// Script is usually converted to byte code, or in other words - prepared - for +// execution. Then, we can run the prepared script. +//----------------------------------------------------------------------------- + +napi_status NAPI_CDECL jsr_run_script( + napi_env env, + napi_value source, + const char *source_url, + napi_value *result) { + return CHECKED_ENV(env)->runScript(source, source_url, result); +} + +napi_status NAPI_CDECL jsr_create_prepared_script( + napi_env env, + const uint8_t *script_data, + size_t script_length, + jsr_data_delete_cb script_delete_cb, + void *deleter_data, + const char *source_url, + jsr_prepared_script *result) { + return CHECKED_ENV(env)->createPreparedScript( + script_data, + script_length, + script_delete_cb, + deleter_data, + source_url, + result); +} + +napi_status NAPI_CDECL +jsr_delete_prepared_script(napi_env env, jsr_prepared_script prepared_script) { + return CHECKED_ENV(env)->deletePreparedScript(prepared_script); +} + +napi_status NAPI_CDECL jsr_prepared_script_run( + napi_env env, + jsr_prepared_script prepared_script, + napi_value *result) { + return CHECKED_ENV(env)->runPreparedScript(prepared_script, result); +} diff --git a/API/node-api/.clang-format b/API/node-api/.clang-format new file mode 100644 index 00000000000..4aad29c328a --- /dev/null +++ b/API/node-api/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never diff --git a/API/node-api/CMakeLists.txt b/API/node-api/CMakeLists.txt new file mode 100644 index 00000000000..0f6d37a288d --- /dev/null +++ b/API/node-api/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(hermesNapi INTERFACE) +target_include_directories(hermesNapi INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +install(DIRECTORY "${PROJECT_SOURCE_DIR}/API/node-api/" DESTINATION include + FILES_MATCHING PATTERN "*.h") diff --git a/API/node-api/js_native_api.h b/API/node-api/js_native_api.h new file mode 100644 index 00000000000..eddd4da927c --- /dev/null +++ b/API/node-api/js_native_api.h @@ -0,0 +1,553 @@ +#ifndef SRC_JS_NATIVE_API_H_ +#define SRC_JS_NATIVE_API_H_ + +// This file needs to be compatible with C compilers. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION_EXPERIMENTAL 2147483647 +#ifndef NAPI_VERSION +// The baseline version for N-API. +// The NAPI_VERSION controls which version will be used by default when +// compilling a native addon. If the addon developer specifically wants to use +// functions available in a new version of N-API that is not yet ported in all +// LTS versions, they can set NAPI_VERSION knowing that they have specifically +// depended on that version. +#define NAPI_VERSION 8 +#endif + +#include "js_native_api_types.h" + +// If you need __declspec(dllimport), either include instead, or +// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. +#ifndef NAPI_EXTERN +#ifdef _WIN32 +#define NAPI_EXTERN __declspec(dllexport) +#elif defined(__wasm32__) +#define NAPI_EXTERN \ + __attribute__((visibility("default"))) \ + __attribute__((__import_module__("napi"))) +#else +#define NAPI_EXTERN __attribute__((visibility("default"))) +#endif +#endif + +#define NAPI_AUTO_LENGTH SIZE_MAX + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +EXTERN_C_START + +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); + +// Getters for defined singletons +NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_null(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_global(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env, + bool value, + napi_value* result); + +// Methods to create Primitive types/Objects +NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_array_with_length(napi_env env, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_double(napi_env env, + double value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_int32(napi_env env, + int32_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_latin1( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env, + napi_value description, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); + +// Methods to get the native napi_value from Primitive type +NAPI_EXTERN napi_status NAPI_CDECL napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_double(napi_env env, + napi_value value, + double* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bool(napi_env env, + napi_value value, + bool* result); + +// Copies LATIN-1 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_latin1( + napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); + +// Copies UTF-8 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf8( + napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); + +// Copies UTF-16 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result); + +// Methods to coerce values +// These APIs may execute user scripts +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result); + +// Methods to work with Objects +NAPI_EXTERN napi_status NAPI_CDECL napi_get_prototype(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties); + +// Methods to work with Arrays +NAPI_EXTERN napi_status NAPI_CDECL napi_is_array(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result); + +// Methods to compare values +NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result); + +// Methods to work with Functions +NAPI_EXTERN 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_EXTERN napi_status NAPI_CDECL napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result); + +// Methods to work with napi_callbacks + +// Gets all callback info in a single call. (Ugly, but faster.) +NAPI_EXTERN 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. + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_new_target( + napi_env env, napi_callback_info cbinfo, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); + +// Methods to work with external data objects +NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, + napi_value value, + void** result); + +// Methods to control object lifespan + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result); + +// Deletes a reference. The referenced value is released, and may +// be GC'd unless there are other references to it. +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_reference(napi_env env, + napi_ref ref); + +// 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_EXTERN napi_status NAPI_CDECL napi_reference_ref(napi_env env, + napi_ref ref, + uint32_t* result); + +// 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_EXTERN napi_status NAPI_CDECL napi_reference_unref(napi_env env, + napi_ref ref, + uint32_t* result); + +// 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_EXTERN napi_status NAPI_CDECL napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_open_handle_scope(napi_env env, napi_handle_scope* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_close_handle_scope(napi_env env, napi_handle_scope scope); +NAPI_EXTERN napi_status NAPI_CDECL napi_open_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_close_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope scope); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); + +// Methods to support error handling +NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_range_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_error(napi_env env, + napi_value value, + bool* result); + +// Methods to support catching exceptions +NAPI_EXTERN napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_and_clear_last_exception(napi_env env, napi_value* result); + +// Methods to work with array buffers and typed arrays +NAPI_EXTERN napi_status NAPI_CDECL napi_is_arraybuffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info( + napi_env env, napi_value arraybuffer, void** data, size_t* byte_length); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_typedarray(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN 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_EXTERN 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); + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_dataview(napi_env env, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_dataview(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* bytelength, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +// version management +NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(napi_env env, + uint32_t* result); + +// Promises +NAPI_EXTERN napi_status NAPI_CDECL napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise); +NAPI_EXTERN napi_status NAPI_CDECL napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution); +NAPI_EXTERN napi_status NAPI_CDECL napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_promise(napi_env env, + napi_value value, + bool* is_promise); + +// Running a script +NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, + napi_value script, + napi_value* result); + +// Memory management +NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( + napi_env env, int64_t change_in_bytes, int64_t* adjusted_value); + +#if NAPI_VERSION >= 5 + +// Dates +NAPI_EXTERN napi_status NAPI_CDECL napi_create_date(napi_env env, + double time, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_is_date(napi_env env, + napi_value value, + bool* is_date); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env, + napi_value value, + double* result); + +// Add finalizer for pointer +NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env, + napi_value js_object, + void* finalize_data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); + +#endif // NAPI_VERSION >= 5 + +#if NAPI_VERSION >= 6 + +// BigInt +NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result); +NAPI_EXTERN 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_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_uint64( + napi_env env, napi_value value, uint64_t* result, bool* lossless); +NAPI_EXTERN 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); + +// Object +NAPI_EXTERN 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); + +// Instance data +NAPI_EXTERN napi_status NAPI_CDECL napi_set_instance_data( + napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(napi_env env, + void** data); +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 7 +// ArrayBuffer detaching +NAPI_EXTERN napi_status NAPI_CDECL +napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result); +#endif // NAPI_VERSION >= 7 + +#if NAPI_VERSION >= 8 +// Type tagging +NAPI_EXTERN napi_status NAPI_CDECL napi_type_tag_object( + napi_env env, napi_value value, const napi_type_tag* type_tag); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_check_object_type_tag(napi_env env, + napi_value value, + const napi_type_tag* type_tag, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_object_freeze(napi_env env, + napi_value object); +NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, + napi_value object); +#endif // NAPI_VERSION >= 8 + +EXTERN_C_END + +#endif // SRC_JS_NATIVE_API_H_ \ No newline at end of file diff --git a/API/node-api/js_native_api_types.h b/API/node-api/js_native_api_types.h new file mode 100644 index 00000000000..87affb6b51f --- /dev/null +++ b/API/node-api/js_native_api_types.h @@ -0,0 +1,167 @@ +#ifndef SRC_JS_NATIVE_API_TYPES_H_ +#define SRC_JS_NATIVE_API_TYPES_H_ + +// This file needs to be compatible with C compilers. +// This is a public include file, and these includes have essentially +// became part of it's API. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) +typedef uint16_t char16_t; +#endif + +#ifndef NAPI_CDECL +#ifdef _WIN32 +#define NAPI_CDECL __cdecl +#else +#define NAPI_CDECL +#endif +#endif + +// JSVM API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_env__* napi_env; +typedef struct napi_value__* napi_value; +typedef struct napi_ref__* napi_ref; +typedef struct napi_handle_scope__* napi_handle_scope; +typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; +typedef struct napi_callback_info__* napi_callback_info; +typedef struct napi_deferred__* napi_deferred; + +typedef enum { + napi_default = 0, + napi_writable = 1 << 0, + napi_enumerable = 1 << 1, + napi_configurable = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static = 1 << 10, + +#if NAPI_VERSION >= 8 + // Default for class methods. + napi_default_method = napi_writable | napi_configurable, + + // Default for object properties, like in JS obj[prop]. + napi_default_jsproperty = napi_writable | napi_enumerable | napi_configurable, +#endif // NAPI_VERSION >= 8 +} napi_property_attributes; + +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, + napi_bigint, +} napi_valuetype; + +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, +} napi_typedarray_type; + +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch, + napi_queue_full, + napi_closing, + napi_bigint_expected, + napi_date_expected, + napi_arraybuffer_expected, + napi_detachable_arraybuffer_expected, + napi_would_deadlock, // unused + napi_no_external_buffers_allowed +} napi_status; +// Note: when adding a new enum value to `napi_status`, please also update +// * `const int last_status` in the definition of `napi_get_last_error_info()' +// in file js_native_api_v8.cc. +// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief +// message explaining the error. +// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly +// added value(s). + +typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, + napi_callback_info info); +typedef void(NAPI_CDECL* napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); + +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; + +#if NAPI_VERSION >= 6 +typedef enum { + napi_key_include_prototypes, + napi_key_own_only +} napi_key_collection_mode; + +typedef enum { + napi_key_all_properties = 0, + napi_key_writable = 1, + napi_key_enumerable = 1 << 1, + napi_key_configurable = 1 << 2, + napi_key_skip_strings = 1 << 3, + napi_key_skip_symbols = 1 << 4 +} napi_key_filter; + +typedef enum { + napi_key_keep_numbers, + napi_key_numbers_to_strings +} napi_key_conversion; +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 8 +typedef struct { + uint64_t lower; + uint64_t upper; +} napi_type_tag; +#endif // NAPI_VERSION >= 8 + +#endif // SRC_JS_NATIVE_API_TYPES_H_ \ No newline at end of file diff --git a/API/node-api/js_runtime_api.h b/API/node-api/js_runtime_api.h new file mode 100644 index 00000000000..d16e33e3a75 --- /dev/null +++ b/API/node-api/js_runtime_api.h @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef SRC_JS_RUNTIME_API_H_ +#define SRC_JS_RUNTIME_API_H_ + +#include "js_native_api.h" + +// +// Node-API extensions required for JavaScript engine hosting. +// +// It is a very early version of the APIs which we consider to be experimental. +// These APIs are not stable yet and are subject to change while we continue +// their development. After some time we will stabilize the APIs and make them +// "officially stable". +// + +#define JSR_API NAPI_EXTERN napi_status NAPI_CDECL + +EXTERN_C_START + +typedef struct jsr_runtime_s* jsr_runtime; +typedef struct jsr_config_s* jsr_config; +typedef struct jsr_prepared_script_s* jsr_prepared_script; +typedef struct jsr_napi_env_scope_s* jsr_napi_env_scope; + +typedef void(NAPI_CDECL* jsr_data_delete_cb)(void* data, void* deleter_data); + +//============================================================================= +// jsr_runtime +//============================================================================= + +JSR_API jsr_create_runtime(jsr_config config, jsr_runtime* runtime); +JSR_API jsr_delete_runtime(jsr_runtime runtime); +JSR_API jsr_runtime_get_node_api_env(jsr_runtime runtime, napi_env* env); + +//============================================================================= +// jsr_config +//============================================================================= + +JSR_API jsr_create_config(jsr_config* config); +JSR_API jsr_delete_config(jsr_config config); + +JSR_API jsr_config_enable_inspector(jsr_config config, bool value); +JSR_API jsr_config_set_inspector_runtime_name(jsr_config config, + const char* name); +JSR_API jsr_config_set_inspector_port(jsr_config config, uint16_t port); +JSR_API jsr_config_set_inspector_break_on_start(jsr_config config, bool value); + +JSR_API jsr_config_enable_gc_api(jsr_config config, bool value); + +//============================================================================= +// jsr_config task runner +//============================================================================= + +// A callback to run task +typedef void(NAPI_CDECL* jsr_task_run_cb)(void* task_data); + +// A callback to post task to the task runner +typedef void(NAPI_CDECL* jsr_task_runner_post_task_cb)( + void* task_runner_data, + void* task_data, + jsr_task_run_cb task_run_cb, + jsr_data_delete_cb task_data_delete_cb, + void* deleter_data); + +JSR_API jsr_config_set_task_runner( + jsr_config config, + void* task_runner_data, + jsr_task_runner_post_task_cb task_runner_post_task_cb, + jsr_data_delete_cb task_runner_data_delete_cb, + void* deleter_data); + +//============================================================================= +// jsr_config script cache +//============================================================================= + +typedef void(NAPI_CDECL* jsr_script_cache_load_cb)( + void* script_cache_data, + const char* source_url, + uint64_t source_hash, + const char* runtime_name, + uint64_t runtime_version, + const char* cache_tag, + const uint8_t** buffer, + size_t* buffer_size, + jsr_data_delete_cb* buffer_delete_cb, + void** deleter_data); + +typedef void(NAPI_CDECL* jsr_script_cache_store_cb)( + void* script_cache_data, + const char* source_url, + uint64_t source_hash, + const char* runtime_name, + uint64_t runtime_version, + const char* cache_tag, + const uint8_t* buffer, + size_t buffer_size, + jsr_data_delete_cb buffer_delete_cb, + void* deleter_data); + +JSR_API jsr_config_set_script_cache( + jsr_config config, + void* script_cache_data, + jsr_script_cache_load_cb script_cache_load_cb, + jsr_script_cache_store_cb script_cache_store_cb, + jsr_data_delete_cb script_cache_data_delete_cb, + void* deleter_data); + +//============================================================================= +// napi_env scope +//============================================================================= + +// Opens the napi_env scope in the current thread. +// Calling Node-API functions without the opened scope may cause a failure. +// The scope must be closed by the jsr_close_napi_env_scope call. +JSR_API jsr_open_napi_env_scope(napi_env env, jsr_napi_env_scope* scope); + +// Closes the napi_env scope in the current thread. It must match to the +// jsr_open_napi_env_scope call. +JSR_API jsr_close_napi_env_scope(napi_env env, jsr_napi_env_scope scope); + +//============================================================================= +// Additional functions to implement JSI +//============================================================================= + +// To implement JSI description() +JSR_API jsr_get_description(napi_env env, const char** result); + +// To implement JSI drainMicrotasks() +JSR_API +jsr_drain_microtasks(napi_env env, int32_t max_count_hint, bool* result); + +// To implement JSI isInspectable() +JSR_API jsr_is_inspectable(napi_env env, bool* result); + +//============================================================================= +// Script preparing and running. +// +// Script is usually converted to byte code, or in other words - prepared - for +// execution. Then, we can run the prepared script. +//============================================================================= + +// Run script with source URL. +JSR_API jsr_run_script(napi_env env, + napi_value source, + const char* source_url, + napi_value* result); + +// Prepare the script for running. +JSR_API jsr_create_prepared_script(napi_env env, + const uint8_t* script_data, + size_t script_length, + jsr_data_delete_cb script_delete_cb, + void* deleter_data, + const char* source_url, + jsr_prepared_script* result); + +// Delete the prepared script. +JSR_API jsr_delete_prepared_script(napi_env env, + jsr_prepared_script prepared_script); + +// Run the prepared script. +JSR_API jsr_prepared_script_run(napi_env env, + jsr_prepared_script prepared_script, + napi_value* result); + +//============================================================================= +// Functions to support unit tests. +//============================================================================= + +// Provides a hint to run garbage collection. +// It is typically used for unit tests. +// It requires enabling GC by calling jsr_config_enable_gc_api. +JSR_API jsr_collect_garbage(napi_env env); + +// Checks if the environment has an unhandled promise rejection. +JSR_API jsr_has_unhandled_promise_rejection(napi_env env, bool* result); + +// Gets and clears the last unhandled promise rejection. +JSR_API jsr_get_and_clear_last_unhandled_promise_rejection(napi_env env, + napi_value* result); + +EXTERN_C_END + +#endif // !SRC_JS_RUNTIME_API_H_