From 9c5c1459a812c7780b8bb3256c646ce3006dc3e6 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 7 Sep 2022 19:20:40 +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. PR-URL: https://github.com/nodejs/node/pull/44279 Fixes: https://github.com/nodejs/node-v8/issues/237 Reviewed-By: Chengzhong Wu Reviewed-By: James M Snell Reviewed-By: Benjamin Gruenbaum --- 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); +}