diff --git a/bindings/otel-thread-ctx.cc b/bindings/otel-thread-ctx.cc index b7c7dfd4..c2ec2669 100644 --- a/bindings/otel-thread-ctx.cc +++ b/bindings/otel-thread-ctx.cc @@ -901,40 +901,38 @@ void GetStoredAlsHash(const FunctionCallbackInfo& args) { Integer::New(isolate, otel_thread_ctx_nodejs_v1.als_identity_hash)); } -// V8 layout constants captured at addon-compile time from the same V8 -// headers Node bundles. Published via the discovery contract so an -// out-of-process reader can decode V8's JSObject / internal hashmap -// layout without doing its own V8-internal-symbol lookups for the -// pointer-compression / sandbox state. Note that nothing published here -// describes our own wrapper: internal field 0 points straight at the -// record, so the reader needs no offset of ours to reach it. +// The nodejs_v1 discovery schema does not publish V8's object layout; it +// fixes it, presuming the V8 Node.js builds by default: 64-bit, pointer +// compression off, sandbox off. These assertions check that presumption +// against the V8 headers we are compiled with, so a build not matching +// the schema will fail to compile. +// +// Each value the reader needs equals one of V8's public constants: +// tagged size (8) kApiTaggedSize +// JSMap table offset (0x18) kJSObjectHeaderSize, because JSCollection +// adds a single `table` field to JSObject +// (deps/v8/src/objects/js-collection.h) +// OrderedHashMap header kFixedArrayHeaderSize, because +// size (0x10) OrderedHashTable derives from FixedArray +// (deps/v8/src/objects/ordered-hash-table.h) +// record slot offset (0x18) kJSObjectHeaderSize plus +// kEmbedderDataSlotExternalPointerOffset, +// which is 0 without the sandbox: internal +// field 0 then holds the raw record pointer +static_assert(v8::internal::kApiTaggedSize == 8, + "nodejs_v1 assumes a V8 built without pointer compression"); +static_assert(v8::internal::Internals::kJSObjectHeaderSize == 0x18, + "unexpected V8 JSObject header size"); +static_assert(v8::internal::Internals::kFixedArrayHeaderSize == 0x10, + "unexpected V8 FixedArray header size"); #if NODE_MAJOR_VERSION >= 22 -constexpr int JS_OBJECT_RECORD_OFFSET = - v8::internal::Internals::kJSObjectHeaderSize + +// Node < 22 lacks this constant; the contract is unusable there anyway, +// as it has no ContinuationPreservedEmbedderData either (see StoreAls). +constexpr int kEmbedderDataSlotExternalPtrOffset = v8::internal::Internals::kEmbedderDataSlotExternalPointerOffset; -#else -// Node < 22 lacks kEmbedderDataSlotExternalPointerOffset. The discovery -// contract isn't usable on these versions (no ContinuationPreservedEmbedderData -// either — see StoreAls), so this value is published only to keep the -// addon's exported surface consistent across Node majors. A would-be -// reader cannot reach a live record through it. -constexpr int JS_OBJECT_RECORD_OFFSET = 0; +static_assert(kEmbedderDataSlotExternalPtrOffset == 0, + "nodejs_v1 assumes a V8 built without the sandbox"); #endif -constexpr int TAGGED_SIZE = v8::internal::kApiTaggedSize; - -// V8 JSMap layout: kTableOffset within the JSMap object holds a tagged -// pointer to the backing OrderedHashMap table. Not exposed in V8's -// public headers; kept in sync with -// deps/v8/src/objects/js-collection.h (JSCollection::kTableOffset) -// and the torque-generated JSCollection layout. -constexpr int JS_MAP_TABLE_OFFSET = 0x18; - -// V8 OrderedHashMap layout: the on-heap table starts with a 16-byte -// header before the element_count / deleted_element_count / -// number_of_buckets fields. Not exposed in V8's public headers; kept in -// sync with deps/v8/src/objects/ordered-hash-table.h -// (OrderedHashTable base layout). -constexpr int ORDERED_HASH_MAP_HEADER_SIZE = 0x10; } // namespace @@ -942,21 +940,6 @@ void OtelThreadCtx::Init(Local exports) { CtxWrap::Init(exports); NODE_SET_METHOD(exports, "otelThreadCtxStoreAls", StoreAls); NODE_SET_METHOD(exports, "otelThreadCtxGetStoredAlsHash", GetStoredAlsHash); - - Isolate* isolate = Isolate::GetCurrent(); - Local ctx = isolate->GetCurrentContext(); - auto publish_int = [&](const char* name, int value) { - exports - ->Set(ctx, - String::NewFromUtf8(isolate, name).ToLocalChecked(), - Integer::New(isolate, value)) - .FromJust(); - }; - publish_int("otelThreadCtxJsMapTableOffset", JS_MAP_TABLE_OFFSET); - publish_int("otelThreadCtxOrderedHashMapHeaderSize", - ORDERED_HASH_MAP_HEADER_SIZE); - publish_int("otelThreadCtxTaggedSize", TAGGED_SIZE); - publish_int("otelThreadCtxJsObjectRecordOffset", JS_OBJECT_RECORD_OFFSET); } } // namespace dd diff --git a/ts/src/otel-thread-ctx.ts b/ts/src/otel-thread-ctx.ts index d2a4c1e7..8df1ae5d 100644 --- a/ts/src/otel-thread-ctx.ts +++ b/ts/src/otel-thread-ctx.ts @@ -48,10 +48,6 @@ import { export interface ProcessContextAttributes { readonly 'threadlocal.schema_version': 'nodejs_v1_dev'; readonly 'threadlocal.attribute_key_map': readonly string[]; - readonly 'threadlocal.js_object_record_offset': number; - readonly 'threadlocal.tagged_size': number; - readonly 'threadlocal.js_map_table_offset': number; - readonly 'threadlocal.ordered_hash_map_header_size': number; } /** @@ -142,24 +138,10 @@ interface Addon { threadContext: ThreadContextCtor; otelThreadCtxStoreAls(als: AsyncLocalStorage): void; otelThreadCtxGetStoredAlsHash(): number; - otelThreadCtxJsObjectRecordOffset: number; - otelThreadCtxTaggedSize: number; - otelThreadCtxJsMapTableOffset: number; - otelThreadCtxOrderedHashMapHeaderSize: number; } const SCHEMA_VERSION = 'nodejs_v1_dev'; -// V8 layout constants the addon captured from the V8 headers Node bundles. -// On non-Linux these fall back to values matching Node's standard build -// (no V8 pointer compression, no sandbox); the reader is Linux-only per -// the OTEP anyway, so the fallbacks just keep processContextAttributes -// consistent in shape. -let JS_OBJECT_RECORD_OFFSET = 0x18; -let TAGGED_SIZE = 8; -let JS_MAP_TABLE_OFFSET = 0x18; -let ORDERED_HASH_MAP_HEADER_SIZE = 0x10; - /** {@inheritDoc ThreadContextCtor} */ export let ThreadContext: ThreadContextCtor; @@ -183,10 +165,6 @@ if (process.platform === 'linux') { // eslint-disable-next-line @typescript-eslint/no-require-imports const findBinding = require('node-gyp-build'); const addon: Addon = findBinding(join(__dirname, '..', '..')); - JS_OBJECT_RECORD_OFFSET = addon.otelThreadCtxJsObjectRecordOffset; - TAGGED_SIZE = addon.otelThreadCtxTaggedSize; - JS_MAP_TABLE_OFFSET = addon.otelThreadCtxJsMapTableOffset; - ORDERED_HASH_MAP_HEADER_SIZE = addon.otelThreadCtxOrderedHashMapHeaderSize; ThreadContext = addon.threadContext; @@ -298,9 +276,5 @@ export function getProcessContextAttributes( return Object.freeze({ 'threadlocal.schema_version': SCHEMA_VERSION, 'threadlocal.attribute_key_map': Object.freeze(keys.slice()), - 'threadlocal.js_object_record_offset': JS_OBJECT_RECORD_OFFSET, - 'threadlocal.tagged_size': TAGGED_SIZE, - 'threadlocal.js_map_table_offset': JS_MAP_TABLE_OFFSET, - 'threadlocal.ordered_hash_map_header_size': ORDERED_HASH_MAP_HEADER_SIZE, }) as ProcessContextAttributes; } diff --git a/ts/test/test-otel-thread-ctx.ts b/ts/test/test-otel-thread-ctx.ts index 372a5fff..e7d0afeb 100644 --- a/ts/test/test-otel-thread-ctx.ts +++ b/ts/test/test-otel-thread-ctx.ts @@ -940,20 +940,9 @@ function captureBytes(opts: { const pca = getProcessContextAttributes(keys); strictAssert.equal(pca['threadlocal.schema_version'], 'nodejs_v1_dev'); strictAssert.deepEqual(pca['threadlocal.attribute_key_map'], keys); - strictAssert.equal(pca['threadlocal.js_object_record_offset'], 0x18); - strictAssert.equal(pca['threadlocal.tagged_size'], 8); - strictAssert.equal(pca['threadlocal.js_map_table_offset'], 0x18); - strictAssert.equal( - pca['threadlocal.ordered_hash_map_header_size'], - 0x10, - ); strictAssert.deepEqual(Object.keys(pca).sort(), [ 'threadlocal.attribute_key_map', - 'threadlocal.js_map_table_offset', - 'threadlocal.js_object_record_offset', - 'threadlocal.ordered_hash_map_header_size', 'threadlocal.schema_version', - 'threadlocal.tagged_size', ]); });