From 9c8ab5eabdc3f470e89608e074e5dd1407af03e0 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 6 Sep 2023 14:55:17 +0200 Subject: [PATCH] bootstrap: make snapshot reproducible This patch uses the new V8 API to {de}serialize context slots for snapshot in order to make the snapshot reproducible. Also added a test for the reproducibility of snapshots. --- src/api/environment.cc | 8 ++- src/node_snapshotable.cc | 56 ++++++++++++++++++++- src/node_snapshotable.h | 7 +++ test/parallel/test-snapshot-reproducible.js | 53 +++++++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-snapshot-reproducible.js diff --git a/src/api/environment.cc b/src/api/environment.cc index cdc2f7aaa8efd8..d55cfb1f860e5e 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -458,7 +458,13 @@ Environment* CreateEnvironment( if (use_snapshot) { context = Context::FromSnapshot(isolate, SnapshotData::kNodeMainContextIndex, - {DeserializeNodeInternalFields, env}) + v8::DeserializeInternalFieldsCallback( + DeserializeNodeInternalFields, env), + nullptr, + MaybeLocal(), + nullptr, + v8::DeserializeContextDataCallback( + DeserializeNodeContextData, env)) .ToLocalChecked(); CHECK(!context.IsEmpty()); diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 991826951e1c4d..8f9ac671190de0 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1155,8 +1155,11 @@ ExitCode SnapshotBuilder::CreateSnapshot(SnapshotData* out, CHECK_EQ(index, SnapshotData::kNodeVMContextIndex); index = creator->AddContext(base_context); CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex); - index = creator->AddContext(main_context, - {SerializeNodeContextInternalFields, env}); + index = creator->AddContext( + main_context, + v8::SerializeInternalFieldsCallback(SerializeNodeContextInternalFields, + env), + v8::SerializeContextDataCallback(SerializeNodeContextData, env)); CHECK_EQ(index, SnapshotData::kNodeMainContextIndex); } @@ -1255,6 +1258,17 @@ std::string SnapshotableObject::GetTypeName() const { } } +void DeserializeNodeContextData(Local holder, + int index, + StartupData payload, + void* callback_data) { + DCHECK(index == ContextEmbedderIndex::kEnvironment || + index == ContextEmbedderIndex::kRealm || + index == ContextEmbedderIndex::kContextTag); + // This is a no-op for now. We will reset all the pointers in + // Environment::AssignToContext() via the realm constructor. +} + void DeserializeNodeInternalFields(Local holder, int index, StartupData payload, @@ -1320,6 +1334,44 @@ void DeserializeNodeInternalFields(Local holder, } } +StartupData SerializeNodeContextData(Local holder, + int index, + void* callback_data) { + DCHECK(index == ContextEmbedderIndex::kEnvironment || + index == ContextEmbedderIndex::kContextifyContext || + index == ContextEmbedderIndex::kRealm || + index == ContextEmbedderIndex::kContextTag); + void* data = holder->GetAlignedPointerFromEmbedderData(index); + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serialize context data, index=%d, holder=%p, ptr=%p\n", + static_cast(index), + *holder, + data); + // Serialization of contextify context is not yet supported. + if (index == ContextEmbedderIndex::kContextifyContext) { + DCHECK_NULL(data); + return {nullptr, 0}; + } + + // We need to use use new[] because V8 calls delete[] on the returned data. + int size = sizeof(ContextEmbedderIndex); + char* result = new char[size]; + ContextEmbedderIndex* index_data = + reinterpret_cast(result); + *index_data = static_cast(index); + + // For now we just reset all of them in Environment::AssignToContext() + switch (index) { + case ContextEmbedderIndex::kEnvironment: + case ContextEmbedderIndex::kContextifyContext: + case ContextEmbedderIndex::kRealm: + case ContextEmbedderIndex::kContextTag: + return StartupData{result, size}; + default: + UNREACHABLE(); + } +} + StartupData SerializeNodeContextInternalFields(Local holder, int index, void* callback_data) { diff --git a/src/node_snapshotable.h b/src/node_snapshotable.h index 5e281b8155c810..600e56c481be90 100644 --- a/src/node_snapshotable.h +++ b/src/node_snapshotable.h @@ -126,10 +126,17 @@ class SnapshotableObject : public BaseObject { v8::StartupData SerializeNodeContextInternalFields(v8::Local holder, int index, void* env); +v8::StartupData SerializeNodeContextData(v8::Local holder, + int index, + void* env); void DeserializeNodeInternalFields(v8::Local holder, int index, v8::StartupData payload, void* env); +void DeserializeNodeContextData(v8::Local holder, + int index, + v8::StartupData payload, + void* env); void SerializeSnapshotableObjects(Realm* realm, v8::SnapshotCreator* creator, RealmSerializeInfo* info); diff --git a/test/parallel/test-snapshot-reproducible.js b/test/parallel/test-snapshot-reproducible.js new file mode 100644 index 00000000000000..4984c64871b3a3 --- /dev/null +++ b/test/parallel/test-snapshot-reproducible.js @@ -0,0 +1,53 @@ +'use strict'; + +require('../common'); +const { spawnSyncAndExitWithoutError } = require('../common/child_process'); +const tmpdir = require('../common/tmpdir'); +const fs = require('fs'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); + +function generateSnapshot() { + tmpdir.refresh(); + + spawnSyncAndExitWithoutError( + process.execPath, + [ + '--random_seed=42', + '--predictable', + '--build-snapshot', + 'node:generate_default_snapshot', + ], + { + cwd: tmpdir.path + } + ); + const blobPath = tmpdir.resolve('snapshot.blob'); + return fs.readFileSync(blobPath); +} + +const buf1 = generateSnapshot(); +const buf2 = generateSnapshot(); +const diff = []; +let offset = 0; +const step = 16; +do { + const length = Math.min(buf1.length - offset, step); + const slice1 = buf1.slice(offset, offset + length).toString('hex'); + const slice2 = buf2.slice(offset, offset + length).toString('hex'); + if (slice1 != slice2) { + diff.push({offset, slice1, slice2}); + } + offset += length; +} while (offset < buf1.length); + +assert.strictEqual(offset, buf1.length); +if (offset < buf2.length) { + const length = Math.min(buf2.length - offset, step); + const slice2 = buf2.slice(offset, offset + length).toString('hex'); + diff.push({offset, slice1: '', slice2}); + offset += length; +} while (offset < buf2.length); + +assert.deepStrictEqual(diff, [], 'Built-in snapshot should not change in different builds.'); +assert.strictEqual(buf1.length, buf2.length);