Files
paseo/vendor/react-native-gpui/packages/hermes/patches/0002-windows-load-node-api-modules.patch
T

216 lines
8.3 KiB
Diff
Vendored

diff --git a/API/napi/hermes_napi.cpp b/API/napi/hermes_napi.cpp
index 777171e..a423d5b 100644
--- a/API/napi/hermes_napi.cpp
+++ b/API/napi/hermes_napi.cpp
@@ -20,8 +20,11 @@
#ifndef _WIN32
#include <dlfcn.h>
+#else
+#include <windows.h>
#endif
#include <cstring>
+#include <string>
//===========================================================================
// Error messages for napi_status codes.
@@ -801,6 +804,105 @@ const napi_module *hermes_napi_get_last_registered_module() {
// Module loading
//===========================================================================
+#ifdef _WIN32
+/// Converts a UTF-8 path to UTF-16, the form LoadLibraryExW takes. Returns
+/// false when the input is not valid UTF-8.
+static bool hermes_napi_utf8ToWide(const char *utf8, std::wstring &out) {
+ // An empty path is never valid, and MultiByteToWideChar rejects a zero
+ // length, so reject it before asking.
+ if (utf8 == nullptr || *utf8 == '\0') {
+ return false;
+ }
+ const int length = MultiByteToWideChar(
+ CP_UTF8, MB_ERR_INVALID_CHARS, utf8, -1, nullptr, 0);
+ if (length <= 0) {
+ return false;
+ }
+ out.resize(static_cast<size_t>(length));
+ // The call writes the terminating NUL too; resize() has already made room
+ // for it, and the returned buffer is one longer than the text.
+ const int written = MultiByteToWideChar(
+ CP_UTF8, MB_ERR_INVALID_CHARS, utf8, -1, &out[0], length);
+ if (written <= 0) {
+ return false;
+ }
+ // Drop the terminator MultiByteToWideChar wrote so the string's own
+ // terminator is the only one.
+ out.resize(static_cast<size_t>(written - 1));
+ return true;
+}
+
+/// The system's message for the last failed Win32 call, as UTF-8. This is what
+/// tells a user that an addon needs `uv_*`, or was built without the
+/// node-gyp delay-load hook (a missing `node.exe` import).
+static std::string hermes_napi_lastErrorMessage() {
+ const DWORD error = GetLastError();
+ if (error == 0) {
+ return "unknown error";
+ }
+ LPWSTR buffer = nullptr;
+ const DWORD length = FormatMessageW(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr,
+ error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ reinterpret_cast<LPWSTR>(&buffer),
+ 0,
+ nullptr);
+ std::string message;
+ if (length == 0 || buffer == nullptr) {
+ message = "error " + std::to_string(error);
+ if (buffer != nullptr) {
+ LocalFree(buffer);
+ }
+ return message;
+ }
+ const int utf8Length = WideCharToMultiByte(
+ CP_UTF8,
+ 0,
+ buffer,
+ static_cast<int>(length),
+ nullptr,
+ 0,
+ nullptr,
+ nullptr);
+ if (utf8Length > 0) {
+ message.resize(static_cast<size_t>(utf8Length));
+ WideCharToMultiByte(
+ CP_UTF8,
+ 0,
+ buffer,
+ static_cast<int>(length),
+ &message[0],
+ utf8Length,
+ nullptr,
+ nullptr);
+ } else {
+ message = "error " + std::to_string(error);
+ }
+ LocalFree(buffer);
+ // FormatMessage's text ends with a newline; a raised TypeError should not.
+ while (!message.empty() &&
+ (message.back() == '\n' || message.back() == '\r' ||
+ message.back() == ' ' || message.back() == '.')) {
+ message.pop_back();
+ }
+ // Some system messages are templates — "193: %1 is not a valid Win32
+ // application" — and `FORMAT_MESSAGE_IGNORE_INSERTS` leaves the placeholder
+ // in place because there is no argument array to fill it from. The only
+ // insert these messages take is the name of the file, so substitute the
+ // caller-visible noun for it rather than showing a user a bare `%1`.
+ static const char *const kPlaceholder = "%1";
+ for (std::string::size_type at = message.find(kPlaceholder);
+ at != std::string::npos;
+ at = message.find(kPlaceholder, at)) {
+ message.replace(at, sizeof("%1") - 1, "the file");
+ }
+ return message;
+}
+#endif
+
napi_status
hermes_napi_load_module(napi_env env, const char *path, napi_value *result) {
NAPI_PREAMBLE(env);
@@ -810,10 +912,64 @@ hermes_napi_load_module(napi_env env, const char *path, napi_value *result) {
using namespace hermes::vm;
Runtime &runtime = env->runtime;
+ // The addon's entry point. Each branch below opens the library its own way and
+ // fills this in; everything after the `#endif` is shared by both. This is
+ // declared out here because upstream declared it inside the `#else`, which put
+ // the whole tail — creating the exports object, calling the entry point, and
+ // the `return` — inside that branch. On Windows the function then fell off the
+ // end and returned whatever happened to be in the return register, which
+ // surfaced as `napi_invalid_arg` with no last-error set.
+ using InitFunc = napi_value (*)(napi_env, napi_value);
+ InitFunc initFunc = nullptr;
+
#ifdef _WIN32
- (void)runtime.raiseTypeError(
- TwineChar16("Loading native modules is not supported on Windows"));
- return captureRuntimeException(env, napi_pending_exception);
+ // Windows has no dlopen. A DLL is loaded with LoadLibraryExW and the path
+ // must be UTF-16; the caller always passes an absolute path.
+ std::wstring widePath;
+ if (!hermes_napi_utf8ToWide(path, widePath)) {
+ (void)runtime.raiseTypeError(
+ TwineChar16("Failed to load module: the path is not valid UTF-8"));
+ return captureRuntimeException(env, napi_pending_exception);
+ }
+
+ // LOAD_WITH_ALTERED_SEARCH_PATH searches the loaded DLL's own directory for
+ // its dependencies, so an addon that ships a sibling DLL finds it.
+ HMODULE handle =
+ LoadLibraryExW(widePath.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (handle == nullptr) {
+ // Read the error and format it before anything else can overwrite
+ // GetLastError; `TwineChar16` does not own the string, so it has to outlive
+ // the expression that raises with it.
+ const std::string reason = hermes_napi_lastErrorMessage();
+ (void)runtime.raiseTypeError(
+ TwineChar16("Failed to load module: ") + TwineChar16(reason));
+ return captureRuntimeException(env, napi_pending_exception);
+ }
+
+ // Look up the modern symbol-based entry point.
+ initFunc = reinterpret_cast<InitFunc>(
+ GetProcAddress(handle, "napi_register_module_v1"));
+
+ // If the modern symbol isn't found, check the deprecated
+ // napi_module_register() path. The shared library's static constructors may
+ // have called napi_module_register() during LoadLibraryExW.
+ const napi_module *mod = nullptr;
+ if (!initFunc) {
+ mod = hermes_napi_get_last_registered_module();
+ if (mod && mod->nm_register_func) {
+ initFunc = mod->nm_register_func;
+ }
+ }
+
+ if (!initFunc) {
+ // The library is deliberately not unloaded, matching the other branch: a
+ // module may already have registered itself through napi_module_register,
+ // and unloading would leave a dangling pointer in lastRegisteredModule.
+ (void)runtime.raiseTypeError(TwineChar16(
+ "Module has no 'napi_register_module_v1' "
+ "export and no registered napi_module"));
+ return captureRuntimeException(env, napi_pending_exception);
+ }
#else
// Open the shared library. RTLD_LOCAL prevents symbol collisions
// between different addons. RTLD_NOW ensures all symbols are resolved
@@ -827,9 +983,7 @@ hermes_napi_load_module(napi_env env, const char *path, napi_value *result) {
}
// Look up the modern symbol-based entry point.
- using InitFunc = napi_value (*)(napi_env, napi_value);
- auto initFunc =
- reinterpret_cast<InitFunc>(dlsym(handle, "napi_register_module_v1"));
+ initFunc = reinterpret_cast<InitFunc>(dlsym(handle, "napi_register_module_v1"));
// If the modern symbol isn't found, check the deprecated
// napi_module_register() path. The shared library's static
@@ -850,6 +1004,7 @@ hermes_napi_load_module(napi_env env, const char *path, napi_value *result) {
"export and no registered napi_module"));
return captureRuntimeException(env, napi_pending_exception);
}
+#endif
// Record the module filename on the caller's env.
env->moduleFileName_ = path;
@@ -877,5 +1032,4 @@ hermes_napi_load_module(napi_env env, const char *path, napi_value *result) {
}
return napi_clear_last_error(env);
-#endif
}