From 0c49026c9a1bc9a5d9a8758388ef6d6b8890cffe Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 17 Aug 2026 10:28:28 +0000 Subject: [PATCH] Error on out-of-range copy addressing instead of wrapping in the repeat family --- node-graph/nodes/gcore/src/record.rs | 15 ++++++++++----- node-graph/nodes/repeat/src/leveled.rs | 8 ++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/node-graph/nodes/gcore/src/record.rs b/node-graph/nodes/gcore/src/record.rs index 24a5c1a21b..f5d9a60d73 100644 --- a/node-graph/nodes/gcore/src/record.rs +++ b/node-graph/nodes/gcore/src/record.rs @@ -6,10 +6,10 @@ //! wiring is by hand until the compiler pass constructs layouts. use core_types::attribute::{Attr, Opacity, RemoveAttr}; -use core_types::context::{DeriveCtx, ExtractArena, ExtractIndex, IndexLink, InjectIndex}; +use core_types::context::{DeriveCtx, ExtractIndex, IndexLink, InjectIndex}; use core_types::extent::{ExtentIn, LevelIn, ValueIn}; use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt}; -use core_types::{Context, Ctx}; +use core_types::Ctx; core_types::attribute! { /// Test-only measured length of an element. @@ -55,6 +55,7 @@ fn fade(_: impl Ctx, (element, opacity): (T, Attr), factor: f64) -> /// writes a per-copy opacity indexed by the copy's own index. #[node_macro::node(category("Test"), extent(repeat_opacity_extent))] fn repeat_opacity(ctx: impl Ctx + ExtractIndex, element: f64, count: u32) -> IList<(f64, Attr)> { + debug_assert!(ctx.innermost_index() < count as u64, "repeat addressed past its copy count"); emit(element, Attr(ctx.innermost_index() as f64)) } @@ -84,7 +85,9 @@ fn repeat( ) -> Result, Interrupt> { let inner = content.inner_extent(ctx)?; let (copy, rest) = ctx.split_innermost(inner); - let copy = copy % count.max(1) as u64; + if copy >= count as u64 { + return Err(GraphError::new("repeat addressed past its copy count").into()); + } let copy = match reverse { true => count as u64 - 1 - copy, false => copy, @@ -112,7 +115,9 @@ fn repeat_faded( ) -> Result)>, Interrupt> { let inner = content.inner_extent(ctx)?; let (copy, rest) = ctx.split_innermost(inner); - let copy = copy % count.max(1) as u64; + if copy >= count as u64 { + return Err(GraphError::new("repeat addressed past its copy count").into()); + } let mut frame = IndexLink { index: 0, outer: None }; let (element, opacity) = content.eval(&ctx.push_level(&mut frame, copy, rest))?; Ok(emit(element, Attr(*opacity * (copy + 1) as f64))) @@ -359,7 +364,7 @@ mod tests { let leveled = repeat_opacity_layout(&base); reserve_for(&[&base, &leveled]); - let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]); + let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(8u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]); assert_eq!(node.layout(), &leveled); let GPoll::Final(value) = node.eval(&indexed) else { panic!("expected a final record"); diff --git a/node-graph/nodes/repeat/src/leveled.rs b/node-graph/nodes/repeat/src/leveled.rs index c49776f512..1f8d2f14b5 100644 --- a/node-graph/nodes/repeat/src/leveled.rs +++ b/node-graph/nodes/repeat/src/leveled.rs @@ -26,7 +26,9 @@ fn repeat_array( ) -> Result)>, Interrupt> { let inner = content.inner_extent(ctx)?; let (copy, rest) = ctx.split_innermost(inner); - let copy = copy % count.max(1) as u64; + if copy >= count as u64 { + return Err(GraphError::new("repeat addressed past its copy count").into()); + } let mut frame = IndexLink { index: 0, outer: None }; let (element, local) = content.eval(&ctx.push_level(&mut frame, copy, rest))?; @@ -63,7 +65,9 @@ fn repeat_radial( ) -> Result)>, Interrupt> { let inner = content.inner_extent(ctx)?; let (copy, rest) = ctx.split_innermost(inner); - let copy = copy % count.max(1) as u64; + if copy >= count as u64 { + return Err(GraphError::new("repeat addressed past its copy count").into()); + } let mut frame = IndexLink { index: 0, outer: None }; let (element, local) = content.eval(&ctx.push_level(&mut frame, copy, rest))?;