From acf2b8e159bccb0dee92bdf4f4b908fd5e362c18 Mon Sep 17 00:00:00 2001 From: belowzeroff Date: Wed, 23 Sep 2026 03:50:55 -0400 Subject: [PATCH] fix(system): validate system command arity --- docs/docs/language/functions.md | 2 +- docs/docs/namespaces/sys.md | 10 +++++----- docs/docs/reference/all-functions.md | 2 +- src/lang/syscmd.c | 2 ++ src/ops/system.c | 3 ++- test/rfl/system/syscmd_coverage.rfl | 11 +++++++---- test/rfl/system/system_branch_cov2.rfl | 1 + 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/docs/language/functions.md b/docs/docs/language/functions.md index 943e1b78..7580b8e6 100644 --- a/docs/docs/language/functions.md +++ b/docs/docs/language/functions.md @@ -516,7 +516,7 @@ spills exactly as it did before. | `.time.timer.del` | unary, restricted | Cancel a scheduled timer by id. Returns null. | `(.time.timer.del 0)` | | `.sys.args` | nullary | Application arguments as a typed dict (`user` subdict for post-`--` args) | `(.sys.args)` | | `env` | unary | List all global environment bindings | `(env 0)` | -| `.sys.build` | variadic | Build metadata: `version` + `build-date` | `(.sys.build)` | +| `.sys.build` | nullary | Build metadata: `version` + `build-date` | `(.sys.build)` | | `.sys.mem` | variadic | Memory allocator statistics (alloc/peak/slab) | `(.sys.mem)` | | `.sys.info` | variadic | System information (cores, page size, memory) | `(.sys.info)` | diff --git a/docs/docs/namespaces/sys.md b/docs/docs/namespaces/sys.md index 5e13d189..fb8c1de8 100644 --- a/docs/docs/namespaces/sys.md +++ b/docs/docs/namespaces/sys.md @@ -10,18 +10,18 @@ Process-level introspection (build, memory, host info) and command-style operati | Function | Arity | Flags | Description | |---|---|---|---| | [`.sys.args`](#sys-args) | variadic | — | Command-line arguments as a typed dict. | -| [`.sys.build`](#sys-build) | variadic | — | Version + build date as a dict. | +| [`.sys.build`](#sys-build) | nullary | — | Version + build date as a dict. | | [`.sys.info`](#sys-info) | variadic | — | Host and process facts: cores, page size, total memory, pid, hostname. | | [`.sys.mem`](#sys-mem) | variadic | — | Allocator statistics. | | [`.sys.prof`](#sys-prof) | variadic | — | Last profiled query's per-step statistics as a table. | | [`.sys.querylog`](#sys-querylog) | variadic | — | Ambient per-query statistics ring as a table. | | [`.sys.querylog.enable`](#sys-querylog-enable) | variadic | restricted | Toggle query-statistics logging. | | [`.sys.gc`](#sys-gc) | variadic | — | Run allocator maintenance and return `0`. | -| [`.sys.env`](#sys-env) | variadic | — | Count or list of globally bound names. | +| [`.sys.env`](#sys-env) | variadic (0–1) | — | Count or list of globally bound names. | | [`.sys.exec`](#sys-exec) | variadic | restricted | Run a shell command; return its exit code, optionally with stdout. | | [`.sys.cmd`](#sys-cmd) | unary | restricted | Dispatch a colon-command string. | | [`.sys.listen`](#sys-listen) | unary | restricted | Bind an IPC listener on a TCP port. | -| [`.sys.timeit`](#sys-timeit) | variadic | — | Toggle / set the per-expression profiler. | +| [`.sys.timeit`](#sys-timeit) | variadic (0–1) | — | Toggle / set the per-expression profiler. | ## `.sys.args` { #sys-args } @@ -244,7 +244,7 @@ GC, not a tracing object collector. Returns `0`. ## `.sys.env` { #sys-env } -Signature: `(.sys.env)`. From a script / IPC context returns the **count** of globally bound names (i64). In a REPL context the same dispatcher prints one line per binding (name + type label) and returns null — the variadic registration accommodates both forms. +Signature: `(.sys.env [compat-arg])`. From a script / IPC context returns the **count** of globally bound names (i64). In a REPL context the same dispatcher prints one line per binding (name + type label) and returns null. At most one optional compatibility argument is accepted; additional arguments return a `domain` error. ```lisp (.sys.env) @@ -330,7 +330,7 @@ Errors: `type` (port not an int / not parseable from string), `domain` (port out ## `.sys.timeit` { #sys-timeit } -Signature: `(.sys.timeit [flag])`. Toggles the per-expression profiler. Calling with no argument flips the current state; passing `0` disables, anything non-zero enables. Returns the new state as `i64` (0/1). +Signature: `(.sys.timeit [flag])`. Toggles the per-expression profiler. Calling with no argument flips the current state; passing `0` disables, anything non-zero enables. Returns the new state as `i64` (0/1). More than one argument returns a `domain` error. ```lisp (.sys.timeit 1) ;; enable profiling diff --git a/docs/docs/reference/all-functions.md b/docs/docs/reference/all-functions.md index eba0d980..2c3e552e 100644 --- a/docs/docs/reference/all-functions.md +++ b/docs/docs/reference/all-functions.md @@ -655,7 +655,7 @@ System interaction, metaprogramming, diagnostics, and runtime inspection. | `.time.now` | variadic | — | Monotonic time in milliseconds | `(.time.now)` | | `.time.timer.set` | variadic | restricted | Schedule callback every `ms`, `num` times (0 = forever); returns id | `(.time.timer.set 1000 0 (fn [t] (println t)))` | | `.time.timer.del` | unary | restricted | Cancel a scheduled timer by id; returns null | `(.time.timer.del 0)` | -| `.sys.build` | variadic | — | Build metadata dict with `version` + `build-date` | `(.sys.build)` | +| `.sys.build` | nullary | — | Build metadata dict with `version` + `build-date` | `(.sys.build)` | | `.sys.mem` | variadic | — | Memory allocator statistics (alloc / peak / slab hits) | `(.sys.mem)` | | `.sys.prof` | variadic | — | Last profiled query's per-step statistics as a table (opt-in via `:t`) | `(.sys.prof)` | | `.sys.querylog` | variadic | — | Ambient per-query statistics ring as a table (opt-in via `-Q` / `.sys.querylog.enable`) | `(.sys.querylog)` | diff --git a/src/lang/syscmd.c b/src/lang/syscmd.c index 9fd7b44c..97bb3006 100644 --- a/src/lang/syscmd.c +++ b/src/lang/syscmd.c @@ -424,9 +424,11 @@ ray_t* ray_sys_listen_fn(ray_t* x) { return invoke_by_name("listen", x); } * matches `.sys.gc`'s convention and avoids the arity error users * would otherwise hit calling `(.sys.env)` with no args. */ ray_t* ray_sys_timeit_fn(ray_t** args, int64_t n) { + if (n > 1) return ray_error("domain", ".sys.timeit accepts at most one argument"); return invoke_by_name("timeit", n > 0 ? args[0] : RAY_NULL_OBJ); } ray_t* ray_sys_env_fn(ray_t** args, int64_t n) { + if (n > 1) return ray_error("domain", ".sys.env accepts at most one argument"); (void)args; return invoke_by_name("env", n > 0 ? args[0] : RAY_NULL_OBJ); } diff --git a/src/ops/system.c b/src/ops/system.c index 17641564..aaf23b0b 100644 --- a/src/ops/system.c +++ b/src/ops/system.c @@ -1228,7 +1228,8 @@ ray_t* ray_env_fn(ray_t* x) { /* (.sys.build) -- return dict with internal build information */ ray_t* ray_internals_fn(ray_t** args, int64_t n) { - (void)args; (void)n; + (void)args; + if (n != 0) return ray_error("domain", ".sys.build takes no arguments"); ray_t* keys = ray_sym_vec_new(RAY_SYM_W64, 2); if (RAY_IS_ERR(keys)) return keys; ray_t* vals = ray_list_new(2); diff --git a/test/rfl/system/syscmd_coverage.rfl b/test/rfl/system/syscmd_coverage.rfl index a69bf0af..39e766eb 100644 --- a/test/rfl/system/syscmd_coverage.rfl +++ b/test/rfl/system/syscmd_coverage.rfl @@ -113,17 +113,20 @@ (.sys.timeit) -- 1 (.sys.timeit) -- 0 -;; ────────────── ray_sys_timeit_fn: variadic with extra args ignored ────── -;; The variadic adapter (line 353) only forwards the first arg. +;; ────────────── ray_sys_timeit_fn: optional arg + arity guard ────────── +;; The variadic adapter accepts zero or one arg, and rejects extras before +;; changing profiler state. (.sys.timeit 1) -- 1 (.sys.timeit 0) -- 0 +(.sys.timeit 1 0) !- domain ;; ────────────── h_env: count > 0 + variadic with arg branch ────────────── -;; ray_sys_env_fn takes optional arg via variadic adapter (line 356); -;; arg is ignored by h_env (line 164). Both call shapes work. +;; ray_sys_env_fn accepts an optional compatibility arg; extra args are +;; rejected before h_env runs. (> (.sys.env) 0) -- true (> (.sys.env 1) 0) -- true (> (.sys.env "x") 0) -- true +(.sys.env 1 2) !- domain ;; ────────────── h_listen: domain / type / io branches ────────────── ;; The test runtime (test/main.c rfl_setup) registers a poll instance — diff --git a/test/rfl/system/system_branch_cov2.rfl b/test/rfl/system/system_branch_cov2.rfl index 73a2d595..b20e8fb0 100644 --- a/test/rfl/system/system_branch_cov2.rfl +++ b/test/rfl/system/system_branch_cov2.rfl @@ -101,6 +101,7 @@ (type (.sys.build)) -- 'DICT (count (.sys.build)) -- 2 (type (at (.sys.build) 'version)) -- 'str +(.sys.build 1) !- domain ;; ══════════════════════════════════════════════════════════════════════ ;; ray_memstat_fn (.sys.mem) (lines 703-728)