Skip to content

Commit d0113ee

Browse files
authored
fix: throw ERR_DIR_CLOSED on use-after-close in Dir (#34910)
Fixes #34900
1 parent aa18dde commit d0113ee

2 files changed

Lines changed: 101 additions & 2 deletions

File tree

ext/node/polyfills/_fs/_fs_dir.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
direntFromDeno,
77
} from "ext:deno_node/internal/fs/utils.mjs";
88
const { default: assert } = core.loadExtScript("ext:deno_node/assert.ts");
9-
const { ERR_MISSING_ARGS } = core.loadExtScript(
9+
const { ERR_MISSING_ARGS, ERR_DIR_CLOSED } = core.loadExtScript(
1010
"ext:deno_node/internal/errors.ts",
1111
);
1212
const { TextDecoder } = core.loadExtScript("ext:deno_web/08_text_encoding.js");
@@ -28,6 +28,7 @@ export default class Dir {
2828
#dirPath: string | Uint8Array;
2929
#syncIterator!: Iterator<Deno.DirEntry, undefined> | null;
3030
#asyncIterator!: AsyncIterator<Deno.DirEntry, undefined> | null;
31+
#closed = false;
3132

3233
constructor(path: string | Uint8Array) {
3334
if (!path) {
@@ -46,6 +47,16 @@ export default class Dir {
4647
// deno-lint-ignore no-explicit-any
4748
read(callback?: (...args: any[]) => void): Promise<Dirent | null> {
4849
return new Promise((resolve, reject) => {
50+
if (this.#closed) {
51+
const err = new ERR_DIR_CLOSED();
52+
if (callback) {
53+
callback(err);
54+
resolve(null);
55+
} else {
56+
reject(err);
57+
}
58+
return;
59+
}
4960
if (!this.#asyncIterator) {
5061
this.#asyncIterator = Deno.readDir(this.path)[SymbolAsyncIterator]();
5162
}
@@ -78,6 +89,9 @@ export default class Dir {
7889
}
7990

8091
readSync(): Dirent | null {
92+
if (this.#closed) {
93+
throw new ERR_DIR_CLOSED();
94+
}
8195
if (!this.#syncIterator) {
8296
this.#syncIterator = Deno.readDirSync(this.path)![SymbolIterator]();
8397
}
@@ -98,6 +112,7 @@ export default class Dir {
98112
// deno-lint-ignore no-explicit-any
99113
close(callback?: (...args: any[]) => void): Promise<void> {
100114
return new Promise((resolve) => {
115+
this.#closed = true;
101116
if (callback) {
102117
callback(null);
103118
}
@@ -111,7 +126,7 @@ export default class Dir {
111126
* finished reading
112127
*/
113128
closeSync() {
114-
//No op
129+
this.#closed = true;
115130
}
116131

117132
[SymbolDispose]() {

tests/unit_node/_fs/_fs_dir_test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,87 @@ Deno.test(
203203
});
204204
},
205205
);
206+
207+
Deno.test({
208+
name: "Dir.readSync throws ERR_DIR_CLOSED after closeSync",
209+
fn() {
210+
const testDir: string = Deno.makeTempDirSync();
211+
try {
212+
const dir = new Dir(testDir);
213+
dir.closeSync();
214+
try {
215+
dir.readSync();
216+
fail("Expected ERR_DIR_CLOSED to be thrown");
217+
} catch (e) {
218+
// deno-lint-ignore no-explicit-any
219+
assertEquals((e as any).code, "ERR_DIR_CLOSED");
220+
}
221+
} finally {
222+
Deno.removeSync(testDir, { recursive: true });
223+
}
224+
},
225+
});
226+
227+
Deno.test({
228+
name: "Dir.read rejects with ERR_DIR_CLOSED after close",
229+
async fn() {
230+
const testDir: string = Deno.makeTempDirSync();
231+
try {
232+
const dir = new Dir(testDir);
233+
await dir.close();
234+
try {
235+
await dir.read();
236+
fail("Expected ERR_DIR_CLOSED to be thrown");
237+
} catch (e) {
238+
// deno-lint-ignore no-explicit-any
239+
assertEquals((e as any).code, "ERR_DIR_CLOSED");
240+
}
241+
} finally {
242+
Deno.removeSync(testDir, { recursive: true });
243+
}
244+
},
245+
});
246+
247+
Deno.test({
248+
name: "Dir.readSync throws ERR_DIR_CLOSED after async close",
249+
async fn() {
250+
const testDir: string = Deno.makeTempDirSync();
251+
try {
252+
const dir = new Dir(testDir);
253+
await dir.close();
254+
try {
255+
dir.readSync();
256+
fail("Expected ERR_DIR_CLOSED to be thrown");
257+
} catch (e) {
258+
// deno-lint-ignore no-explicit-any
259+
assertEquals((e as any).code, "ERR_DIR_CLOSED");
260+
}
261+
} finally {
262+
Deno.removeSync(testDir, { recursive: true });
263+
}
264+
},
265+
});
266+
267+
Deno.test({
268+
name: "Dir.read callback receives ERR_DIR_CLOSED after close",
269+
async fn() {
270+
const testDir: string = Deno.makeTempDirSync();
271+
try {
272+
const dir = new Dir(testDir);
273+
await dir.close();
274+
await new Promise<void>((resolve, reject) => {
275+
// deno-lint-ignore no-explicit-any
276+
dir.read((err: any) => {
277+
try {
278+
assertEquals(err.code, "ERR_DIR_CLOSED");
279+
resolve();
280+
} catch (e) {
281+
reject(e);
282+
}
283+
});
284+
});
285+
} finally {
286+
Deno.removeSync(testDir, { recursive: true });
287+
}
288+
},
289+
});

0 commit comments

Comments
 (0)