From 0358bfbf9daa68f066465c2baaa4dba9bf057803 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 13:30:17 +0100 Subject: [PATCH 01/10] Runtime: add pos to file objects --- runtime/js/fs_fake.js | 8 ++++++++ runtime/js/fs_node.js | 4 ++++ runtime/js/io.js | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/runtime/js/fs_fake.js b/runtime/js/fs_fake.js index 7881b034fc..e10c68162a 100644 --- a/runtime/js/fs_fake.js +++ b/runtime/js/fs_fake.js @@ -461,6 +461,9 @@ class MlFakeFd_out extends MlFakeFile { seek(_len, _whence, raise_unix) { caml_raise_system_error(raise_unix, "ESPIPE", "lseek", "illegal seek"); } + pos() { + return -1; + } close() { this.log = undefined; @@ -544,6 +547,11 @@ class MlFakeFd { ); this.offset = offset; this.seeked = true; + return offset; + } + + pos() { + return this.offset; } close() { diff --git a/runtime/js/fs_node.js b/runtime/js/fs_node.js index f09a259ff2..917397a651 100644 --- a/runtime/js/fs_node.js +++ b/runtime/js/fs_node.js @@ -487,6 +487,10 @@ class MlNodeFd extends MlFile { return this.offset; } + pos() { + return this.offset; + } + stat(large) { try { var js_stats = this.fs.fstatSync(this.fd); diff --git a/runtime/js/io.js b/runtime/js/io.js index 962bb19366..972f1a6e9d 100644 --- a/runtime/js/io.js +++ b/runtime/js/io.js @@ -204,7 +204,7 @@ function caml_ml_open_descriptor_out(fd) { var buffered = file.flags.buffered !== undefined ? file.flags.buffered : 1; var channel = { file: file, - offset: file.offset, + offset: file.pos(), fd: fd, opened: true, out: true, @@ -230,7 +230,7 @@ function caml_ml_open_descriptor_in(fd) { var refill = null; var channel = { file: file, - offset: file.offset, + offset: file.pos(), fd: fd, opened: true, out: false, From a9cf72892a424b3c301ddce958cf8079e5832611 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 13:32:32 +0100 Subject: [PATCH 02/10] Runtime: fix typo in error message --- runtime/js/fs.js | 2 +- runtime/js/unix.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/js/fs.js b/runtime/js/fs.js index 71bd1c4119..29f0844748 100644 --- a/runtime/js/fs.js +++ b/runtime/js/fs.js @@ -278,7 +278,7 @@ function caml_sys_rename(o, n) { var n_root = resolve_fs_device(n); if (o_root.device !== n_root.device) caml_failwith("caml_sys_rename: cannot move file between two filesystem"); - if (!o_root.device.rename) caml_failwith("caml_sys_rename: no implemented"); + if (!o_root.device.rename) caml_failwith("caml_sys_rename: not implemented"); o_root.device.rename(o_root.rest, n_root.rest); } diff --git a/runtime/js/unix.js b/runtime/js/unix.js index 146042617b..e18bc083f2 100644 --- a/runtime/js/unix.js +++ b/runtime/js/unix.js @@ -331,7 +331,7 @@ function caml_unix_rename(o, n) { var n_root = resolve_fs_device(n); if (o_root.device !== n_root.device) caml_raise_system_error(/* raise Unix_error */ 1, "EXDEV", "rename"); - if (!o_root.device.rename) caml_failwith("caml_sys_rename: no implemented"); + if (!o_root.device.rename) caml_failwith("caml_sys_rename: not implemented"); o_root.device.rename(o_root.rest, n_root.rest, /* raise Unix_error */ true); } @@ -716,7 +716,7 @@ function caml_unix_close(fd) { //Alias: win_inchannel_of_filedescr //Requires: caml_unix_lookup_file, caml_ml_open_descriptor_in function caml_unix_inchannel_of_filedescr(fd) { - var file = caml_unix_lookup_file(fd, "out_channel_of_descr"); + var file = caml_unix_lookup_file(fd, "in_channel_of_descr"); file.check_stream_semantics("in_channel_of_descr"); return caml_ml_open_descriptor_in(fd); } From 67294ea51a538c0b124ffb73dc21ecb3002f155d Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 13:33:59 +0100 Subject: [PATCH 03/10] Runtime: small refactoring --- runtime/js/fs_node.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/js/fs_node.js b/runtime/js/fs_node.js index 917397a651..96a53e50c1 100644 --- a/runtime/js/fs_node.js +++ b/runtime/js/fs_node.js @@ -122,7 +122,7 @@ class MlNodeDevice { } access(name, f, raise_unix) { - var consts = require("node:fs").constants; + var consts = this.fs.constants; var res = 0; for (var key in f) { switch (key) { @@ -152,7 +152,7 @@ class MlNodeDevice { } open(name, f, perms, raise_unix) { - var consts = require("node:fs").constants; + var consts = this.fs.constants; var res = 0; for (var key in f) { switch (key) { From 079e6de4ddf28df9fd2775b5a33088f6469fac5f Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 13:36:38 +0100 Subject: [PATCH 04/10] Runtime: jsoo_is_win32 --- runtime/js/fs.js | 9 ++------- runtime/js/fs_node.js | 13 ++++++++----- runtime/js/sys.js | 3 ++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/runtime/js/fs.js b/runtime/js/fs.js index 29f0844748..c62e536db0 100644 --- a/runtime/js/fs.js +++ b/runtime/js/fs.js @@ -50,6 +50,7 @@ function MlFile() {} //Provides: path_is_absolute //Requires: fs_node_supported +//Requires: jsoo_is_win32 function make_path_is_absolute() { function posix(path) { if (path.charAt(0) === "/") return ["", path.slice(1)]; @@ -72,13 +73,7 @@ function make_path_is_absolute() { } return; } - if ( - fs_node_supported() && - globalThis.process && - globalThis.process.platform - ) { - return globalThis.process.platform === "win32" ? win32 : posix; - } else return posix; + return jsoo_is_win32 ? win32 : posix; } var path_is_absolute = make_path_is_absolute(); diff --git a/runtime/js/fs_node.js b/runtime/js/fs_node.js index 96a53e50c1..3239bd0318 100644 --- a/runtime/js/fs_node.js +++ b/runtime/js/fs_node.js @@ -17,6 +17,11 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +//Provides: jsoo_is_win32 +var jsoo_is_win32 = + globalThis.Deno?.build?.os === "windows" || + globalThis.process?.platform === "win32"; + //Provides: fs_node_supported function fs_node_supported() { return globalThis.process?.versions?.node !== undefined; @@ -30,6 +35,7 @@ function fs_node_supported() { //Provides: MlNodeDevice //Requires: MlNodeFd, caml_raise_sys_error, caml_string_of_jsstring //Requires: caml_raise_nodejs_error, fs_node_stats_from_js +//Requires: jsoo_is_win32 class MlNodeDevice { constructor(root) { this.fs = require("node:fs"); @@ -133,10 +139,7 @@ class MlNodeDevice { res |= consts.W_OK; break; case "x": - res |= - globalThis.process?.platform === "win32" - ? consts.R_OK - : consts.X_OK; + res |= jsoo_is_win32 ? consts.R_OK : consts.X_OK; break; case "f": res |= consts.F_OK; @@ -210,7 +213,7 @@ class MlNodeDevice { } rename(o, n, raise_unix) { - if (globalThis.process?.platform === "win32") { + if (jsoo_is_win32) { try { var target = this.nm(n); var source = this.nm(o); diff --git a/runtime/js/sys.js b/runtime/js/sys.js index e43c3974a0..35679638af 100644 --- a/runtime/js/sys.js +++ b/runtime/js/sys.js @@ -298,7 +298,8 @@ function caml_sys_const_backend_type() { } //Provides: os_type -var os_type = globalThis.process?.platform === "win32" ? "Win32" : "Unix"; +//Requires: jsoo_is_win32 +var os_type = jsoo_is_win32 ? "Win32" : "Unix"; //Provides: caml_sys_get_config const //Requires: caml_string_of_jsbytes, os_type From dbc601756f0ddb19f5e372306954c0be878e9f85 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 13:55:21 +0100 Subject: [PATCH 05/10] Runtime: rename stat helper --- runtime/js/fs_node.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/js/fs_node.js b/runtime/js/fs_node.js index 3239bd0318..9be4654b22 100644 --- a/runtime/js/fs_node.js +++ b/runtime/js/fs_node.js @@ -34,7 +34,7 @@ function fs_node_supported() { //Provides: MlNodeDevice //Requires: MlNodeFd, caml_raise_sys_error, caml_string_of_jsstring -//Requires: caml_raise_nodejs_error, fs_node_stats_from_js +//Requires: caml_raise_nodejs_error, ocaml_stats_from_node_stats //Requires: jsoo_is_win32 class MlNodeDevice { constructor(root) { @@ -260,7 +260,7 @@ class MlNodeDevice { stat(name, large, raise_unix) { try { var js_stats = this.fs.statSync(this.nm(name)); - return fs_node_stats_from_js(js_stats, large); + return ocaml_stats_from_node_stats(js_stats, large); } catch (err) { caml_raise_nodejs_error(err, raise_unix); } @@ -269,7 +269,7 @@ class MlNodeDevice { lstat(name, large, raise_unix) { try { var js_stats = this.fs.lstatSync(this.nm(name)); - return fs_node_stats_from_js(js_stats, large); + return ocaml_stats_from_node_stats(js_stats, large); } catch (err) { caml_raise_nodejs_error(err, raise_unix); } @@ -324,9 +324,9 @@ class MlNodeDevice { } } -//Provides: fs_node_stats_from_js +//Provides: ocaml_stats_from_node_stats //Requires: caml_int64_of_float -function fs_node_stats_from_js(js_stats, large) { +function ocaml_stats_from_node_stats(js_stats, large) { /* ===Unix.file_kind=== * type file_kind = * S_REG (** Regular file *) @@ -392,7 +392,7 @@ class MlNodeDevice {} //Provides: MlNodeFd //Requires: MlFile, caml_uint8_array_of_string, caml_uint8_array_of_bytes, caml_bytes_set, caml_raise_sys_error -//Requires: caml_raise_nodejs_error, caml_raise_system_error, fs_node_stats_from_js +//Requires: caml_raise_nodejs_error, caml_raise_system_error, ocaml_stats_from_node_stats class MlNodeFd extends MlFile { constructor(fd, flags) { super(); @@ -497,7 +497,7 @@ class MlNodeFd extends MlFile { stat(large) { try { var js_stats = this.fs.fstatSync(this.fd); - return fs_node_stats_from_js(js_stats, large); + return ocaml_stats_from_node_stats(js_stats, large); } catch (err) { caml_raise_nodejs_error(err, /* raise Unix_error */ 1); } From 7478f80910862d6a6e5824a8b007180cdec958b5 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 14:02:45 +0100 Subject: [PATCH 06/10] Runtime: refactor isatty --- runtime/js/fs_node.js | 5 +++++ runtime/js/unix.js | 11 ++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/runtime/js/fs_node.js b/runtime/js/fs_node.js index 9be4654b22..7c0a21d4ab 100644 --- a/runtime/js/fs_node.js +++ b/runtime/js/fs_node.js @@ -421,6 +421,11 @@ class MlNodeFd extends MlFile { } } + isatty() { + var tty = require("node:tty"); + return tty.isatty(this.fd) ? 1 : 0; + } + length() { try { return this.fs.fstatSync(this.fd).size; diff --git a/runtime/js/unix.js b/runtime/js/unix.js index e18bc083f2..a26991daa4 100644 --- a/runtime/js/unix.js +++ b/runtime/js/unix.js @@ -98,15 +98,12 @@ function caml_unix_filedescr_of_fd(x) { } //Provides: caml_unix_isatty -//Requires: fs_node_supported, caml_unix_lookup_file +//Requires: caml_unix_lookup_file //Alias: unix_isatty function caml_unix_isatty(fd) { - if (fs_node_supported()) { - var tty = require("node:tty"); - return tty.isatty(caml_unix_lookup_file(fd).fd) ? 1 : 0; - } else { - return 0; - } + var file = caml_unix_lookup_file(fd); + if (!file.isatty) return 0; + return file.isatty(); } //Provides: caml_unix_isatty From c7d5f1e13c4f41de909c4af8e47e751292f0dec5 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Thu, 27 Nov 2025 16:46:32 +0100 Subject: [PATCH 07/10] Runtime: fix missing return --- runtime/js/unix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/js/unix.js b/runtime/js/unix.js index a26991daa4..0a0581abdb 100644 --- a/runtime/js/unix.js +++ b/runtime/js/unix.js @@ -230,7 +230,7 @@ function caml_strerror(errno) { //If: browser function caml_strerror(errno) { const code = unix_error[errno]; - code || "Unknown error " + errno; + return code || "Unknown error " + errno; } //Provides: unix_error_message From 6bde8cdae97612b4f4a15f6f25030a6fde606c2b Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Fri, 28 Nov 2025 12:05:47 +0100 Subject: [PATCH 08/10] Runtime: fix bug fake filesystem --- compiler/tests-compiler/sys_fs.ml | 33 +++++++++++++++++++++++++++++++ runtime/js/fs_fake.js | 16 ++++++++------- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/compiler/tests-compiler/sys_fs.ml b/compiler/tests-compiler/sys_fs.ml index f21724d622..1275d055f2 100644 --- a/compiler/tests-compiler/sys_fs.ml +++ b/compiler/tests-compiler/sys_fs.ml @@ -275,3 +275,36 @@ f (); Sys.chdir "/static"; f () EXPECTED ERROR EXPECTED ERROR |}] + +let%expect_test "check fake filesystem with path containing special regex chars" = + compile_and_run + {| +let f () = + let touch path = + let oc = open_out path in + close_out oc + in + Sys.mkdir "test.dir" 0o777; + Sys.mkdir "test_dir" 0o777; + touch "test.dir/dot"; + touch "test_dir/underscore"; + print_endline "test.dir:"; + Array.iter print_endline (Sys.readdir "test.dir"); + print_endline "test_dir:"; + Array.iter print_endline (Sys.readdir "test_dir"); + print_endline "" +in +f (); Sys.chdir "/static"; f () + |}; + [%expect + {| + test.dir: + dot + test_dir: + underscore + + test.dir: + dot + test_dir: + underscore + |}] diff --git a/runtime/js/fs_fake.js b/runtime/js/fs_fake.js index e10c68162a..f4914ce864 100644 --- a/runtime/js/fs_fake.js +++ b/runtime/js/fs_fake.js @@ -149,7 +149,6 @@ class MlFakeDevice { rmdir(name, raise_unix) { var name_slash = name === "" ? "" : this.slash(name); - var r = new RegExp("^" + name_slash + "([^/]+)"); if (!this.exists(name)) caml_raise_system_error( raise_unix, @@ -167,7 +166,7 @@ class MlFakeDevice { this.nm(name), ); for (var n in this.content) { - if (n.match(r)) + if (n.startsWith(name_slash) && n !== name_slash) caml_raise_system_error( raise_unix, "ENOTEMPTY", @@ -187,14 +186,17 @@ class MlFakeDevice { if (!this.is_dir(name)) { caml_raise_sys_error(name + ": Not a directory"); } - var r = new RegExp("^" + name_slash + "([^/]+)"); var seen = {}; var a = []; for (var n in this.content) { - var m = n.match(r); - if (m && !seen[m[1]]) { - seen[m[1]] = true; - a.push(m[1]); + if (n.startsWith(name_slash) && n !== name_slash) { + var last = n.indexOf("/", name_slash.length); + if (last < 0) last = undefined; + var m = n.slice(name_slash.length, last); + if (m && !seen[m]) { + seen[m] = true; + a.push(m); + } } } return a; From 34d1ed5098bf843c034a2bcab3ac69ba95ab79d1 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Fri, 28 Nov 2025 12:22:06 +0100 Subject: [PATCH 09/10] Runtime: cleanup and fixes --- runtime/js/fs.js | 2 +- runtime/js/fs_fake.js | 10 ++++++---- runtime/js/fs_node.js | 3 ++- runtime/js/io.js | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/runtime/js/fs.js b/runtime/js/fs.js index c62e536db0..a097d18e0b 100644 --- a/runtime/js/fs.js +++ b/runtime/js/fs.js @@ -358,7 +358,7 @@ function caml_read_file_content(name) { var file = root.device.open(root.rest, { rdonly: 1 }); var len = file.length(); var buf = new Uint8Array(len); - file.read(buf, 0, len); + file.read(buf, 0, len, false); return caml_string_of_uint8_array(buf); } caml_raise_no_such_file(caml_jsstring_of_string(name)); diff --git a/runtime/js/fs_fake.js b/runtime/js/fs_fake.js index f4914ce864..f1e2255260 100644 --- a/runtime/js/fs_fake.js +++ b/runtime/js/fs_fake.js @@ -293,7 +293,7 @@ class MlFakeDevice { this.nm(name), ); file = this.content[name]; - if (f.truncate) file.truncate(); + if (f.truncate) file.truncate(0); } else if (f.create) { this.create_dir_if_needed(name); this.content[name] = new MlFakeFile(caml_create_bytes(0)); @@ -362,8 +362,9 @@ class MlFakeFile extends MlFile { truncate(len) { var old = this.data; + var old_len = caml_ml_bytes_length(old); this.data = caml_create_bytes(len | 0); - caml_blit_bytes(old, 0, this.data, 0, len); + caml_blit_bytes(old, 0, this.data, 0, Math.min(len, old_len)); } length() { @@ -393,12 +394,13 @@ class MlFakeFile extends MlFile { if (offset + len >= clen) { len = clen - offset; } - if (len) { + if (len > 0) { var data = caml_create_bytes(len | 0); caml_blit_bytes(this.data, offset, data, 0, len); buf.set(caml_uint8_array_of_bytes(data), pos); + return len; } - return len; + return 0; } } diff --git a/runtime/js/fs_node.js b/runtime/js/fs_node.js index 7c0a21d4ab..2c878a070e 100644 --- a/runtime/js/fs_node.js +++ b/runtime/js/fs_node.js @@ -403,12 +403,13 @@ class MlNodeFd extends MlFile { var stats = this.fs.fstatSync(fd); flags.noSeek = stats.isCharacterDevice() || stats.isFIFO() || stats.isSocket(); + this.offset = this.flags.append ? stats.size : 0; } catch (err) { // The fstat will fail on standard streams under Windows with node // 18 (and lower). See https://github.com/libuv/libuv/pull/3811. flags.noSeek = true; + this.offset = 0; } - this.offset = this.flags.append ? stats.size : 0; this.seeked = false; } diff --git a/runtime/js/io.js b/runtime/js/io.js index 972f1a6e9d..f3a22683f0 100644 --- a/runtime/js/io.js +++ b/runtime/js/io.js @@ -27,7 +27,7 @@ var caml_sys_fds = new Array(3); function caml_sys_close(fd) { var x = caml_sys_fds[fd]; if (x) { - x.file.close(); + x.file.close(false); delete caml_sys_fds[fd]; } return 0; @@ -360,6 +360,7 @@ function caml_refill(chan) { chan.buffer, chan.buffer_max, chan.buffer.length - chan.buffer_max, + false, ); chan.offset += nread; chan.buffer_max += nread; @@ -568,7 +569,7 @@ function caml_ml_flush(chanid) { ); } else { for (var pos = 0; pos < chan.buffer_curr; ) { - pos += chan.file.write(chan.buffer, pos, chan.buffer_curr - pos); + pos += chan.file.write(chan.buffer, pos, chan.buffer_curr - pos, false); } } chan.offset += chan.buffer_curr; From 74fe97b108466f4bb7608b82b0e9ba89d4c9da0d Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Mon, 1 Dec 2025 13:18:02 +0100 Subject: [PATCH 10/10] CHANGES --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c618bbea86..469ff0a8e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,10 +11,11 @@ ## Bug fixes * Compiler: fix purity of comparison functions (again) (#2092) * Compiler: fix inlining (#2107) -* Runtime/wasm: fix Unix.times (#2096) * Ppx: disable spurious warning for unused "self" in object literal (#2128) * Ppx: fix labelled arguments for methods (#2126) +* Runtime/wasm: fix Unix.times (#2096) * Runtime: runtime with target-env=browser should not rely on "require(..)" (#2129) +* Runtime: fix fake filesystem with path containing special regexp chars. (#2132) # 6.2.0 (2025-07-30) - Lille