Skip to content

fix(specs): bound simulation and layout loops to their arrays - #411

Open
dmitrii-f-t27 wants to merge 2 commits into
gHashTag:mainfrom
dmitrii-f-t27:fix/bounds-410
Open

dmitrii-f-t27 wants to merge 2 commits into
gHashTag:mainfrom
dmitrii-f-t27:fix/bounds-410

Conversation

@dmitrii-f-t27

Copy link
Copy Markdown
Contributor

Closes #410

Refs #399. Stacked on #409: this branch starts at #409's head 59f39b68, because #409 changes the same three specs and makes these modules compile in Rust. Until #409 merges, this PR also shows its commit; merge #409 first, then this PR (the diff then reduces to the single commit da49268a).

What changes

Four generated functions indexed past a fixed-size array or divided by zero for out-of-range inputs. The spec is the fix; all three backends are regenerated with the unchanged pin eb8b4208, no generated file is edited by hand.

Spec function Input from #410 Before After
network_simulator::inject_fault EVENT_NODE_FAILURE, target_id = 32 index out of bounds returns 0 (the existing failure value); node_states untouched
traffic_animator::update_animation_packets packet_count = 65 index out of bounds count clamped to MAX_PACKETS (64); active_packets field reports 64
topology_visualizer::calculate_circular_layout node_count = 33 index out of bounds count clamped to MAX_NODES (32) and returned
topology_visualizer::calculate_hierarchical_layout node_count = 3 division by zero one node per level when node_count < 4; count also clamped to 32

In-range inputs return the same values as before. Public argument and return types are unchanged. inject_fault uses if/else instead of a nested early return, because the pinned Verilog emitter does not skip the statements after a nested early return (the first draft failed icarus-simulate for exactly that reason).

Tests

  • Each spec gains a test block with the boundary inputs. Locally they pass under zig test (Zig 0.15.2), the C runner (-DT27_TEST_MAIN) and t27c icarus-simulate.
  • tests/gen_rust_bounds.rs (declared in Cargo.toml) calls the generated gen/rust modules with the inputs from the issue table plus u32::MAX. Negative control: against the previous gen/rust all five tests panic (topology_visualizer.rs:220, :242, network_simulator.rs:283, traffic_animator.rs:283).
  • Local, rustc 1.96.0: cargo fmt --all --check, cargo clippy --all-targets -- -D warnings and cargo test pass, including gen_rust_corpus_compiles from fix(gen): compile all 102 generated Rust modules and gate the corpus #409.
  • Drift: all nine artifacts match pinned t27c output; tools/regen.rs --check-staged verified 9 staged artifacts.
  • Typecheck warning ratchet unchanged: network_simulator 6, traffic_animator 28, topology_visualizer 27.

CI on this PR is the source of truth; the local runs above are not a substitute for it.

Not in scope

Same pattern, not listed in #410, left for a follow-up: run_simulation_step (event_count > MAX_EVENTS), generate_traffic_heat_map (packet_count > MAX_PACKETS) and calculate_animation_stats (frame_count > MAX_FRAMES).

Dmitrii Fedorov (@dmitrii-f-t27)

Ten generated Rust modules did not compile under rustc 1.96 with
-D warnings, although the drift check passed, because the drift check
only compares text with the pinned t27c output.

Spec changes, regenerated with the unchanged pin eb8b4208:
- Nine specs assigned to value parameters (E0384, E0594). Each such
  function now copies the parameter into a local `var` and writes the
  copy. This is the value semantics that the generated C (struct by
  value) and Zig (shadow) already had, so no result changes.
- api_documenter: the array repeat count is a literal, because the
  pinned Rust emitter copies a u32 count verbatim (E0308).
- auto_config: drop five `value >= 0` tests on u32 (unused_comparisons).
- integration_framework: rename type_documentation to
  TYPE_DOCUMENTATION (non_upper_case_globals).

Public argument and return types are unchanged. Typecheck warning
counts are unchanged for all ten specs.

tests/gen_rust_corpus_compiles.rs compiles every gen/rust module as its
own crate with -D warnings, enforces a floor of 102 modules, and first
proves that five negative controls (E0384, E0594, E0308,
unused_comparisons, non_upper_case_globals) are rejected.
tests/gen_rust_value_params.rs checks results and the caller contract
of the changed functions.

