diff --git a/src/bootstrap-fork.ts b/src/bootstrap-fork.ts index b87e855ba85fe5..73dd8665d546dc 100644 --- a/src/bootstrap-fork.ts +++ b/src/bootstrap-fork.ts @@ -19,7 +19,7 @@ function pipeLoggingToParent(): void { * Prevent circular stringify and convert arguments to real array */ function safeToString(args: ArrayLike): string { - const seen: unknown[] = []; + const ancestors: unknown[] = []; const argsArray: unknown[] = []; // Massage some arguments with special treatment @@ -50,15 +50,20 @@ function pipeLoggingToParent(): void { } try { - const res = JSON.stringify(argsArray, function (key, value: unknown) { + const res = JSON.stringify(argsArray, function (this: unknown, key, value: unknown) { - // Objects get special treatment to prevent circles - if (isObject(value) || Array.isArray(value)) { - if (seen.indexOf(value) !== -1) { + // Track only current ancestors so shared references are serialized in full. + if (typeof value === 'object' && value !== null) { + // `this` is the object holding `key`, pop the subtrees that are already done + while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) { + ancestors.pop(); + } + + if (ancestors.indexOf(value) !== -1) { return '[Circular]'; } - seen.push(value); + ancestors.push(value); } return value; @@ -84,14 +89,6 @@ function pipeLoggingToParent(): void { } } - function isObject(obj: unknown): boolean { - return typeof obj === 'object' - && obj !== null - && !Array.isArray(obj) - && !(obj instanceof RegExp) - && !(obj instanceof Date); - } - function safeSendConsoleMessage(severity: 'log' | 'warn' | 'error', args: string): void { safeSend({ type: '__$console', severity, arguments: args }); } diff --git a/src/vs/base/test/node/processes/fixtures/fork_console.ts b/src/vs/base/test/node/processes/fixtures/fork_console.ts new file mode 100644 index 00000000000000..c979fc7e30c932 --- /dev/null +++ b/src/vs/base/test/node/processes/fixtures/fork_console.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const sharedValue = { value: 1 }; + +const circularValue: { name: string; self?: unknown } = { name: 'circular' }; +circularValue.self = circularValue; + +console.log(sharedValue, { a: sharedValue, b: sharedValue }, circularValue); diff --git a/src/vs/base/test/node/processes/processes.integrationTest.ts b/src/vs/base/test/node/processes/processes.integrationTest.ts index 3f30e931271d56..0c726f4744627b 100644 --- a/src/vs/base/test/node/processes/processes.integrationTest.ts +++ b/src/vs/base/test/node/processes/processes.integrationTest.ts @@ -59,6 +59,34 @@ suite('Processes', () => { }); }); + test('console forwarding - shared references are not reported as circular', function (done: (err?: unknown) => void) { + if (process.env['VSCODE_PID']) { + return done(); // this test fails when run from within VS Code + } + + const child = fork('vs/base/test/node/processes/fixtures/fork_console'); + + child.on('message', msgFromChild => { + const msg = msgFromChild as { type?: string; arguments?: string }; + if (msg.type !== '__$console') { + return; + } + + child.kill(); + + try { + assert.deepStrictEqual(JSON.parse(msg.arguments!), [ + { value: 1 }, + { a: { value: 1 }, b: { value: 1 } }, + { name: 'circular', self: '[Circular]' } + ]); + done(); + } catch (error) { + done(error); + } + }); + }); + (!platform.isWindows || process.env['VSCODE_PID'] ? test.skip : test)('buffered sending - lots of data (potential deadlock on win32)', function (done: () => void) { // test is only relevant for Windows and seems to crash randomly on some Linux builds const child = fork('vs/base/test/node/processes/fixtures/fork_large'); const sender = processes.createQueuedSender(child);