From 3142fcb6c37ad3db45fcdc1f6bcc491d1ee812f8 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 15 Aug 2022 22:44:33 +0800 Subject: [PATCH] bootstrap: clean up inspector console methods during serialization Some console methods are created by the V8 inspector after an inspector client is created, reset them to undefined during sereialization to avoid holding on to invalid references in the snapshot. V8 will take care of the re-initialization when another inspector client is created during deserialization. --- lib/internal/console/constructor.js | 27 +++++++++++ test/fixtures/snapshot/console.js | 9 ++++ test/parallel/test-snapshot-console.js | 62 ++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 test/fixtures/snapshot/console.js create mode 100644 test/parallel/test-snapshot-console.js diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index d8434f2311a375..9fb1d449a579e4 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -28,6 +28,7 @@ const { SafeArrayIterator, SafeMap, SafeWeakMap, + SafeSet, StringPrototypeIncludes, StringPrototypePadStart, StringPrototypeRepeat, @@ -687,6 +688,32 @@ Console.prototype.groupCollapsed = Console.prototype.group; function initializeGlobalConsole(globalConsole) { globalConsole[kBindStreamsLazy](process); globalConsole[kBindProperties](true, 'auto'); + + const { + addSerializeCallback, + isBuildingSnapshot, + } = require('v8').startupSnapshot; + + if (!internalBinding('config').hasInspector || !isBuildingSnapshot()) { + return; + } + const { console: consoleFromVM } = internalBinding('inspector'); + const nodeConsoleKeys = ObjectKeys(Console.prototype); + const vmConsoleKeys = ObjectKeys(consoleFromVM); + const originalKeys = new SafeSet(vmConsoleKeys.concat(nodeConsoleKeys)); + const inspectorConsoleKeys = new SafeSet(); + for (const key of ObjectKeys(globalConsole)) { + if (!originalKeys.has(key)) { + inspectorConsoleKeys.add(key); + } + } + // During deserialization these should be reinstalled to console by + // V8 when the inspector client is created. + addSerializeCallback(() => { + for (const key of inspectorConsoleKeys) { + globalConsole[key] = undefined; + } + }); } module.exports = { diff --git a/test/fixtures/snapshot/console.js b/test/fixtures/snapshot/console.js new file mode 100644 index 00000000000000..fc209e0c6cebca --- /dev/null +++ b/test/fixtures/snapshot/console.js @@ -0,0 +1,9 @@ +const { + setDeserializeMainFunction, +} = require('v8').startupSnapshot; + +console.log(JSON.stringify(Object.keys(console), null, 2)); + +setDeserializeMainFunction(() => { + console.log(JSON.stringify(Object.keys(console), null, 2)); +}); diff --git a/test/parallel/test-snapshot-console.js b/test/parallel/test-snapshot-console.js new file mode 100644 index 00000000000000..781b088ab25315 --- /dev/null +++ b/test/parallel/test-snapshot-console.js @@ -0,0 +1,62 @@ +'use strict'; + +// TODO(joyeecheung): remove the flag when it is turned on by default in V8. +// Flags: --experimental-async-stack-tagging-api +// This tests the console works in the deserialized snapshot. + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); +const path = require('path'); +const fs = require('fs'); + +tmpdir.refresh(); +const blobPath = path.join(tmpdir.path, 'snapshot.blob'); +const entry = fixtures.path('snapshot', 'console.js'); + +{ + const child = spawnSync(process.execPath, [ + '--experimental-async-stack-tagging-api', + '--snapshot-blob', + blobPath, + '--build-snapshot', + entry, + ], { + cwd: tmpdir.path + }); + const stdout = child.stdout.toString(); + if (child.status !== 0) { + console.log(stdout); + console.log(child.stderr.toString()); + assert.strictEqual(child.status, 0); + } + assert.deepStrictEqual(Object.keys(console), JSON.parse(stdout)); + const stats = fs.statSync(path.join(tmpdir.path, 'snapshot.blob')); + assert(stats.isFile()); +} + +{ + const child = spawnSync(process.execPath, [ + '--experimental-async-stack-tagging-api', + '--snapshot-blob', + blobPath, + ], { + cwd: tmpdir.path, + env: { + ...process.env, + } + }); + + const stdout = child.stdout.toString(); + if (child.status !== 0) { + console.log(stdout); + console.log(child.stderr.toString()); + assert.strictEqual(child.status, 0); + } + assert.deepStrictEqual(Object.keys(console), JSON.parse(stdout)); + assert.strictEqual(child.status, 0); +}