mirror of
https://github.com/nodejs/node.git
synced 2025-05-10 17:57:53 +00:00

Before this commit, when the user calls methods on a closed or errored fs event watcher, they could hit a crash since the FSEventWrap in C++ land may have already been destroyed with the internal pointer set to nullptr. This commit makes sure that the user cannot hit crashes like that, instead the methods calling on a closed watcher will be noops. Also explicitly documents that the watchers should not be used in `close` and `error` event handlers. PR-URL: https://github.com/nodejs/node/pull/20985 Fixes: https://github.com/nodejs/node/issues/20738 Fixes: https://github.com/nodejs/node/issues/20297 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Ron Korving <ron@ronkorving.nl> Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
105 lines
3.1 KiB
JavaScript
105 lines
3.1 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
|
|
// Tests if `filename` is provided to watcher on supported platforms
|
|
|
|
const fs = require('fs');
|
|
const assert = require('assert');
|
|
const { join } = require('path');
|
|
|
|
class WatchTestCase {
|
|
constructor(shouldInclude, dirName, fileName, field) {
|
|
this.dirName = dirName;
|
|
this.fileName = fileName;
|
|
this.field = field;
|
|
this.shouldSkip = !shouldInclude;
|
|
}
|
|
get dirPath() { return join(tmpdir.path, this.dirName); }
|
|
get filePath() { return join(this.dirPath, this.fileName); }
|
|
}
|
|
|
|
const cases = [
|
|
// Watch on a directory should callback with a filename on supported systems
|
|
new WatchTestCase(
|
|
common.isLinux || common.isOSX || common.isWindows || common.isAIX,
|
|
'watch1',
|
|
'foo',
|
|
'filePath'
|
|
),
|
|
// Watch on a file should callback with a filename on supported systems
|
|
new WatchTestCase(
|
|
common.isLinux || common.isOSX || common.isWindows,
|
|
'watch2',
|
|
'bar',
|
|
'dirPath'
|
|
)
|
|
];
|
|
|
|
const tmpdir = require('../common/tmpdir');
|
|
tmpdir.refresh();
|
|
|
|
for (const testCase of cases) {
|
|
if (testCase.shouldSkip) continue;
|
|
fs.mkdirSync(testCase.dirPath);
|
|
// Long content so it's actually flushed.
|
|
const content1 = Date.now() + testCase.fileName.toLowerCase().repeat(1e4);
|
|
fs.writeFileSync(testCase.filePath, content1);
|
|
|
|
let interval;
|
|
const pathToWatch = testCase[testCase.field];
|
|
const watcher = fs.watch(pathToWatch);
|
|
watcher.on('error', (err) => {
|
|
if (interval) {
|
|
clearInterval(interval);
|
|
interval = null;
|
|
}
|
|
assert.fail(err);
|
|
});
|
|
watcher.on('close', common.mustCall(() => {
|
|
watcher.close(); // Closing a closed watcher should be a noop
|
|
// Starting a closed watcher should be a noop
|
|
watcher.start();
|
|
}));
|
|
watcher.on('change', common.mustCall(function(eventType, argFilename) {
|
|
if (interval) {
|
|
clearInterval(interval);
|
|
interval = null;
|
|
}
|
|
if (common.isOSX)
|
|
assert.strictEqual(['rename', 'change'].includes(eventType), true);
|
|
else
|
|
assert.strictEqual(eventType, 'change');
|
|
assert.strictEqual(argFilename, testCase.fileName);
|
|
|
|
// Starting a started watcher should be a noop
|
|
watcher.start();
|
|
watcher.start(pathToWatch);
|
|
|
|
watcher.close();
|
|
|
|
// We document that watchers cannot be used anymore when it's closed,
|
|
// here we turn the methods into noops instead of throwing
|
|
watcher.close(); // Closing a closed watcher should be a noop
|
|
watcher.start(); // Starting a closed watcher should be a noop
|
|
}));
|
|
|
|
// Long content so it's actually flushed. toUpperCase so there's real change.
|
|
const content2 = Date.now() + testCase.fileName.toUpperCase().repeat(1e4);
|
|
interval = setInterval(() => {
|
|
fs.writeFileSync(testCase.filePath, '');
|
|
fs.writeFileSync(testCase.filePath, content2);
|
|
}, 100);
|
|
}
|
|
|
|
[false, 1, {}, [], null, undefined].forEach((input) => {
|
|
common.expectsError(
|
|
() => fs.watch(input, common.mustNotCall()),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
type: TypeError,
|
|
message: 'The "filename" argument must be one of type string, Buffer, ' +
|
|
`or URL. Received type ${typeof input}`
|
|
}
|
|
);
|
|
});
|