mirror of https://github.com/nodejs/node.git
test_runner: add `__proto__` null
PR-URL: https://github.com/nodejs/node/pull/48663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
This commit is contained in:
parent
42b5711d0b
commit
ee391f3781
|
@ -111,6 +111,14 @@ module.exports = {
|
|||
},
|
||||
] },
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'lib/internal/test_runner/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'node-core/set-proto-to-null-in-object': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
// ESLint built-in rules
|
||||
|
|
|
@ -250,7 +250,7 @@ class TestCoverage {
|
|||
let dir;
|
||||
|
||||
try {
|
||||
mkdirSync(this.originalCoverageDirectory, { recursive: true });
|
||||
mkdirSync(this.originalCoverageDirectory, { __proto__: null, recursive: true });
|
||||
dir = opendirSync(this.coverageDirectory);
|
||||
|
||||
for (let entry; (entry = dir.readSync()) !== null;) {
|
||||
|
|
|
@ -116,6 +116,7 @@ function setup(root) {
|
|||
const globalOptions = parseCommandLine();
|
||||
|
||||
const hook = createHook({
|
||||
__proto__: null,
|
||||
init(asyncId, type, triggerAsyncId, resource) {
|
||||
if (resource instanceof Test) {
|
||||
testResources.set(asyncId, resource);
|
||||
|
@ -216,7 +217,7 @@ function runInParentContext(Factory) {
|
|||
|
||||
const test = (name, options, fn) => run(name, options, fn);
|
||||
ArrayPrototypeForEach(['skip', 'todo', 'only'], (keyword) => {
|
||||
test[keyword] = (name, options, fn) => run(name, options, fn, { [keyword]: true });
|
||||
test[keyword] = (name, options, fn) => run(name, options, fn, { __proto__: null, [keyword]: true });
|
||||
});
|
||||
return test;
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ class MockTracker {
|
|||
validateObject(options, 'options');
|
||||
const { times = Infinity } = options;
|
||||
validateTimes(times, 'options.times');
|
||||
const ctx = new MockFunctionContext(implementation, { original }, times);
|
||||
const ctx = new MockFunctionContext(implementation, { __proto__: null, original }, times);
|
||||
return this.#setupMock(ctx, original);
|
||||
}
|
||||
|
||||
|
@ -189,7 +189,7 @@ class MockTracker {
|
|||
);
|
||||
}
|
||||
|
||||
const restore = { descriptor, object: objectOrFunction, methodName };
|
||||
const restore = { __proto__: null, descriptor, object: objectOrFunction, methodName };
|
||||
const impl = implementation === kDefaultFunction ?
|
||||
original : implementation;
|
||||
const ctx = new MockFunctionContext(impl, restore, times);
|
||||
|
@ -238,6 +238,7 @@ class MockTracker {
|
|||
}
|
||||
|
||||
return this.method(object, methodName, implementation, {
|
||||
__proto__: null,
|
||||
...options,
|
||||
getter,
|
||||
});
|
||||
|
@ -265,6 +266,7 @@ class MockTracker {
|
|||
}
|
||||
|
||||
return this.method(object, methodName, implementation, {
|
||||
__proto__: null,
|
||||
...options,
|
||||
setter,
|
||||
});
|
||||
|
@ -297,6 +299,7 @@ class MockTracker {
|
|||
throw err;
|
||||
} finally {
|
||||
FunctionPrototypeCall(trackCall, ctx, {
|
||||
__proto__: null,
|
||||
arguments: argList,
|
||||
error,
|
||||
result,
|
||||
|
@ -321,6 +324,7 @@ class MockTracker {
|
|||
throw err;
|
||||
} finally {
|
||||
FunctionPrototypeCall(trackCall, ctx, {
|
||||
__proto__: null,
|
||||
arguments: argList,
|
||||
error,
|
||||
result,
|
||||
|
|
|
@ -45,7 +45,7 @@ function setPosition(node, pos) {
|
|||
}
|
||||
|
||||
function abortIt(signal) {
|
||||
return new AbortError(undefined, { cause: signal.reason });
|
||||
return new AbortError(undefined, { __proto__: null, cause: signal.reason });
|
||||
}
|
||||
|
||||
const SUPPORTED_TIMERS = ['setTimeout', 'setInterval'];
|
||||
|
@ -194,7 +194,9 @@ class MockTimers {
|
|||
|
||||
#toggleEnableTimers(activate) {
|
||||
const options = {
|
||||
__proto__: null,
|
||||
toFake: {
|
||||
__proto__: null,
|
||||
setTimeout: () => {
|
||||
this.#realSetTimeout = globalThis.setTimeout;
|
||||
this.#realClearTimeout = globalThis.clearTimeout;
|
||||
|
@ -233,6 +235,7 @@ class MockTimers {
|
|||
},
|
||||
},
|
||||
toReal: {
|
||||
__proto__: null,
|
||||
setTimeout: () => {
|
||||
globalThis.setTimeout = this.#realSetTimeout;
|
||||
globalThis.clearTimeout = this.#realClearTimeout;
|
||||
|
|
|
@ -42,7 +42,7 @@ class SpecReporter extends Transform {
|
|||
#failedTests = [];
|
||||
|
||||
constructor() {
|
||||
super({ writableObjectMode: true });
|
||||
super({ __proto__: null, writableObjectMode: true });
|
||||
}
|
||||
|
||||
#indent(nesting) {
|
||||
|
@ -127,7 +127,7 @@ class SpecReporter extends Transform {
|
|||
}
|
||||
}
|
||||
_transform({ type, data }, encoding, callback) {
|
||||
callback(null, this.#handleEvent({ type, data }));
|
||||
callback(null, this.#handleEvent({ __proto__: null, type, data }));
|
||||
}
|
||||
_flush(callback) {
|
||||
if (this.#failedTests.length === 0) {
|
||||
|
|
|
@ -18,7 +18,7 @@ const kDefaultIndent = ' '; // 4 spaces
|
|||
const kFrameStartRegExp = /^ {4}at /;
|
||||
const kLineBreakRegExp = /\n|\r\n/;
|
||||
const kDefaultTAPVersion = 13;
|
||||
const inspectOptions = { colors: false, breakLength: Infinity };
|
||||
const inspectOptions = { __proto__: null, colors: false, breakLength: Infinity };
|
||||
let testModule; // Lazy loaded due to circular dependency.
|
||||
|
||||
function lazyLoadTest() {
|
||||
|
|
|
@ -304,9 +304,9 @@ class FileTest extends Test {
|
|||
function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
|
||||
const watchMode = filesWatcher != null;
|
||||
const subtest = root.createSubtest(FileTest, path, async (t) => {
|
||||
const args = getRunArgs({ path, inspectPort, testNamePatterns });
|
||||
const args = getRunArgs({ __proto__: null, path, inspectPort, testNamePatterns });
|
||||
const stdio = ['pipe', 'pipe', 'pipe'];
|
||||
const env = { ...process.env, NODE_TEST_CONTEXT: 'child-v8' };
|
||||
const env = { __proto__: null, ...process.env, NODE_TEST_CONTEXT: 'child-v8' };
|
||||
if (watchMode) {
|
||||
stdio.push('ipc');
|
||||
env.WATCH_REPORT_DEPENDENCIES = '1';
|
||||
|
@ -315,7 +315,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
|
|||
env.FORCE_COLOR = '1';
|
||||
}
|
||||
|
||||
const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8', env, stdio });
|
||||
const child = spawn(process.execPath, args, { __proto__: null, signal: t.signal, encoding: 'utf8', env, stdio });
|
||||
if (watchMode) {
|
||||
filesWatcher.runningProcesses.set(path, child);
|
||||
filesWatcher.watcher.watchChildProcessModules(child, path);
|
||||
|
@ -332,7 +332,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
|
|||
subtest.parseMessage(data);
|
||||
});
|
||||
|
||||
const rl = createInterface({ input: child.stderr });
|
||||
const rl = createInterface({ __proto__: null, input: child.stderr });
|
||||
rl.on('line', (line) => {
|
||||
if (isInspectorMessage(line)) {
|
||||
process.stderr.write(line + '\n');
|
||||
|
@ -350,8 +350,8 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
|
|||
});
|
||||
|
||||
const { 0: { 0: code, 1: signal } } = await SafePromiseAll([
|
||||
once(child, 'exit', { signal: t.signal }),
|
||||
finished(child.stdout, { signal: t.signal }),
|
||||
once(child, 'exit', { __proto__: null, signal: t.signal }),
|
||||
finished(child.stdout, { __proto__: null, signal: t.signal }),
|
||||
]);
|
||||
|
||||
if (watchMode) {
|
||||
|
@ -384,7 +384,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
|
|||
function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
|
||||
const runningProcesses = new SafeMap();
|
||||
const runningSubtests = new SafeMap();
|
||||
const watcher = new FilesWatcher({ debounce: 200, mode: 'filter', signal });
|
||||
const watcher = new FilesWatcher({ __proto__: null, debounce: 200, mode: 'filter', signal });
|
||||
const filesWatcher = { __proto__: null, watcher, runningProcesses, runningSubtests };
|
||||
|
||||
watcher.on('changed', ({ owners }) => {
|
||||
|
@ -469,7 +469,7 @@ function run(options) {
|
|||
});
|
||||
}
|
||||
|
||||
const root = createTestTree({ concurrency, timeout, signal });
|
||||
const root = createTestTree({ __proto__: null, concurrency, timeout, signal });
|
||||
let testFiles = files ?? createTestFileList();
|
||||
|
||||
if (shard) {
|
||||
|
|
|
@ -79,7 +79,7 @@ function stopTest(timeout, signal) {
|
|||
if (timeout === kDefaultTimeout) {
|
||||
return once(signal, 'abort');
|
||||
}
|
||||
return PromisePrototypeThen(setTimeout(timeout, null, { ref: false, signal }), () => {
|
||||
return PromisePrototypeThen(setTimeout(timeout, null, { __proto__: null, ref: false, signal }), () => {
|
||||
throw new ERR_TEST_FAILURE(
|
||||
`test timed out after ${timeout}ms`,
|
||||
kTestTimeoutFailure,
|
||||
|
@ -506,7 +506,7 @@ class Test extends AsyncResource {
|
|||
|
||||
getRunArgs() {
|
||||
const ctx = new TestContext(this);
|
||||
return { ctx, args: [ctx] };
|
||||
return { __proto__: null, ctx, args: [ctx] };
|
||||
}
|
||||
|
||||
async runHook(hook, args) {
|
||||
|
@ -540,12 +540,12 @@ class Test extends AsyncResource {
|
|||
const { args, ctx } = this.getRunArgs();
|
||||
const after = async () => {
|
||||
if (this.hooks.after.length > 0) {
|
||||
await this.runHook('after', { args, ctx });
|
||||
await this.runHook('after', { __proto__: null, args, ctx });
|
||||
}
|
||||
};
|
||||
const afterEach = runOnce(async () => {
|
||||
if (this.parent?.hooks.afterEach.length > 0) {
|
||||
await this.parent.runHook('afterEach', { args, ctx });
|
||||
await this.parent.runHook('afterEach', { __proto__: null, args, ctx });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -554,7 +554,7 @@ class Test extends AsyncResource {
|
|||
await this.parent.runHook('before', this.parent.getRunArgs());
|
||||
}
|
||||
if (this.parent?.hooks.beforeEach.length > 0) {
|
||||
await this.parent.runHook('beforeEach', { args, ctx });
|
||||
await this.parent.runHook('beforeEach', { __proto__: null, args, ctx });
|
||||
}
|
||||
const stopPromise = stopTest(this.timeout, this.signal);
|
||||
const runArgs = ArrayPrototypeSlice(args);
|
||||
|
@ -811,7 +811,7 @@ class Suite extends Test {
|
|||
|
||||
getRunArgs() {
|
||||
const ctx = new SuiteContext(this);
|
||||
return { ctx, args: [ctx] };
|
||||
return { __proto__: null, ctx, args: [ctx] };
|
||||
}
|
||||
|
||||
async run() {
|
||||
|
|
|
@ -12,7 +12,7 @@ class TestsStream extends Readable {
|
|||
#canPush;
|
||||
|
||||
constructor() {
|
||||
super({ objectMode: true });
|
||||
super({ __proto__: null, objectMode: true });
|
||||
this.#buffer = [];
|
||||
this.#canPush = true;
|
||||
}
|
||||
|
@ -83,6 +83,8 @@ class TestsStream extends Readable {
|
|||
|
||||
[kEmitMessage](type, data) {
|
||||
this.emit(type, data);
|
||||
// Disabling as this going to the user-land
|
||||
// eslint-disable-next-line node-core/set-proto-to-null-in-object
|
||||
this.#tryPush({ type, data });
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ function createDeferredCallback() {
|
|||
resolve();
|
||||
};
|
||||
|
||||
return { promise, cb };
|
||||
return { __proto__: null, promise, cb };
|
||||
}
|
||||
|
||||
function isTestFailureError(err) {
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
messages: {
|
||||
error: 'Add `__proto__: null` to object',
|
||||
},
|
||||
fixable: 'code',
|
||||
},
|
||||
create: function(context) {
|
||||
return {
|
||||
ObjectExpression(node) {
|
||||
// Not adding __proto__ to module.exports as it will break a lot of libraries
|
||||
if (isModuleExportsObject(node) || isModifiedExports(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const properties = node.properties;
|
||||
let hasProto = false;
|
||||
|
||||
for (const property of properties) {
|
||||
|
||||
if (!property.key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property.key.type === 'Identifier' && property.key.name === '__proto__') {
|
||||
hasProto = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (property.key.type === 'Literal' && property.key.value === '__proto__') {
|
||||
hasProto = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProto) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (properties.length > 0) {
|
||||
// If the object has properties but no __proto__ property
|
||||
context.report({
|
||||
node,
|
||||
message: 'Every object must have __proto__: null',
|
||||
fix: function(fixer) {
|
||||
// Generate the fix suggestion to add __proto__: null
|
||||
const sourceCode = context.getSourceCode();
|
||||
const firstProperty = properties[0];
|
||||
const firstPropertyToken = sourceCode.getFirstToken(firstProperty);
|
||||
|
||||
|
||||
const isMultiLine = properties.length === 1 ?
|
||||
// If the object has only one property,
|
||||
// it's multiline if the property is not on the same line as the object parenthesis
|
||||
properties[0].loc.start.line !== node.loc.start.line :
|
||||
// If the object has more than one property,
|
||||
// it's multiline if the first and second properties are not on the same line
|
||||
properties[0].loc.start.line !== properties[1].loc.start.line;
|
||||
|
||||
const fixText = `__proto__: null,${isMultiLine ? '\n' : ' '}`;
|
||||
|
||||
// Insert the fix suggestion before the first property
|
||||
return fixer.insertTextBefore(firstPropertyToken, fixText);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (properties.length === 0) {
|
||||
// If the object is empty and missing __proto__
|
||||
context.report({
|
||||
node,
|
||||
message: 'Every empty object must have __proto__: null',
|
||||
fix: function(fixer) {
|
||||
// Generate the fix suggestion to create the object with __proto__: null
|
||||
const fixText = '{ __proto__: null }';
|
||||
|
||||
// Replace the empty object with the fix suggestion
|
||||
return fixer.replaceText(node, fixText);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// Helper function to check if the object is `module.exports`
|
||||
function isModuleExportsObject(node) {
|
||||
return (
|
||||
node.parent &&
|
||||
node.parent.type === 'AssignmentExpression' &&
|
||||
node.parent.left &&
|
||||
node.parent.left.type === 'MemberExpression' &&
|
||||
node.parent.left.object &&
|
||||
node.parent.left.object.name === 'module' &&
|
||||
node.parent.left.property &&
|
||||
node.parent.left.property.name === 'exports'
|
||||
);
|
||||
}
|
||||
|
||||
function isModifiedExports(node) {
|
||||
return (
|
||||
node.parent &&
|
||||
(isObjectAssignCall(node.parent) || isObjectDefinePropertiesCall(node.parent))
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to check if the node represents an ObjectAssign call
|
||||
function isObjectAssignCall(node) {
|
||||
return (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'ObjectAssign' &&
|
||||
node.arguments.length > 1 &&
|
||||
node.arguments.some((arg) =>
|
||||
arg.type === 'MemberExpression' &&
|
||||
arg.object.name === 'module' &&
|
||||
arg.property.name === 'exports',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to check if the node represents an ObjectDefineProperties call
|
||||
function isObjectDefinePropertiesCall(node) {
|
||||
return (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'ObjectDefineProperties' &&
|
||||
node.arguments.length > 1 &&
|
||||
node.arguments.some((arg) =>
|
||||
arg.type === 'MemberExpression' &&
|
||||
arg.object.name === 'module' &&
|
||||
arg.property.name === 'exports',
|
||||
)
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue