Skip to content

Fix v8::External::Value/New for the new ExternalPointerTypeTag API#1015

Open
gdavidkov wants to merge 1 commit intonodejs:mainfrom
gdavidkov:fix/external-value-v8-13
Open

Fix v8::External::Value/New for the new ExternalPointerTypeTag API#1015
gdavidkov wants to merge 1 commit intonodejs:mainfrom
gdavidkov:fix/external-value-v8-13

Conversation

@gdavidkov
Copy link
Copy Markdown
Contributor

@gdavidkov gdavidkov commented May 5, 2026

Problem

V8 added an ExternalPointerTypeTag parameter to v8::External::New and v8::External::Value and removed the prior overloads:

static Local<External> New(Isolate*, void*, ExternalPointerTypeTag);
void* Value(ExternalPointerTypeTag) const;

The new signatures are gated by V8's external-pointer-tagging build configuration, signaled by the public macro V8_EXTERNAL_POINTER_TAG_COUNT. A V8_MAJOR_VERSION check is unreliable here because Node and Chromium ship divergent V8 builds — the feature can be enabled in Electron's V8 and not in upstream Node's V8 at the same nominal major version.

nan still calls the removed forms — 28 sites in nan_callbacks_12_inl.h (the imp::*CallbackWrapper trampolines) and 3 in nan_implementation_12_inl.h (the External / Function / FunctionTemplate factories). Both are header-inline, so any addon that includes <nan.h> fails to build against a tag-enabled V8. Reproduced against Electron 42.0.0-beta.8 (Node 24.15.0) on Windows + MSVC 2022:

nan_callbacks_12_inl.h(189): error C2664:
  cannot convert from 'v8::Isolate *' to 'v8::ExternalPointerTypeTag'
nan_implementation_12_inl.h(79): error C2660:
  'v8::External::New' does not take 2 arguments

Fix

Two private inline helpers in the existing imp:: namespace, one per file next to its callers:

inline void* GetExternalPointer(v8::Local<v8::External> ext);
inline v8::Local<v8::External> NewExternal(v8::Isolate*, void*);

Each selects the tag-taking signature when V8_EXTERNAL_POINTER_TAG_COUNT is defined, passing v8::kExternalPointerTypeTagDefault (V8's no-tagging default for embedders that do not partition externals by type), and falls back to the legacy form otherwise. All 31 callsites are routed through the helpers. Both use the same tag, so create/read tags match by construction.

Backwards compatibility

Every change is gated on #ifdef V8_EXTERNAL_POINTER_TAG_COUNT. The #else branches reproduce the prior code byte-for-byte — no-op on every V8 build that doesn't define the macro.

Tests

test/cpp/news.cpp::NewExternal and test/cpp/nannew.cpp::testExternal round-trip a void* through Nan::New<v8::External> and read it back via raw v8::External::Value(). They hit the same compile break as nan's headers; the same gate is applied at each test site. NewExternal exercises imp::NewExternal directly; imp::GetExternalPointer is exercised every time any NAN_METHOD test runs from JS, since imp::FunctionCallbackWrapper unpacks the registered callback through it before dispatch.

Out of scope

V8 14 work already landed (#1010, #1013); this PR addresses only the External API change. nan_callbacks_pre_12_inl.h / nan_implementation_pre_12_inl.h target Node ≤ 0.12 (pre-V8-3.x), which uses a still-older single-arg form, so a tag-enabled V8 never selects those files.

Verified

Builds clean against Electron 42.0.0-beta.8 (Node 24.15.0) on Windows + MSVC 2022. CI will exercise both branches across the existing matrix.

@gdavidkov gdavidkov marked this pull request as draft May 5, 2026 19:30
V8 13 added an ExternalPointerTypeTag parameter (uint16_t pointer-tag for the
sandbox) to both v8::External::New and v8::External::Value:

  static Local<External> New(Isolate*, void*, ExternalPointerTypeTag);
  void* Value(ExternalPointerTypeTag) const;

The old zero-arg Value() and two-arg New(Isolate*, void*) overloads are gone,
so any nan-using addon fails to compile against Electron 42 / Node 24's V8:

  nan_callbacks_12_inl.h(189): error C2664:
    'void *v8::External::Value(v8::ExternalPointerTypeTag) const':
    cannot convert argument 1 from 'v8::Isolate *' to 'v8::ExternalPointerTypeTag'
  nan_implementation_12_inl.h(79): error C2660:
    'v8::External::New': function does not take 2 arguments

Add two private inline helpers in the existing imp:: namespace:

  imp::GetExternalPointer(v8::Local<v8::External>)
  imp::NewExternal(v8::Isolate*, void*)

Each selects the V8 13+ signature when V8_MAJOR_VERSION >= 13 (passing
v8::kExternalPointerTypeTagDefault, the documented no-tagging default for
embedders that do not partition externals by type) and falls back to the
legacy signature otherwise.  All 28 Value() trampoline callsites in
nan_callbacks_12_inl.h and all 3 New() factory callsites in
nan_implementation_12_inl.h are routed through the helpers.  Both helpers
share kExternalPointerTypeTagDefault so the tag matches between create and
read.  No new macros are introduced.

Tests
-----

The existing news.cpp::NewExternal and nannew.cpp::testExternal cases call
v8::External::Value() directly (consumer-side raw-V8 calls, not through nan),
so they failed to compile against V8 13 in the same way as nan's headers.
Added the same V8 13 guard inline at those two sites so the tests build and
run on V8 13 while remaining identical on older V8.  Together these two
existing tests already cover the round-trip exercised by this fix:

  - news.cpp::NewExternal exercises the factory path:
      Nan::New<v8::External>(...)  ->  Factory<v8::External>::New(value)
                                   ->  imp::NewExternal(isolate, value)
                                   ->  v8::External::New(isolate, value, tag)
  - The trampoline path (imp::GetExternalPointer) is exercised every time
    any NAN_METHOD test is invoked from JS, including news.cpp::NewExternal
    itself, since FunctionCallbackWrapper unpacks the registered callback
    pointer through that helper before dispatching.

Scope and what is not changed
-----------------------------

This patch addresses the v8::External break specifically.  Other unrelated
V8 13 API changes in nan, if any, are out of scope (mirroring the prior
nan_weak.h fix which was a separate, surgical commit).

nan_callbacks_pre_12_inl.h and nan_implementation_pre_12_inl.h are
intentionally untouched: they target NODE_MODULE_VERSION <=
NODE_0_12_MODULE_VERSION (Node <= 0.12, pre-V8-3.x), where neither overload
exists.

Backwards compatibility
-----------------------

Every change is gated on `defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 13`.
On V8 <= 12 the else branch reproduces the original code byte-for-byte, so
the patch is a no-op for every V8 version nan currently supports.
@gdavidkov gdavidkov force-pushed the fix/external-value-v8-13 branch from 7cfa358 to 59d874d Compare May 5, 2026 19:34
@gdavidkov gdavidkov changed the title Fix v8::External::Value/New for V8 13 (ExternalPointerTypeTag) Fix v8::External::Value/New for the new ExternalPointerTypeTag API May 5, 2026
@gdavidkov gdavidkov marked this pull request as ready for review May 5, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant