Conversation
📝 WalkthroughWalkthroughAdds a Rust FFI core crate and multiple cdylib WASM plugin crates with tests and fixtures, a multi-system Nix flake and CI workflow, workspace Cargo updates, editor/task and .gitignore entries, and per-system devShells/checks to build and run plugins via builtins.wasm. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as "User / Nix expression"
participant Nix as "Nix evaluator"
participant Loader as "builtins.wasm (loader)"
participant Plugin as "WASM module (plugin)"
participant FFI as "nix-wasm-rust FFI"
Note over User,Nix: Nix expression invokes builtins.wasm to call a plugin
User->>Nix: evaluate expression (calls builtins.wasm)
Nix->>Loader: request instantiate/load `<plugin>.wasm`
Loader->>Plugin: invoke exported function (pass opaque Value)
Plugin->>FFI: call FFI helpers (get_*/make_*, call, warn)
FFI-->>Plugin: return primitives / handles
Plugin->>Plugin: perform plugin logic (compute/parse/render/convert)
Plugin->>FFI: construct result via make_* APIs
FFI-->>Plugin: return result handle
Plugin-->>Loader: return Value to loader
Loader-->>Nix: deliver result into evaluation
Nix-->>User: evaluation returns result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
9ab433c to
d2cd6cc
Compare
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-priv/c1279376ce2b948c9835a1072944f29f47e6f556?narHash=sha256-%2B1E1dQsjkH6J/H5jZdChvciyHVrURb%2BsPPbQy7JLvTU%3D' (2025-09-19)
→ 'github:DeterminateSystems/nix-priv/a8a675040f8beda9cae3b03a4864bdba1d14d753?narHash=sha256-LxWPE%2BNkDBdEhx9fJA2X9vUWEgqbJvdZbuYKXJqajfY%3D' (2025-09-30)
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-priv/a8a6750' (2025-09-30)
→ 'github:DeterminateSystems/nix-priv/d6d322b' (2025-12-20)
• Updated input 'nix/nixpkgs':
'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.808723%2Brev-b1b3291469652d5a2edb0becc4ef0246fff97a7c/0198daf7-011a-7703-95d7-57146e794342/source.tar.gz' (2025-08-23)
→ 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.811874%2Brev-daf6dc47aa4b44791372d6139ab7b25269184d55/019a3494-3498-707e-9086-1fb81badc7fe/source.tar.gz' (2025-10-27)
• Updated input 'nixpkgs':
'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.809873%2Brev-9a094440e02a699be5c57453a092a8baf569bdad/019948d8-e1ce-71d3-a6ab-b8e7de3004e2/source.tar.gz' (2025-09-14)
→ follows 'nix/nixpkgs'
Flake lock file updates:
• Updated input 'flake-schemas':
'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz' (2024-07-26)
→ 'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.2.0/019a4a84-544d-7c59-b26d-e334e320c932/source.tar.gz' (2025-10-27)
• Updated input 'nix':
'github:DeterminateSystems/nix-priv/d6d322b' (2025-12-20)
→ 'github:DeterminateSystems/nix-src/d6d322b' (2025-12-20)
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (8)
flake.nix (1)
7-7: Verify the custom Nix fork dependency.The flake depends on a custom Nix fork with a
wasmbranch, suggesting thebuiltins.wasmfunctionality used in tests is not yet in upstream Nix. Ensure this is documented in the README so users understand they need this specific Nix version.Do you want me to help create documentation explaining this dependency?
nix-wasm-plugin-fib/src/lib.rs (1)
7-13: Exponential time complexity in recursive Fibonacci.The naive recursive implementation has O(2^n) complexity. While this works for the test case (n=33), larger inputs will be extremely slow or may cause stack overflow. For a demo/example plugin this is likely acceptable, but consider noting this limitation or using an iterative approach if real-world usage is expected.
🔎 Optional iterative alternative
- fn fib2(n: i64) -> i64 { - if n < 2 { - 1 - } else { - fib2(n - 1) + fib2(n - 2) - } - } + fn fib2(n: i64) -> i64 { + if n < 2 { + return 1; + } + let (mut a, mut b) = (1i64, 1i64); + for _ in 2..=n { + (a, b) = (b, a + b); + } + b + }nix-wasm-plugin-ini/src/lib.rs (1)
8-8: Consider graceful error handling for malformed INI input.Using
expect()will cause a panic if the INI parsing fails, which may abort the WASM module. Since this parses user-provided data (not system initialization), consider returning an error value or propagating the error more gracefully to the caller.Based on the retrieved learnings, unwraps are acceptable during Nix initialization for catastrophic failures, but this is user input parsing where malformed data is a recoverable scenario.
nix-wasm-plugin-yaml/src/lib.rs (4)
4-32: Consider defensive handling forYaml::Realconversion.Line 6:
as_f64().expect()should always succeed forYaml::Real, but the underlying yaml-rust2 implementation returnsNoneif the string can't be parsed as f64. Consider handling this edge case gracefully rather than panicking.The panic-based error handling for non-string keys (line 22) and unimplemented types (line 30) is acceptable for a plugin context, but be aware these will abort the WASM execution on malformed input.
34-47: Missing type validation on input argument.The function assumes
argis a string and directly callsget_string()without verifyingarg.get_type() == Type::String. If called with a non-string value, this could lead to undefined behavior depending on the host's FFI implementation.🔎 Proposed fix
#[no_mangle] pub extern "C" fn fromYAML(arg: Value) -> Value { + assert!(matches!(arg.get_type(), Type::String), "fromYAML expects a string argument"); let s = arg.get_string(); - let yaml = YamlLoader::load_from_str(&s).unwrap(); + let yaml = YamlLoader::load_from_str(&s).expect("invalid YAML input"); let mut docs = vec![];
49-75: Float serialization may lose precision; error message could be clearer.
Line 52: Using
format!("{}", v.get_float())forYaml::Realmay not preserve the original precision or format. Consider using{:?}or a more precise formatter if round-trip fidelity matters.Lines 70-73: The panic message shows the type as a raw integer (
v.get_type() as u64), which isn't user-friendly. Consider using Debug formatting ifTypeimplements it.🔎 Proposed improvement
- Type::Float => Yaml::Real(format!("{}", v.get_float())), + Type::Float => Yaml::Real(v.get_float().to_string()), ... - _ => panic!( - "Nix type {} cannot be serialized to YAML", - v.get_type() as u64 - ), + t => panic!( + "Nix type {:?} cannot be serialized to YAML", + t + ),
77-89: Missing type validation on input argument.Similar to
fromYAML, this function assumesargis a list without validation. Add a type check for robustness.🔎 Proposed fix
#[no_mangle] pub extern "C" fn toYAML(arg: Value) -> Value { + assert!(matches!(arg.get_type(), Type::List), "toYAML expects a list argument"); let mut out_str = String::new(); for v in arg.get_list() { let yaml = to_yaml(v); let mut emitter = YamlEmitter::new(&mut out_str); - emitter.dump(&yaml).unwrap(); + emitter.dump(&yaml).expect("failed to emit YAML"); out_str.push('\n'); }nix-wasm-rust/src/lib.rs (1)
12-30: Address FIXME: externref consideration.The FIXME at line 12 notes a potential improvement using WebAssembly's
externreftype. This would provide better type safety and garbage collection integration for Value references. Current approach usingu32handles works but requires manual lifecycle management on the host side.Would you like me to open an issue to track the externref migration?
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockflake.lockis excluded by!**/*.lock
📒 Files selected for processing (27)
.github/workflows/ci.yml.gitignore.vscode/tasks.jsonCargo.tomlflake.nixnix-wasm-plugin-fib/Cargo.tomlnix-wasm-plugin-fib/src/lib.rsnix-wasm-plugin-fib/tests/fib.expnix-wasm-plugin-fib/tests/fib.nixnix-wasm-plugin-ini/Cargo.tomlnix-wasm-plugin-ini/src/lib.rsnix-wasm-plugin-ini/tests/test1.expnix-wasm-plugin-ini/tests/test1.ininix-wasm-plugin-ini/tests/test1.nixnix-wasm-plugin-mandelbrot/Cargo.tomlnix-wasm-plugin-mandelbrot/src/lib.rsnix-wasm-plugin-yaml/Cargo.tomlnix-wasm-plugin-yaml/src/lib.rsnix-wasm-plugin-yaml/tests/fromYAML.expnix-wasm-plugin-yaml/tests/fromYAML.nixnix-wasm-plugin-yaml/tests/test1.yamlnix-wasm-plugin-yaml/tests/toYAML.expnix-wasm-plugin-yaml/tests/toYAML.nixnix-wasm-plugin-yaml/tests/toYAML2.expnix-wasm-plugin-yaml/tests/toYAML2.nixnix-wasm-rust/Cargo.tomlnix-wasm-rust/src/lib.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
📚 Learning: 2025-10-21T17:29:29.023Z
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: flake.nix:175-177
Timestamp: 2025-10-21T17:29:29.023Z
Learning: In Nix flake.nix files using Rust with bindgen, when `rustPlatform.bindgenHook` is included in `nativeBuildInputs`, it automatically sets up `LIBCLANG_PATH` and other bindgen-related environment variables. An explicit `LIBCLANG_PATH` setting in the package derivation is not required, even if it appears in the devShell.
Applied to files:
nix-wasm-plugin-ini/Cargo.tomlflake.nixnix-wasm-rust/Cargo.toml
📚 Learning: 2025-10-21T19:23:14.234Z
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
Applied to files:
flake.nixnix-wasm-rust/Cargo.toml
🧬 Code graph analysis (3)
nix-wasm-plugin-ini/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (4)
make_string(68-73)make_string(70-70)make_attrset(143-148)make_attrset(145-145)
nix-wasm-plugin-fib/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (4)
warn(3-10)warn(5-5)make_int(40-45)make_int(42-42)
nix-wasm-plugin-yaml/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (18)
make_float(54-59)make_float(56-56)make_int(40-45)make_int(42-42)make_string(68-73)make_string(70-70)make_bool(94-99)make_bool(96-96)make_list(115-120)make_list(117-117)make_attrset(143-148)make_attrset(145-145)make_null(108-113)make_null(110-110)get_float(61-66)get_float(63-63)get_type(33-38)get_type(35-35)
🪛 actionlint (1.7.9)
.github/workflows/ci.yml
19-19: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci
🔇 Additional comments (33)
.gitignore (1)
1-5: Well-structured .gitignore for Nix/Rust/WASM project.The patterns appropriately cover the key artifacts for this project type: WebAssembly binaries, Nix build outputs, Rust compiler artifacts, editor backups, and test outputs. All patterns are syntactically correct and serve clear purposes aligned with the project's technology stack.
Cargo.toml (2)
1-9: LGTM!The workspace configuration correctly includes all plugin crates and uses resolver 2, which is the current standard for Rust workspaces.
11-13: LGTM!The release profile optimizations are well-suited for WebAssembly targets. LTO and size optimization (
opt-level = 's') will produce smaller WASM binaries, which is important for plugin distribution and loading performance..vscode/tasks.json (1)
1-25: LGTM!The VSCode task configuration correctly targets WASM builds with appropriate settings. The
wasm32-unknown-unknowntarget and release builds align well with the project's WebAssembly plugin focus..github/workflows/ci.yml (1)
21-29: LGTM!The Nix setup with FlakeHub cache and
nix flake check --all-systemsprovides comprehensive multi-system verification for the WASM plugins.flake.nix (2)
23-57: LGTM!The build derivation is well-structured with comprehensive testing. The checkPhase runs Nix evaluation tests against expected outputs, and the installPhase applies additional WASM optimization with
wasm-opt -O3. The inclusion of the custom Nix CLI from the wasm branch ensures test compatibility.
60-73: LGTM!The development shell and checks are properly configured. The devShell includes essential Rust development tools and WASM utilities, while checks ensure the build succeeds across all supported systems.
nix-wasm-plugin-fib/tests/fib.nix (1)
1-1: LGTM!The test correctly invokes the fibonacci WASM plugin with input 33, demonstrating the
builtins.wasmintegration.nix-wasm-plugin-fib/tests/fib.exp (1)
1-1: LGTM!The expected value is correct for fibonacci(33).
nix-wasm-plugin-yaml/tests/fromYAML.nix (1)
1-1: LGTM!The test correctly loads YAML data from a file and parses it via the WASM plugin, demonstrating the YAML parsing functionality.
nix-wasm-plugin-yaml/tests/toYAML2.nix (1)
1-1: LGTM!The test exercises YAML serialization with diverse data types including integers, null values, strings, and nested attribute sets, providing good coverage of the toYAML functionality.
nix-wasm-rust/Cargo.toml (1)
1-4: LGTM!The minimal manifest is appropriate for a core FFI library with no external dependencies.
nix-wasm-plugin-yaml/tests/toYAML2.exp (1)
1-1: LGTM!Test expectation format is valid.
nix-wasm-plugin-ini/tests/test1.nix (1)
1-1: LGTM!The Nix expression correctly loads the WASM plugin and invokes the fromINI function with test data.
nix-wasm-plugin-fib/Cargo.toml (1)
1-10: LGTM!The configuration is correct for a WASM plugin with appropriate cdylib type and path dependency.
nix-wasm-plugin-yaml/tests/test1.yaml (1)
1-22: LGTM!The multi-document YAML file is well-formed with proper syntax, diverse data types, and correct document separators.
nix-wasm-plugin-ini/tests/test1.ini (1)
1-15: LGTM!The INI test data is valid and intentionally includes duplicate
RequiresMountsForkeys (lines 3-5) to test the plugin's handling of multi-value directives common in systemd unit files.nix-wasm-plugin-ini/tests/test1.exp (1)
1-1: No action required—behavior is expected.The rust-ini library (version 0.21) uses a HashMap-based structure for storing INI properties, which means duplicate keys are handled by keeping only the last value. This is standard INI parser behavior. The test correctly validates that when three
RequiresMountsForentries are provided, the final output contains only the last value (/nix/var/nix/db). This is intentional and not a bug.Likely an incorrect or invalid review comment.
nix-wasm-plugin-ini/Cargo.toml (1)
11-11: No action required. The rust-ini version 0.21 is current—the latest release is 0.21.3 (published 2025-08-30)—and no security vulnerabilities are known for this crate.Likely an incorrect or invalid review comment.
nix-wasm-plugin-mandelbrot/Cargo.toml (1)
1-11: LGTM!The manifest is correctly structured for a WASM plugin crate. The
cdylibcrate type is appropriate for producing a dynamic library compatible with the WASM target, and the dependencies align with the implementation insrc/lib.rs.nix-wasm-plugin-yaml/tests/toYAML.exp (1)
1-1: LGTM!Test expectation file containing the expected multi-document YAML output for the
toYAMLround-trip test.nix-wasm-plugin-yaml/tests/toYAML.nix (1)
1-3: LGTM!The test correctly exercises the YAML round-trip by parsing
test1.yamlwithfromYAMLand then serializing it back withtoYAML.nix-wasm-plugin-fib/src/lib.rs (1)
1-16: LGTM overall - clean FFI plugin structure.The function correctly uses the nix-wasm-rust FFI conventions with
#[no_mangle]andextern "C". The implementation demonstrates the Value abstraction for input/output exchange with the host.nix-wasm-plugin-yaml/tests/fromYAML.exp (1)
1-1: LGTM!Test expectation file containing the expected JSON representation of the parsed YAML multi-document input.
nix-wasm-plugin-yaml/Cargo.toml (1)
1-11: LGTM!Good choice using
yaml-rust2, which is the actively maintained fork of the originalyaml-rustcrate. The manifest structure correctly follows the established plugin pattern.nix-wasm-plugin-ini/src/lib.rs (1)
1-24: LGTM - clean implementation of INI-to-attrset conversion.The function correctly iterates sections and properties, building a nested attribute set structure. The handling of
Nonesection names as empty strings is appropriate for representing global (unnamed) INI sections.nix-wasm-plugin-mandelbrot/src/lib.rs (2)
59-67: Verify the mirroring logic produces the expected output.The mirroring skips one line (
.skip(1)) before reversing, which excludes the center line from duplication. This appears intentional for symmetry, but ensure the final visualization meets expectations—particularly that the aspect ratio and symmetry look correct.
1-92: LGTM - impressive Mandelbrot visualization plugin.The implementation correctly uses smooth coloring for anti-aliasing effects, proper complex number iteration, and ANSI escape sequences for terminal rendering. The use of
num::Complex64is appropriate for the computation.nix-wasm-plugin-yaml/src/lib.rs (1)
1-2: LGTM!Imports are appropriate for the YAML conversion functionality.
nix-wasm-rust/src/lib.rs (4)
3-10: LGTM!The
warnfunction correctly passes the string as a pointer-length pair to the FFI boundary.
32-66: LGTM!The type query and primitive constructors/accessors follow correct FFI patterns.
87-113: LGTM!These accessors and constructors are correctly implemented.
143-148: Verify tuple slice layout compatibility with FFI host.The
make_attrsetfunction passes a pointer to&[(&str, Value)]. The tuple(&str, Value)is not#[repr(C)], so its memory layout is not guaranteed. The FFI host must correctly interpret Rust's tuple layout for(&str, Value), which consists of(ptr, len, ValueId).If the host is also Rust-generated or explicitly handles this layout, this works. Otherwise, consider a
#[repr(C)]struct for the attribute entry.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
.github/workflows/ci.yml (1)
18-19: Update to actions/checkout@v4.The v3 runner is deprecated and may not work on newer GitHub Actions infrastructure.
🔎 Proposed fix
- uses: actions/checkout@v3 + uses: actions/checkout@v4
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/ci.yml
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: flake.nix:175-177
Timestamp: 2025-10-21T17:29:29.023Z
Learning: In Nix flake.nix files using Rust with bindgen, when `rustPlatform.bindgenHook` is included in `nativeBuildInputs`, it automatically sets up `LIBCLANG_PATH` and other bindgen-related environment variables. An explicit `LIBCLANG_PATH` setting in the package derivation is not required, even if it appears in the devShell.
🪛 actionlint (1.7.9)
.github/workflows/ci.yml
19-19: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
21-21: step must run script with "run" section or run action with "uses" section
(syntax-check)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/d6d322b' (2025-12-20)
→ 'github:DeterminateSystems/nix-src/e7bfa9b' (2025-12-25)
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
.github/workflows/ci.yml (1)
21-22: Fix syntax error in Install Nix step.Line 22 has an extra leading dash, creating two malformed steps instead of one valid step. This will cause the workflow to fail validation.
🔎 Proposed fix
- name: Install Nix - - uses: DeterminateSystems/determinate-nix-action@main + uses: DeterminateSystems/determinate-nix-action@mainnix-wasm-rust/src/lib.rs (2)
122-134: Use mutable pointer forcopy_listdestination.The buffer
resis written to by the FFI function but is declared immutable and passed viaas_ptr(). This should usemutandas_mut_ptr()for correctness.🔎 Proposed fix
pub fn get_list(&self) -> Vec<Value> { let len = self.get_list_length(); - let res: Vec<Value> = vec![Value(0); len]; + let mut res: Vec<Value> = vec![Value(0); len]; extern "C" { fn copy_list(value: ValueId, ptr: u32, len: usize); } unsafe { if len > 0 { - copy_list(self.0, res.as_ptr() as u32, len); + copy_list(self.0, res.as_mut_ptr() as u32, len); } } res }
150-175: Use mutable pointers forcopy_attrsetandcopy_attrnamedestinations.Both
attrs(line 157) andbuf(line 165) are written to by FFI functions but are declared immutable. This causes undefined behavior under Rust's aliasing model.🔎 Proposed fix
pub fn get_attrset(&self) -> BTreeMap<String, Value> { extern "C" { fn get_attrset_length(value: ValueId) -> usize; fn copy_attrset(value: ValueId, ptr: u32, len: usize); fn copy_attrname(value: ValueId, attr_idx: usize, ptr: u32, len: usize); } let len = unsafe { get_attrset_length(self.0) }; - let attrs: Vec<(ValueId, usize)> = vec![(0, 0); len]; + let mut attrs: Vec<(ValueId, usize)> = vec![(0, 0); len]; if len > 0 { unsafe { - copy_attrset(self.0, attrs.as_ptr() as u32, len); + copy_attrset(self.0, attrs.as_mut_ptr() as u32, len); } } let mut res = BTreeMap::new(); for (attr_idx, (value, attr_len)) in attrs.iter().enumerate() { - let buf: Vec<u8> = vec![0; *attr_len]; + let mut buf: Vec<u8> = vec![0; *attr_len]; unsafe { - copy_attrname(self.0, attr_idx, buf.as_ptr() as u32, *attr_len); + copy_attrname(self.0, attr_idx, buf.as_mut_ptr() as u32, *attr_len); } res.insert( String::from_utf8(buf).expect("Nix attribute name should be UTF-8."), Value(*value), ); } res }
🧹 Nitpick comments (2)
nix-wasm-plugin-mandelbrot/src/lib.rs (1)
13-16: Consider validating width to prevent overflow from negative values.If the
widthattribute is negative,v.get_int() as usizewill wrap to a very large value, potentially causing memory exhaustion or panic when allocating the output string.🔎 Proposed fix
let width = args .get("width") - .map(|v| v.get_int() as usize) + .map(|v| v.get_int().max(1) as usize) .unwrap_or(120);nix-wasm-rust/src/lib.rs (1)
12-17: Consider addingCopyderive toValue.Since
Valuewraps au32(which isCopy), derivingCopywould make the API more ergonomic and avoid unnecessary clones in plugin code.🔎 Proposed change
#[repr(transparent)] -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Value(ValueId);
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/ci.ymlnix-wasm-plugin-mandelbrot/src/lib.rsnix-wasm-rust/src/lib.rs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: flake.nix:175-177
Timestamp: 2025-10-21T17:29:29.023Z
Learning: In Nix flake.nix files using Rust with bindgen, when `rustPlatform.bindgenHook` is included in `nativeBuildInputs`, it automatically sets up `LIBCLANG_PATH` and other bindgen-related environment variables. An explicit `LIBCLANG_PATH` setting in the package derivation is not required, even if it appears in the devShell.
📚 Learning: 2025-11-05T19:24:31.622Z
Learnt from: cole-h
Repo: DeterminateSystems/determinate-nixd PR: 427
File: src/config/mod.rs:75-77
Timestamp: 2025-11-05T19:24:31.622Z
Learning: In `src/config/mod.rs` and `src/command/builder/mod.rs`, the `cpu_count` field uses `isize` (not an unsigned integer type) because it must match the Swift FFI boundary where `swift_rs::Int` is used in the `run_builder_vm` function signature in `src/command/builder/macos.rs`.
Applied to files:
nix-wasm-rust/src/lib.rs
🧬 Code graph analysis (1)
nix-wasm-plugin-mandelbrot/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (2)
make_string(68-73)make_string(70-70)
🪛 actionlint (1.7.9)
.github/workflows/ci.yml
21-21: step must run script with "run" section or run action with "uses" section
(syntax-check)
🔇 Additional comments (8)
.github/workflows/ci.yml (1)
1-19: LGTM on the workflow structure.The workflow triggers, permissions, checkout action (now v4), cache setup, and build/test steps are correctly configured. The
nix flake check --all-systemscommand will validate all packages and checks defined in the flake.Also applies to: 24-29
nix-wasm-plugin-mandelbrot/src/lib.rs (3)
74-84: LGTM!The function now correctly uses the
ITERATIONSconstant, and the color gradient logic is well-implemented.
86-92: LGTM!The helper functions are correct. The
to_rgbfunction works safely becauseiter_to_colorclamps color values to [0.0, 1.0].
22-57: LGTM on the Mandelbrot iteration and smooth coloring logic.The smooth coloring algorithm using the continuous potential is correctly implemented, and the ANSI color output with interpolation produces good visual results.
nix-wasm-rust/src/lib.rs (4)
3-10: LGTM!The
warnfunction correctly passes the string slice to the FFI boundary using pointer and length.
19-30: LGTM!The
Typeenum is correctly marked#[repr(C)]for stable ABI, and explicit discriminant values starting at 1 suggest0may be reserved for an error/unknown state on the host side.
75-85: LGTM!The mutability issue from the previous review has been addressed. The buffer is now correctly declared as
mutandas_mut_ptr()is used for the FFI call.
32-73: LGTM on the getter and constructor methods.The FFI patterns for
get_type,make_int,get_int,make_float,get_float,make_string,get_string_length,make_bool,get_bool,make_null,make_list,get_list_length, andmake_attrsetare correctly implemented. Read-only operations appropriately use*constpointers.Also applies to: 87-120, 136-148
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
flake.nix (1)
10-17: LGTM on helper structure; x86_64-darwin concern already noted.The
forAllSystemshelper andcargoTomlparsing are correctly implemented. The missingx86_64-darwininsupportedSystemswas already flagged in a previous review.
🧹 Nitpick comments (3)
flake.nix (3)
32-48: Consider addingrunHookcalls to custom phases.Custom
buildPhase,checkPhase, andinstallPhaseimplementations should callrunHookto ensure standard phase hooks are executed. This allows other tools and overlays to inject pre/post logic.🔎 Proposed fix
- buildPhase = "cargo build --release"; + buildPhase = '' + runHook preBuild + cargo build --release + runHook postBuild + ''; checkPhase = '' + runHook preCheck for i in nix-wasm-plugin-*/tests/*.nix; do echo "running test $i..." base="$(dirname $i)/$(basename $i .nix)" nix eval --store dummy:// --offline --json --show-trace -I plugins=target/wasm32-unknown-unknown/release --impure --file "$i" > "$base.out" cmp "$base.exp" "$base.out" done + runHook postCheck ''; installPhase = '' + runHook preInstall mkdir -p $out for i in target/wasm32-unknown-unknown/release/*.wasm; do wasm-opt -O3 -o "$out/$(basename "$i")" "$i" done + runHook postInstall '';
62-71: Unusedreckeyword.The
recon line 62 is unnecessary sincedefaultdoesn't reference other attributes within the same attrset.🔎 Proposed fix
- devShells = forAllSystems ({ pkgs, system }: rec { - default = with pkgs; self.packages.${system}.default.overrideAttrs (attrs: { + devShells = forAllSystems ({ pkgs, system }: { + default = with pkgs; self.packages.${system}.default.overrideAttrs (attrs: {
73-76: Unusedreckeyword; checks structure is correct.Same as
devShells- therecis unnecessary here. The check correctly runs the package build for CI validation.🔎 Proposed fix
- checks = forAllSystems ({ pkgs, system }: rec { + checks = forAllSystems ({ pkgs, system }: { build = self.packages.${system}.default; });
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
README.mdflake.nix
✅ Files skipped from review due to trivial changes (1)
- README.md
🔇 Additional comments (2)
flake.nix (2)
1-8: LGTM!The flake description and inputs are well-structured. Using the custom
nix-src/wasmbranch for WASM plugin support is appropriate for this use case.
20-60: Packages structure is well-designed.The
nix-wasm-pluginsderivation correctly:
- Extracts metadata from
Cargo.toml- Targets
wasm32-unknown-unknown- Runs integration tests comparing expected vs actual outputs
- Optimizes WASM output with
wasm-opt -O3- Includes all necessary native build inputs
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/5b31e99' (2026-01-04)
→ 'github:DeterminateSystems/nix-src/d977b54' (2026-01-06)
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/d977b54' (2026-01-06)
→ 'github:DeterminateSystems/nix-src/8bc414c' (2026-01-19)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@flake.nix`:
- Around line 50-56: Remove the unused build deps by editing the
nativeBuildInputs list: drop wasm-bindgen-cli and wasm-pack from
nativeBuildInputs since the build only runs cargo build, the check uses nix
eval, and install uses wasm-opt from binaryen; keep other entries
(rustc.llvmPackages.lld, binaryen, inputs.nix.packages.${system}.nix-cli) intact
so the pure wasm32-unknown-unknown build remains unchanged.
♻️ Duplicate comments (1)
flake.nix (1)
10-18: Theletbindings are well-structured.Parsing
cargoTomlfor single source of truth on package metadata and theforAllSystemshelper pattern are idiomatic Nix practices.Note: The missing
x86_64-darwininsupportedSystemswas flagged in a previous review.
🧹 Nitpick comments (4)
flake.nix (4)
6-6: Unused input:flake-schemasis declared but never referenced.The
flake-schemasinput is defined but not used anywhere in the outputs. Consider removing it to reduce unnecessary dependencies, or add aschemasoutput if you intend to use it.♻️ Suggested fix
inputs = { nixpkgs.follows = "nix/nixpkgs"; - flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*.tar.gz"; nix.url = "github:DeterminateSystems/nix-src/wasm"; };
32-32: MissingrunHookcalls in custom phases.Custom
buildPhase,checkPhase, andinstallPhaseshould includerunHook preBuild/runHook postBuild(and equivalents) to allow users to inject pre/post hooks when overriding the derivation.♻️ Suggested fix for buildPhase
- buildPhase = "cargo build --release"; + buildPhase = '' + runHook preBuild + cargo build --release + runHook postBuild + '';Similar changes should be applied to
checkPhase(addrunHook preCheck/runHook postCheck) andinstallPhase(addrunHook preInstall/runHook postInstall).
64-73: Unnecessaryreckeyword.The
reckeyword on line 65 is unused sincedefaultdoesn't reference any other attributes in this set. Removing it improves clarity.♻️ Suggested fix
- devShells = forAllSystems ({ pkgs, system }: rec { - default = with pkgs; self.packages.${system}.default.overrideAttrs (attrs: { + devShells = forAllSystems ({ pkgs, system }: { + default = self.packages.${system}.default.overrideAttrs (attrs: { nativeBuildInputs = attrs.nativeBuildInputs ++ [ - wabt - rust-analyzer - rustfmt - clippy + pkgs.wabt + pkgs.rust-analyzer + pkgs.rustfmt + pkgs.clippy ]; }); });Note: The
with pkgs;was also moved since it's only needed for the list items after removingrec.
75-77: Unnecessaryreckeyword in checks.Similar to
devShells, thereckeyword is unused here sincebuilddoesn't reference itself or other attributes.♻️ Suggested fix
- checks = forAllSystems ({ pkgs, system }: rec { + checks = forAllSystems ({ pkgs, system }: { build = self.packages.${system}.default; });
| nativeBuildInputs = [ | ||
| rustc.llvmPackages.lld | ||
| wasm-bindgen-cli | ||
| wasm-pack | ||
| binaryen | ||
| inputs.nix.packages.${system}.nix-cli | ||
| ]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, get an overview of the repository structure
find . -type f -name "*.rs" | head -20
find . -type f -name "Cargo.toml" | head -10Repository: DeterminateSystems/nix-wasm-rust
Length of output: 490
🏁 Script executed:
# Check if wasm-bindgen or wasm-pack are referenced anywhere in the Rust source
rg -n "wasm-bindgen|wasm-pack" --type rust
rg -n "wasm_bindgen" --type rustRepository: DeterminateSystems/nix-wasm-rust
Length of output: 58
🏁 Script executed:
# Also check in Cargo.toml files and other configuration files
rg -n "wasm-bindgen|wasm-pack" --type tomlRepository: DeterminateSystems/nix-wasm-rust
Length of output: 58
🏁 Script executed:
# Check what's in the flake.nix file around the mentioned lines
head -70 flake.nix | tail -30Repository: DeterminateSystems/nix-wasm-rust
Length of output: 936
🏁 Script executed:
# Check if wasm-bindgen-cli or wasm-pack are invoked anywhere in the flake.nix
rg -n "wasm-bindgen|wasm-pack" flake.nixRepository: DeterminateSystems/nix-wasm-rust
Length of output: 132
🏁 Script executed:
# Look at the buildPhase and other phases to see what actually runs
cat flake.nix | grep -A 20 "buildPhase"Repository: DeterminateSystems/nix-wasm-rust
Length of output: 839
🏁 Script executed:
# Check all Cargo.toml files for wasm-bindgen dependency declarations
for file in $(find . -name "Cargo.toml"); do
if grep -q "wasm" "$file"; then
echo "=== $file ==="
grep "wasm" "$file"
fi
doneRepository: DeterminateSystems/nix-wasm-rust
Length of output: 917
Remove unused build dependencies wasm-bindgen-cli and wasm-pack.
These tools are declared in nativeBuildInputs (lines 52–53) but are never invoked. The build phase runs only cargo build, the check phase uses nix eval, and the install phase uses wasm-opt from binaryen. No Cargo.toml declares these as dependencies, and no Rust source contains any reference to them. Since this is a pure wasm32-unknown-unknown build without JavaScript bindings generation, these tools can be safely removed.
🤖 Prompt for AI Agents
In `@flake.nix` around lines 50 - 56, Remove the unused build deps by editing the
nativeBuildInputs list: drop wasm-bindgen-cli and wasm-pack from
nativeBuildInputs since the build only runs cargo build, the check uses nix
eval, and install uses wasm-opt from binaryen; keep other entries
(rustc.llvmPackages.lld, binaryen, inputs.nix.packages.${system}.nix-cli) intact
so the pure wasm32-unknown-unknown build remains unchanged.
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/8bc414c' (2026-01-19)
→ 'github:DeterminateSystems/nix-src/cd76ab4' (2026-01-19)
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/cd76ab4' (2026-01-19)
→ 'github:DeterminateSystems/nix-src/22551fa' (2026-01-19)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@nix-wasm-plugin-grep/src/lib.rs`:
- Around line 4-22: The plugin's exported grep may panic across the extern "C"
boundary if nix_wasm_init_v1() hasn't been run first; either clearly document
that callers must call nix_wasm_init_v1() before using grep (update README / API
docs to state initialization order) or add local defense-in-depth in grep by
wrapping its body (the code that calls recurse and builds matches) in
std::panic::catch_unwind and, on Err, convert the panic into the same
abort/FFI-safe behavior used by the global hook (e.g., invoke the same
panic-to-FFI function or abort) so no unwinding crosses the FFI boundary; refer
to functions grep, recurse and the global initializer nix_wasm_init_v1 when
making the change.
♻️ Duplicate comments (3)
nix-wasm-rust/src/lib.rs (2)
195-200:make_attrsetuses ABI‑unstable(&str, Value)in FFI.Tuples containing
&strare not FFI‑safe;#[allow(improper_ctypes)]just suppresses the warning. Use arepr(C)struct with pointer+length andValueId(and update the host side to match).🛠️ Proposed fix
type ValueId = u32; +#[repr(C)] +struct AttrEntry { + name_ptr: *const u8, + name_len: usize, + value_id: ValueId, +} + impl Value { pub fn make_attrset(attrs: &[(&str, Value)]) -> Value { extern "C" { - #[allow(improper_ctypes)] - fn make_attrset(ptr: *const (&str, Value), len: usize) -> Value; + fn make_attrset(ptr: *const AttrEntry, len: usize) -> Value; } - unsafe { make_attrset(attrs.as_ptr(), attrs.len()) } + let entries: Vec<AttrEntry> = attrs + .iter() + .map(|(k, v)| AttrEntry { + name_ptr: k.as_ptr(), + name_len: k.len(), + value_id: v.0, + }) + .collect(); + unsafe { make_attrset(entries.as_ptr(), entries.len()) } }
203-219:copy_attrsetuses tuple layout across FFI.
*mut (ValueId, usize)has no guaranteed C ABI; replace it with arepr(C)struct and adjust the helper/buffers.🛠️ Proposed fix
+#[repr(C)] +struct AttrMeta { + value_id: ValueId, + name_len: usize, +} + - fn get_attrset_from_attrs(&self, attrs: &[(ValueId, usize)]) -> BTreeMap<String, Value> { + fn get_attrset_from_attrs(&self, attrs: &[AttrMeta]) -> BTreeMap<String, Value> { extern "C" { fn copy_attrname(value: ValueId, attr_idx: usize, ptr: *mut u8, len: usize); } let mut res = BTreeMap::new(); - for (attr_idx, (value, attr_len)) in attrs.iter().enumerate() { - let mut buf = vec![0; *attr_len]; + for (attr_idx, entry) in attrs.iter().enumerate() { + let mut buf = vec![0; entry.name_len]; unsafe { - copy_attrname(self.0, attr_idx, buf.as_mut_ptr(), *attr_len); + copy_attrname(self.0, attr_idx, buf.as_mut_ptr(), entry.name_len); } res.insert( String::from_utf8(buf).expect("Nix attribute name should be UTF-8."), - Value(*value), + Value(entry.value_id), ); } res } pub fn get_attrset(&self) -> BTreeMap<String, Value> { extern "C" { - #[allow(improper_ctypes)] - fn copy_attrset(value: ValueId, ptr: *mut (ValueId, usize), max_len: usize) -> usize; + fn copy_attrset(value: ValueId, ptr: *mut AttrMeta, max_len: usize) -> usize; } unsafe { // Optimistically call with a small buffer on the stack. - let mut buf = [(0, 0); 32]; + let mut buf = [AttrMeta { value_id: 0, name_len: 0 }; 32]; let len = copy_attrset(self.0, buf.as_mut_ptr(), buf.len()); if len > buf.len() { // If it didn't fit, allocate a buffer of the right size. - let mut buf = vec![(0, 0); len]; + let mut buf = vec![AttrMeta { value_id: 0, name_len: 0 }; len]; let len2 = copy_attrset(self.0, buf.as_mut_ptr(), buf.len()); assert!(len2 == len); self.get_attrset_from_attrs(&buf) } else { self.get_attrset_from_attrs(&buf[0..len]) } } }Also applies to: 221-238
nix-wasm-plugin-test/src/lib.rs (1)
4-75: Prevent panics from crossing the FFI boundary.All exported
extern "C"functions can panic viaexpect/get_*; unwinding across FFI is UB. Wrap each export withcatch_unwind(or useextern "C-unwind"if supported) and return a sentinel value.🛠️ Proposed fix (apply pattern to all exports)
use nix_wasm_rust::Value; +use std::panic; +fn with_unwind_guard<F: FnOnce() -> Value>(f: F) -> Value { + panic::catch_unwind(panic::AssertUnwindSafe(f)) + .unwrap_or_else(|_| Value::make_null()) +} + #[no_mangle] pub extern "C" fn range(arg: Value) -> Value { - let args = arg.get_attrset(); - - let start = args.get("start").map(|v| v.get_int()).unwrap_or(0); - let end = args.get("end").map(|v| v.get_int()).unwrap_or(10); - - let mut list = vec![]; - - for i in start..=end { - list.push(Value::make_int(i)); - } - - Value::make_list(&list) + with_unwind_guard(|| { + let args = arg.get_attrset(); + + let start = args.get("start").map(|v| v.get_int()).unwrap_or(0); + let end = args.get("end").map(|v| v.get_int()).unwrap_or(10); + + let mut list = vec![]; + + for i in start..=end { + list.push(Value::make_int(i)); + } + + Value::make_list(&list) + }) }
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/22551fa' (2026-01-19)
→ 'github:DeterminateSystems/nix-src/b878824' (2026-01-20)
| @@ -0,0 +1 @@ | |||
| [1,1,1] | |||
There was a problem hiding this comment.
I kinda expected what the LLM commented on... Why isn't it [1, 2, 3]? It's a static, and it's called from the same evaluation, so I'd expect that it would be properly incrementing each time, instead of reinitializing each time...
There was a problem hiding this comment.
Because every Wasm invocation starts from a fresh instance. So the global variable in the Wasm memory does nothing. This test is to verify that Wasm calls are indeed pure and don't retain state across calls.
There was a problem hiding this comment.
Ahhhhh gotcha, that makes sense. Thanks.
Co-authored-by: Cole Helbling <[email protected]>
Flake lock file updates:
• Updated input 'nix':
'github:DeterminateSystems/nix-src/b878824' (2026-01-20)
→ 'github:DeterminateSystems/nix-src/57ac520' (2026-01-21)
This adds a crate for writing WASM functions in Rust that can be executed by the Nix evaluator using
builtins.wasm.To try it out:
Summary by CodeRabbit
New Features
Chores
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.