Files
node/test/parallel/test-fs-callback-throw.js
Marco c9f1d2d319 fs: do not swallow exceptions thrown by completion callbacks
Since 93644d5126 the TryCatch guarding StringBytes::Encode in the
completion callbacks of mkdtemp, realpath.native, readlink, recursive
mkdir, recursive readdir and dir.read is still active when the JS
callback runs. Exceptions thrown by the callback, or by the nextTick
queue drained after it, are caught by it and never reported.

Leave the TryCatch before calling into JS.

Fixes: https://github.com/nodejs/node/issues/65667
Refs: https://github.com/nodejs/node/pull/57706
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66308
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
2026-10-29 08:56:23 +00:00

63 lines
1.8 KiB
JavaScript

'use strict';
// Refs: https://github.com/nodejs/node/issues/65667
// Exceptions thrown after an fs operation completes must reach
// 'uncaughtException' instead of being swallowed.
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
tmpdir.refresh();
const dir = tmpdir.path;
const file = path.join(dir, 'file');
const link = path.join(dir, 'link');
fs.writeFileSync(file, '');
// The callback throws.
const callbackCases = {
'mkdtemp': (cb) => fs.mkdtemp(path.join(dir, 'x-'), cb),
'realpath.native': (cb) => fs.realpath.native(dir, cb),
'mkdir recursive': (cb) => fs.mkdir(path.join(dir, 'a', 'b'), { recursive: true }, cb),
'readdir recursive': (cb) => fs.readdir(dir, { recursive: true }, cb),
};
if (common.canCreateSymLink()) {
fs.symlinkSync(file, link);
callbackCases.readlink = (cb) => fs.readlink(link, cb);
}
// A nextTick callback scheduled after the promise settles throws.
const promiseCases = {
'promises.mkdtemp': () => fs.promises.mkdtemp(path.join(dir, 'p-')),
'dir.read': async () => {
const d = await fs.promises.opendir(dir);
await d.read();
process.nextTick(() => d.closeSync());
},
};
const cases = [
...Object.entries(callbackCases).map(([name, run]) => [name, () => {
run(common.mustSucceed(() => { throw new Error(name); }));
}]),
...Object.entries(promiseCases).map(([name, run]) => [name, async () => {
await run();
process.nextTick(() => { throw new Error(name); });
}]),
];
let current;
process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err.message, current);
next();
}, cases.length));
function next() {
const entry = cases.shift();
if (entry === undefined) return;
current = entry[0];
entry[1]();
}
next();