Fix LoadModuleCS parsing for module paths containing spaces (#1951)#1962
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes module-loading via --loadmodulecs / LoadModuleCS when module paths contain spaces by centralizing spec parsing in ModuleUtils.TryParseModuleSpec, and updates both runtime loading and option validation to use it. Adds targeted unit and end-to-end tests plus documentation for the supported syntax.
Changes:
- Add
ModuleUtils.TryParseModuleSpecto correctly split<module-path> [args...]while preserving spaces in paths (including quoted paths). - Use the shared parser in
GarnetServer.LoadModulesand inModuleFilePathValidationAttributeto keep load/validate behavior consistent. - Add parser unit tests + end-to-end tests loading a module from a space-containing path, and document the supported syntax.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| website/docs/extensions/module.md | Documents module loading syntax, including paths-with-spaces and quoting. |
| test/standalone/Garnet.test.scripting/RespModuleTests.cs | Adds end-to-end coverage for loading modules from paths containing spaces (with/without args). |
| test/standalone/Garnet.test.scripting/ModuleUtilsTests.cs | Adds unit tests for the new module-spec parser. |
| libs/server/Module/ModuleUtils.cs | Introduces shared module-spec parsing logic for --loadmodulecs-style strings. |
| libs/host/GarnetServer.cs | Switches module loading to use TryParseModuleSpec output (path + args). |
| libs/host/Configuration/OptionsValidators.cs | Switches LoadModuleCS path validation to use TryParseModuleSpec for correct handling of spaces. |
|
Ran an additional round of automated review (GPT-5.6 and Gemini 3.1 Pro). Addressed the substantive findings in dfcf0fd:
New tests: a filesystem-probing parser unit test, plus integration tests loading a module from a |
--loadmodulecs split each module spec on every space, so a module path containing spaces (e.g. "C:\Users\John Doe\Garnet Modules\MyModule.dll") was broken into a bogus path plus arguments and failed to load. The same bug existed in the module file-path option validator. Add a shared ModuleUtils.TryParseModuleSpec that delimits the module path from its arguments at the module assembly extension (.dll/.exe) or an explicit double-quoted path, preserving spaces within the path. Use it in both GarnetServer.LoadModules and ModuleFilePathValidationAttribute so the two paths stay consistent. Add unit tests for the parser and integration tests that load a module from a path containing spaces (with and without arguments), and document the loading syntax. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b4a95d2-b7cf-46d7-91a7-8e0df9168c97
- TryParseModuleSpec now returns false for malformed quoted specs (an unterminated quote, or an empty/whitespace-only quoted path) instead of falling through and returning the raw specification as the path. - ModuleFilePathValidationAttribute now reports a validation error for any specification that fails to parse, instead of silently ignoring it. - LoadModules logs an error for a malformed (non-empty) module spec. - Add parser tests for invalid quoted specs, and a config-validation test asserting a malformed --loadmodulecs spec is rejected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b4a95d2-b7cf-46d7-91a7-8e0df9168c97
- Parser: resolve the module path against the filesystem (the longest prefix that exists as a file or directory wins) before falling back to the assembly extension heuristic. This correctly delimits a path whose directory name contains a ".dll"/".exe" fragment, and a directory module given a ".dll" argument, instead of splitting at the first extension token (Gemini). - FileUtils.TryGetFiles: match assembly extensions case-insensitively so an uppercase .DLL/.EXE module path loads instead of yielding no assemblies (GPT). - LoadModules: guard against an empty loaded-assembly list before indexing it, logging a clear error instead of throwing at startup (GPT). - Tests: a filesystem-probing parser test, plus integration tests loading a module from a ".dll"-fragment directory name and from an uppercase .DLL path. - Docs: describe filesystem-based path resolution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b4a95d2-b7cf-46d7-91a7-8e0df9168c97
dfcf0fd to
3a333cc
Compare
Revert the case-insensitive extension matching in FileUtils.TryGetFiles (it is shared by REGISTERCS and, on case-sensitive filesystems, would change directory-scan semantics and could fail a load on an unrelated uppercase- extension file). The parser's extension heuristic is likewise made case-sensitive so .dll/.exe are treated uniformly. Uppercase-extension loading was never supported; the startup crash the case-insensitive change was paired with is already prevented by the empty-assembly guard in LoadModules, so behavior is unchanged from before this PR. Users on a case-sensitive filesystem should match the extension case in their paths. Remove the uppercase-extension load test and adjust parser tests accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b4a95d2-b7cf-46d7-91a7-8e0df9168c97
|
Follow-up on the earlier "case-insensitive assembly extensions" change (from the GPT review round): reverted in a399ea9.
Uppercase-extension loading was never supported (pre-PR The filesystem-probing path resolution (which handles real files per-OS) and the empty-assembly guard are retained. |
Replace the filesystem-probing / extension-heuristic path delimiting with a deterministic rule, matching how a command line handles paths with spaces: the first space-separated token is the module path and the rest are its arguments, unless the path is wrapped in double quotes (in which case the quoted text is the path and may contain spaces). This removes the "guess the path/argument boundary" behavior (and the filesystem probing in the parser), which was non-deterministic and awkward. A module path containing spaces must be quoted. Update the parser tests, integration tests (quote the space-containing paths), and the module loading documentation accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b4a95d2-b7cf-46d7-91a7-8e0df9168c97
|
Simplified the module-spec parser to a deterministic rule (commit 458589a), addressing the indeterminacy/exploitability concern. Instead of probing the filesystem / guessing the path-vs-argument boundary, parsing now works like a command line: the first space-separated token is the module path and the rest are arguments, unless the path is wrapped in double quotes (in which case the quoted text is the path and may contain spaces). A module path containing spaces must be quoted, e.g.: This removes the filesystem probing from the parser entirely (net −156 lines) and makes behavior fully deterministic and input-only. Retained: the empty-assembly guard in |
- LoadModuleCsInvalidSpecIsRejected now quotes the valid module path in the spec, so the test does not fail if the assembly location contains spaces. - Clarify in the module docs that the double quotes must be part of the module specification string Garnet parses (distinct from shell quoting, and thus typically escaped on the command line); add a JSON config example. Keeps the invariant that only explicitly quoted paths may contain spaces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b4a95d2-b7cf-46d7-91a7-8e0df9168c97
Problem
--loadmodulecs(and theLoadModuleCSconfiguration setting) failed to load modules whose path contains spaces. Each module spec was split on every space (moduleCS.Split(' ')), so a path such asC:\Users\John Doe\Garnet Modules\MyModule.dllwas broken into a bogus path plus spurious arguments and failed to load. The module file-path option validator had the same bug (filePathArg.Split(' ')[0]).Fixes #1951.
Fix
Added a shared parser
ModuleUtils.TryParseModuleSpec(spec, out modulePath, out moduleArgs)that delimits the module path from its arguments as follows:.dll/.exe), preserving spaces within the path; remaining tokens are arguments.Both
GarnetServer.LoadModulesandModuleFilePathValidationAttributenow use this single parser, so the load path and validation path stay consistent.This makes the exact reported command work with no extra escaping:
Backward compatibility is preserved:
path arg0 arg1(no spaces in path) still parses into a path plus arguments.Tests
ModuleUtilsTests— 14 parser cases: plain paths, paths with spaces, args, quoted paths,.exe, trimming, directories, and empty/whitespace input.RespModuleTests(RespModuleAdditionalTests) — 2 end-to-end tests that load a module from a path containing spaces via--loadmodulecs, with and without arguments.ImportExportConfigLocal,ImportExportRedisConfigLocal), anddotnet format --verify-no-changesis clean.Docs
Documented the module loading syntax (spaces-in-path support and quoting) in
website/docs/extensions/module.md.