Closes gHashTag#399
Four generated functions indexed past a fixed-size array or divided by
zero for out-of-range inputs, so the generated Rust panicked, the C
read out of bounds or trapped, and Zig safety checks trapped.

Spec changes, regenerated with the unchanged pin eb8b4208:
- network_simulator.inject_fault: a node failure with
  target_id >= MAX_NODES returns 0 (the existing failure value) and
  leaves node_states alone. Written as if/else rather than an early
  return, because the pinned Verilog emitter does not skip statements
  after a nested early return.
- traffic_animator.update_animation_packets: packet_count is clamped
  to MAX_PACKETS; the active_packets field reports the clamped count.
- topology_visualizer.calculate_circular_layout and
  calculate_hierarchical_layout: node_count is clamped to MAX_NODES
  and the clamped count is returned. The hierarchical layout uses one
  node per level when node_count < 4 instead of dividing by zero.

In-range inputs return the same values as before. Public argument and
return types are unchanged. Typecheck warning counts are unchanged
(6, 28, 27).

Each spec gains a test block with the boundary inputs (runs under zig
test, the C runner and icarus-simulate). tests/gen_rust_bounds.rs calls
the generated Rust with the inputs from the issue table; against the
previous gen/rust all five tests panic.

Closes gHashTag#410
Refs gHashTag#399
@tri-claw

tri-claw Bot commented Oct 11, 2026

Copy link
Copy Markdown

Summary by t27-bees

Checked by the t27 compiler

t27c parse, parse-complete and typecheck on each changed .t27 file at da49268. Nothing was generated, built or run. A pass means the file is well formed, not that it is right.

  • specs/api_documenter.t27: parses and typechecks.
  • specs/auto_config.t27: parses and typechecks.
  • specs/bandwidth_allocator.t27: parses and typechecks.
  • specs/cache_management.t27: parses and typechecks.
  • specs/compression_engine.t27: parses and typechecks.
  • specs/integration_framework.t27: parses and typechecks.
  • specs/network_simulator.t27: parses and typechecks.
  • specs/performance_profiler.t27: parses and typechecks.
  • specs/topology_visualizer.t27: parses and typechecks.
  • specs/traffic_animator.t27: parses and typechecks.

Read by the model

This PR fixes four out-of-bounds accesses and one division-by-zero in generated code by adding bounds checks and clamping in the spec files, then regenerating all three backends. The fixes target inject_fault, update_animation_packets, calculate_circular_layout, and calculate_hierarchical_layout with tests for each boundary.

Worth a look

  • gen/c/api_documenter.c:337 and gen/rust/api_documenter.rs:276 — Array initialization changed from [0; MAX_PARAMETERS] to hardcoded [0; 16]. If MAX_PARAMETERS ≠ 16 in the spec, this creates a size mismatch between the type (t27_arr_uint32_t_16 / [u32; MAX_PARAMETERS]) and the initializer.
  • gen/c/network_simulator.c:317–329 — inject_fault uses if/else instead of an early return to work around a Verilog emitter bug (statements after nested early return weren't skipped). This changes control flow shape; verify the Verilog backend still produces equivalent hardware.
  • gen/c/auto_config.c:210–238 — validate_config dropped value >= 0 checks (always true for uint32_t). Functionally equivalent, but if any parameter type becomes signed in a future spec change, the bounds check would silently disappear.
  • gen/c/topology_visualizer.c:233–255 — calculate_hierarchical_layout now clamps node_count to MAX_NODES before computing nodes_per_level = count / 4, then forces nodes_per_level = 1 when zero. This prevents div-by-zero but changes the layout for node_count ∈ {1,2,3} (previously would have divided by zero; now each node gets its own level).
  • gen/c/cache_management.c:133–185 and gen/rust/cache_management.rs:78–136 — add_entry, remove_entry, access_cache, age_cache all copy the input array to a local slots/mut slots before mutating. In C this is necessary (arrays decay to pointers), but in Rust the parameter is already a mutable copy ([u32; N] is Copy). The extra copy is harmless but suggests the code generator treats all backends uniformly rather than leveraging Rust's ownership.

Read: 44 files, +1651 -467, the whole diff up to 60000 characters (the rest was not read). Head da49268. Model: openai-compatible/nvidia/nemotron-3-ultra-550b-a55b. Commands: @t27-bees review | summary | help | pause | resume. Free for every repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated simulation and layout functions index past arrays or divide by zero for out-of-range counts

1 participant