| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b229083 commit 1af5ad6
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,17 +23,18 @@ const { emitWarningSync } = require('internal/process/warning'); | |||
| 23 | 23 | const { | |
| 24 | 24 | initializeCallbacks, | |
| 25 | 25 | namespace: { | |
| 26 | - addSerializeCallback, | ||
| 27 | 26 | addDeserializeCallback, | |
| 28 | 27 | isBuildingSnapshot, | |
| 29 | 28 | }, | |
| 29 | + addAfterUserSerializeCallback, | ||
| 30 | 30 | } = require('internal/v8/startup_snapshot'); | |
| 31 | 31 | ||
| 32 | 32 | const { | |
| 33 | 33 | prepareMainThreadExecution, | |
| 34 | 34 | } = require('internal/process/pre_execution'); | |
| 35 | 35 | ||
| 36 | 36 | const path = require('path'); | |
| 37 | + const { getOptionValue } = require('internal/options'); | ||
| 37 | 38 | ||
| 38 | 39 | const supportedModules = new SafeSet(new SafeArrayIterator([ | |
| 39 | 40 | // '_http_agent', | |
@@ -140,22 +141,56 @@ function main() { | |||
| 140 | 141 | prepareMainThreadExecution(false, false); | |
| 141 | 142 | initializeCallbacks(); | |
| 142 | 143 | ||
| 143 | - let stackTraceLimitDesc; | ||
| 144 | - addDeserializeCallback(() => { | ||
| 145 | - if (stackTraceLimitDesc !== undefined) { | ||
| 146 | - ObjectDefineProperty(Error, 'stackTraceLimit', stackTraceLimitDesc); | ||
| 144 | + // In a context created for building snapshots, V8 does not install Error.stackTraceLimit and as | ||
| 145 | + // a result, if an error is created during the snapshot building process, error.stack would be | ||
| 146 | + // undefined. To prevent users from tripping over this, install Error.stackTraceLimit based on | ||
| 147 | + // --stack-trace-limit by ourselves (which defaults to 10). | ||
| 148 | + // See https://chromium-review.googlesource.com/c/v8/v8/+/3319481 | ||
| 149 | + const initialStackTraceLimitDesc = { | ||
| 150 | + value: getOptionValue('--stack-trace-limit'), | ||
| 151 | + configurable: true, | ||
| 152 | + writable: true, | ||
| 153 | + enumerable: true, | ||
| 154 | + __proto__: null, | ||
| 155 | + }; | ||
| 156 | + ObjectDefineProperty(Error, 'stackTraceLimit', initialStackTraceLimitDesc); | ||
| 157 | + | ||
| 158 | + let stackTraceLimitDescToRestore; | ||
| 159 | + // Error.stackTraceLimit needs to be removed during serialization, because when V8 deserializes | ||
| 160 | + // the snapshot, it expects Error.stackTraceLimit to be unset so that it can install it as a new property | ||
| 161 | + // using the value of --stack-trace-limit. | ||
| 162 | + addAfterUserSerializeCallback(() => { | ||
| 163 | + const desc = ObjectGetOwnPropertyDescriptor(Error, 'stackTraceLimit'); | ||
| 164 | + | ||
| 165 | + // If it's modified by users, emit a warning. | ||
| 166 | + if (desc && ( | ||
| 167 | + desc.value !== initialStackTraceLimitDesc.value || | ||
| 168 | + desc.configurable !== initialStackTraceLimitDesc.configurable || | ||
| 169 | + desc.writable !== initialStackTraceLimitDesc.writable || | ||
| 170 | + desc.enumerable !== initialStackTraceLimitDesc.enumerable | ||
| 171 | + )) { | ||
| 172 | + process._rawDebug('Error.stackTraceLimit has been modified by the snapshot builder script.'); | ||
| 173 | + // We want to use null-prototype objects to not rely on globally mutable | ||
| 174 | + // %Object.prototype%. | ||
| 175 | + if (desc.configurable) { | ||
| 176 | + stackTraceLimitDescToRestore = desc; | ||
| 177 | + ObjectSetPrototypeOf(stackTraceLimitDescToRestore, null); | ||
| 178 | + process._rawDebug('It will be preserved after snapshot deserialization and override ' + | ||
| 179 | + '--stack-trace-limit passed into the deserialized application.\n' + | ||
| 180 | + 'To allow --stack-trace-limit override in the deserialized application, ' + | ||
| 181 | + 'delete Error.stackTraceLimit.'); | ||
| 182 | + } else { | ||
| 183 | + process._rawDebug('It is not configurable and will crash the application upon deserialization.\n' + | ||
| 184 | + 'To fix the error, make Error.stackTraceLimit configurable.'); | ||
| 185 | + } | ||
| 147 | 186 | } | |
| 187 | + | ||
| 188 | + delete Error.stackTraceLimit; | ||
| 148 | 189 | }); | |
| 149 | - addSerializeCallback(() => { | ||
| 150 | - stackTraceLimitDesc = ObjectGetOwnPropertyDescriptor(Error, 'stackTraceLimit'); | ||
| 151 | 190 | ||
| 152 | - if (stackTraceLimitDesc !== undefined) { | ||
| 153 | - // We want to use null-prototype objects to not rely on globally mutable | ||
| 154 | - // %Object.prototype%. | ||
| 155 | - ObjectSetPrototypeOf(stackTraceLimitDesc, null); | ||
| 156 | - process._rawDebug('Deleting Error.stackTraceLimit from the snapshot. ' + | ||
| 157 | - 'It will be re-installed after deserialization'); | ||
| 158 | - delete Error.stackTraceLimit; | ||
| 191 | + addDeserializeCallback(() => { | ||
| 192 | + if (stackTraceLimitDescToRestore) { | ||
| 193 | + ObjectDefineProperty(Error, 'stackTraceLimit', stackTraceLimitDescToRestore); | ||
| 159 | 194 | } | |
| 160 | 195 | }); | |
| 161 | 196 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -58,11 +58,16 @@ function addDeserializeCallback(callback, data) { | |||
| 58 | 58 | } | |
| 59 | 59 | ||
| 60 | 60 | const serializeCallbacks = []; | |
| 61 | + const afterUserSerializeCallbacks = []; // Callbacks to run after user callbacks | ||
| 61 | 62 | function runSerializeCallbacks() { | |
| 62 | 63 | while (serializeCallbacks.length > 0) { | |
| 63 | 64 | const { 0: callback, 1: data } = serializeCallbacks.shift(); | |
| 64 | 65 | callback(data); | |
| 65 | 66 | } | |
| 67 | + while (afterUserSerializeCallbacks.length > 0) { | ||
| 68 | + const { 0: callback, 1: data } = afterUserSerializeCallbacks.shift(); | ||
| 69 | + callback(data); | ||
| 70 | + } | ||
| 66 | 71 | } | |
| 67 | 72 | ||
| 68 | 73 | function addSerializeCallback(callback, data) { | |
@@ -71,6 +76,10 @@ function addSerializeCallback(callback, data) { | |||
| 71 | 76 | serializeCallbacks.push([callback, data]); | |
| 72 | 77 | } | |
| 73 | 78 | ||
| 79 | + function addAfterUserSerializeCallback(callback, data) { | ||
| 80 | + afterUserSerializeCallbacks.push([callback, data]); | ||
| 81 | + } | ||
| 82 | + | ||
| 74 | 83 | function initializeCallbacks() { | |
| 75 | 84 | // Only run the serialize callbacks in snapshot building mode, otherwise | |
| 76 | 85 | // they throw. | |
@@ -117,4 +126,5 @@ module.exports = { | |||
| 117 | 126 | setDeserializeMainFunction, | |
| 118 | 127 | isBuildingSnapshot, | |
| 119 | 128 | }, | |
| 129 | + addAfterUserSerializeCallback, | ||
| 120 | 130 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,24 @@ | |||
| 1 | + | ||
| 2 | + const { | ||
| 3 | + setDeserializeMainFunction, | ||
| 4 | + } = require('v8').startupSnapshot; | ||
| 5 | + | ||
| 6 | + console.log(`During snapshot building, Error.stackTraceLimit =`, Error.stackTraceLimit); | ||
| 7 | + console.log(getError('During snapshot building', 30)); | ||
| 8 | + | ||
| 9 | + setDeserializeMainFunction(() => { | ||
| 10 | + console.log(`After snapshot deserialization, Error.stackTraceLimit =`, Error.stackTraceLimit); | ||
| 11 | + console.log(getError('After snapshot deserialization', 30)); | ||
| 12 | + }); | ||
| 13 | + | ||
| 14 | + function getError(message, depth) { | ||
| 15 | + let counter = 1; | ||
| 16 | + function recurse() { | ||
| 17 | + if (counter++ < depth) { | ||
| 18 | + return recurse(); | ||
| 19 | + } | ||
| 20 | + const error = new Error(message); | ||
| 21 | + return error.stack; | ||
| 22 | + } | ||
| 23 | + return recurse(); | ||
| 24 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,44 @@ | |||
| 1 | + | ||
| 2 | + const { | ||
| 3 | + addSerializeCallback, | ||
| 4 | + setDeserializeMainFunction, | ||
| 5 | + } = require('v8').startupSnapshot; | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + | ||
| 8 | + if (process.env.TEST_IN_SERIALIZER) { | ||
| 9 | + addSerializeCallback(checkMutate); | ||
| 10 | + } else { | ||
| 11 | + checkMutate(); | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + function checkMutate() { | ||
| 15 | + // Check that mutation to Error.stackTraceLimit is effective in the snapshot | ||
| 16 | + // builder script. | ||
| 17 | + assert.strictEqual(typeof Error.stackTraceLimit, 'number'); | ||
| 18 | + Error.stackTraceLimit = 0; | ||
| 19 | + assert.strictEqual(getError('', 30), 'Error'); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + setDeserializeMainFunction(() => { | ||
| 23 | + // Check that the mutation is preserved in the deserialized main function. | ||
| 24 | + assert.strictEqual(Error.stackTraceLimit, 0); | ||
| 25 | + assert.strictEqual(getError('', 30), 'Error'); | ||
| 26 | + | ||
| 27 | + // Check that it can still be mutated. | ||
| 28 | + Error.stackTraceLimit = 10; | ||
| 29 | + const error = getError('', 30); | ||
| 30 | + const matches = [...error.matchAll(/at recurse/g)]; | ||
| 31 | + assert.strictEqual(matches.length, 10); | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + function getError(message, depth) { | ||
| 35 | + let counter = 1; | ||
| 36 | + function recurse() { | ||
| 37 | + if (counter++ < depth) { | ||
| 38 | + return recurse(); | ||
| 39 | + } | ||
| 40 | + const error = new Error(message); | ||
| 41 | + return error.stack; | ||
| 42 | + } | ||
| 43 | + return recurse(); | ||
| 44 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,46 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests mutation to Error.stackTraceLimit in both the snapshot builder script | ||
| 4 | + // and the snapshot main script work. | ||
| 5 | + | ||
| 6 | + require('../common'); | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const tmpdir = require('../common/tmpdir'); | ||
| 9 | + const fixtures = require('../common/fixtures'); | ||
| 10 | + const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); | ||
| 11 | + | ||
| 12 | + const blobPath = tmpdir.resolve('snapshot.blob'); | ||
| 13 | + | ||
| 14 | + function test(additionalArguments = [], additionalEnv = {}) { | ||
| 15 | + tmpdir.refresh(); | ||
| 16 | + // Check the mutation works without --stack-trace-limit. | ||
| 17 | + spawnSyncAndAssert(process.execPath, [ | ||
| 18 | + ...additionalArguments, | ||
| 19 | + '--snapshot-blob', | ||
| 20 | + blobPath, | ||
| 21 | + '--build-snapshot', | ||
| 22 | + fixtures.path('snapshot', 'mutate-error-stack-trace-limit.js'), | ||
| 23 | + ], { | ||
| 24 | + cwd: tmpdir.path, | ||
| 25 | + env: { | ||
| 26 | + ...process.env, | ||
| 27 | + ...additionalEnv, | ||
| 28 | + } | ||
| 29 | + }, { | ||
| 30 | + stderr(output) { | ||
| 31 | + assert.match(output, /Error\.stackTraceLimit has been modified by the snapshot builder script/); | ||
| 32 | + assert.match(output, /It will be preserved after snapshot deserialization/); | ||
| 33 | + } | ||
| 34 | + }); | ||
| 35 | + spawnSyncAndExitWithoutError(process.execPath, [ | ||
| 36 | + '--snapshot-blob', | ||
| 37 | + blobPath, | ||
| 38 | + ], { | ||
| 39 | + cwd: tmpdir.path | ||
| 40 | + }); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + test(); | ||
| 44 | + test([], { TEST_IN_SERIALIZER: 1 }); | ||
| 45 | + test(['--stack-trace-limit=50']); | ||
| 46 | + test(['--stack-trace-limit=50'], { TEST_IN_SERIALIZER: 1 }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,46 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests Error.stackTraceLimit is fixed up for snapshot-building contexts, | ||
| 4 | + // and can be restored after deserialization. | ||
| 5 | + | ||
| 6 | + require('../common'); | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const tmpdir = require('../common/tmpdir'); | ||
| 9 | + const fixtures = require('../common/fixtures'); | ||
| 10 | + const { spawnSyncAndAssert } = require('../common/child_process'); | ||
| 11 | + | ||
| 12 | + tmpdir.refresh(); | ||
| 13 | + const blobPath = tmpdir.resolve('snapshot.blob'); | ||
| 14 | + { | ||
| 15 | + spawnSyncAndAssert(process.execPath, [ | ||
| 16 | + '--stack-trace-limit=50', | ||
| 17 | + '--snapshot-blob', | ||
| 18 | + blobPath, | ||
| 19 | + '--build-snapshot', | ||
| 20 | + fixtures.path('snapshot', 'error-stack.js'), | ||
| 21 | + ], { | ||
| 22 | + cwd: tmpdir.path | ||
| 23 | + }, { | ||
| 24 | + stdout(output) { | ||
| 25 | + assert.match(output, /During snapshot building, Error\.stackTraceLimit = 50/); | ||
| 26 | + const matches = [...output.matchAll(/at recurse/g)]; | ||
| 27 | + assert.strictEqual(matches.length, 30); | ||
| 28 | + } | ||
| 29 | + }); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + { | ||
| 33 | + spawnSyncAndAssert(process.execPath, [ | ||
| 34 | + '--stack-trace-limit=20', | ||
| 35 | + '--snapshot-blob', | ||
| 36 | + blobPath, | ||
| 37 | + ], { | ||
| 38 | + cwd: tmpdir.path | ||
| 39 | + }, { | ||
| 40 | + stdout(output) { | ||
| 41 | + assert.match(output, /After snapshot deserialization, Error\.stackTraceLimit = 20/); | ||
| 42 | + const matches = [...output.matchAll(/at recurse/g)]; | ||
| 43 | + assert.strictEqual(matches.length, 20); | ||
| 44 | + } | ||
| 45 | + }); | ||
| 46 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments