forked from 1b-agent/opencadstudio-upstream
Bound typed counts and values that could crash the editor
- ARRAYRECT, ARRAYPOLAR, ARRAYPATH, ARRAY3D and COPY Array reject item counts above 100,000 (checked as a product of every dimension) and keep the prompt open. Unbounded counts allocated one transform per item and aborted on allocation failure. Stored defaults are clamped as well. - EXPLODE leaves a MINSERT above the same limit intact instead of materializing every cell. - Typed lengths and angles reject non-finite values and magnitudes above 1e15; SCALE rejects a non-finite factor. - Zero-radius FILLET compares candidate intersections with total_cmp and rejects non-finite results; partial_cmp().unwrap() panicked on NaN. - SOLID falls back to a minimal kernel tolerance when its corners' extent overflows, since Tolerance::new panics on infinity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
38aa2ee34d
commit
a3d65e2fc8
7 files changed
+223
-15
No files matched your search
@@ -45,6 +45,27 @@ pub const DROPDOWN_ITEMS: &[(&str, &str, IconKind)] = &[
|
||||
),
|
||||
];
|
||||
|
||||
// ── Item limit ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// Most items one array may create, including the source. Every item becomes
|
||||
/// a transform and then a copy of each selected object, so an unbounded typed
|
||||
/// count exhausts memory before anything is drawn.
|
||||
pub(crate) const MAX_ARRAY_ITEMS: u64 = 100_000;
|
||||
|
||||
/// Whether an array of `counts` (rows, columns, levels, ...) stays in bounds.
|
||||
pub(crate) fn array_items_within_limit(counts: &[u64]) -> bool {
|
||||
counts
|
||||
.iter()
|
||||
.try_fold(1u64, |total, &count| total.checked_mul(count))
|
||||
.is_some_and(|total| total <= MAX_ARRAY_ITEMS)
|
||||
}
|
||||
|
||||
pub(crate) fn array_limit_error() -> CmdResult {
|
||||
CmdResult::ReportError(
|
||||
crate::tf!("Array size exceeds the limit of {} items.", MAX_ARRAY_ITEMS).into_owned(),
|
||||
)
|
||||
}
|
||||
|
||||
// ── Rectangular Array ──────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -72,8 +93,8 @@ impl ArrayRectCommand {
|
||||
handles,
|
||||
wire_models,
|
||||
step: RectStep::Rows,
|
||||
default_rows: defaults::get_array_rows() as u32,
|
||||
default_cols: defaults::get_array_cols() as u32,
|
||||
default_rows: (defaults::get_array_rows() as u32).clamp(1, MAX_ARRAY_ITEMS as u32),
|
||||
default_cols: (defaults::get_array_cols() as u32).clamp(1, MAX_ARRAY_ITEMS as u32),
|
||||
default_row_sp: defaults::get_array_row_sp(),
|
||||
default_col_sp: defaults::get_array_col_sp(),
|
||||
plane: WorkingPlane::default(),
|
||||
@@ -154,6 +175,9 @@ impl CadCommand for ArrayRectCommand {
|
||||
self.default_rows
|
||||
} else {
|
||||
let v = t.parse::<u32>().unwrap_or(self.default_rows).max(1);
|
||||
if !array_items_within_limit(&[v as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
defaults::set_array_rows(v as f64);
|
||||
self.default_rows = v;
|
||||
v
|
||||
@@ -165,11 +189,15 @@ impl CadCommand for ArrayRectCommand {
|
||||
let cols = if t.is_empty() {
|
||||
self.default_cols
|
||||
} else {
|
||||
let v = t.parse::<u32>().unwrap_or(self.default_cols).max(1);
|
||||
defaults::set_array_cols(v as f64);
|
||||
self.default_cols = v;
|
||||
v
|
||||
t.parse::<u32>().unwrap_or(self.default_cols).max(1)
|
||||
};
|
||||
if !array_items_within_limit(&[rows as u64, cols as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
if !t.is_empty() {
|
||||
defaults::set_array_cols(cols as f64);
|
||||
self.default_cols = cols;
|
||||
}
|
||||
self.step = RectStep::RowSp { rows, cols };
|
||||
None
|
||||
}
|
||||
@@ -222,6 +250,10 @@ impl CadCommand for ArrayRectCommand {
|
||||
}
|
||||
RectStep::ColSp { rows, cols, row_sp } => (rows, cols, row_sp, self.default_col_sp),
|
||||
};
|
||||
// Defaults are only bounded one count at a time.
|
||||
if !array_items_within_limit(&[rows as u64, cols as u64]) {
|
||||
return vec![];
|
||||
}
|
||||
Self::build_transforms(rows, cols, row_sp, col_sp, self.plane)
|
||||
.iter()
|
||||
.flat_map(|t| {
|
||||
@@ -274,7 +306,7 @@ impl ArrayPolarCommand {
|
||||
handles,
|
||||
wire_models,
|
||||
step: PolarStep::Center,
|
||||
default_count: defaults::get_array_p_count() as u32,
|
||||
default_count: (defaults::get_array_p_count() as u32).clamp(2, MAX_ARRAY_ITEMS as u32),
|
||||
default_angle: defaults::get_array_p_angle(),
|
||||
plane: WorkingPlane::default(),
|
||||
}
|
||||
@@ -330,6 +362,9 @@ impl CadCommand for ArrayPolarCommand {
|
||||
self.default_count
|
||||
} else {
|
||||
let v = t.parse::<u32>().unwrap_or(self.default_count).max(2);
|
||||
if !array_items_within_limit(&[v as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
defaults::set_array_p_count(v as f64);
|
||||
self.default_count = v;
|
||||
v
|
||||
@@ -461,7 +496,7 @@ impl ArrayPathCommand {
|
||||
wire_models,
|
||||
all_entities,
|
||||
step: PathStep::SelectPath,
|
||||
default_count: defaults::get_array_path_count() as u32,
|
||||
default_count: (defaults::get_array_path_count() as u32).clamp(2, MAX_ARRAY_ITEMS as u32),
|
||||
pick_pt: DVec3::ZERO,
|
||||
}
|
||||
}
|
||||
@@ -686,6 +721,9 @@ impl CadCommand for ArrayPathCommand {
|
||||
self.default_count
|
||||
} else {
|
||||
let v = t.parse::<u32>().unwrap_or(self.default_count).max(2);
|
||||
if !array_items_within_limit(&[v as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
defaults::set_array_path_count(v as f64);
|
||||
self.default_count = v;
|
||||
v
|
||||
@@ -891,6 +929,9 @@ impl CadCommand for Array3DCommand {
|
||||
} else {
|
||||
t.parse::<u32>().unwrap_or(2).max(1)
|
||||
};
|
||||
if !array_items_within_limit(&[v as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
self.step = Array3DStep::Cols { rows: v };
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
@@ -900,6 +941,9 @@ impl CadCommand for Array3DCommand {
|
||||
} else {
|
||||
t.parse::<u32>().unwrap_or(2).max(1)
|
||||
};
|
||||
if !array_items_within_limit(&[rows as u64, v as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
self.step = Array3DStep::Levels { rows, cols: v };
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
@@ -909,6 +953,9 @@ impl CadCommand for Array3DCommand {
|
||||
} else {
|
||||
t.parse::<u32>().unwrap_or(2).max(1)
|
||||
};
|
||||
if !array_items_within_limit(&[rows as u64, cols as u64, v as u64]) {
|
||||
return Some(array_limit_error());
|
||||
}
|
||||
self.step = Array3DStep::RowSp {
|
||||
rows,
|
||||
cols,
|
||||
@@ -987,3 +1034,33 @@ impl CadCommand for Array3DCommand {
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["ARRAY3D", "3DARRAY"] });
|
||||
|
||||
#[cfg(test)]
|
||||
mod item_limit_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn item_limit_counts_every_dimension_without_overflow() {
|
||||
assert!(array_items_within_limit(&[MAX_ARRAY_ITEMS]));
|
||||
assert!(!array_items_within_limit(&[MAX_ARRAY_ITEMS + 1]));
|
||||
assert!(!array_items_within_limit(&[1_000, 1_000]));
|
||||
assert!(!array_items_within_limit(&[u32::MAX as u64, u32::MAX as u64, u32::MAX as u64]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oversized_counts_are_rejected_at_the_prompt() {
|
||||
let mut rect = ArrayRectCommand::new(vec![Handle::new(1)], Vec::new());
|
||||
assert!(rect.on_text_input("100000").is_none());
|
||||
assert!(matches!(rect.on_text_input("100000"), Some(CmdResult::ReportError(_))));
|
||||
assert!(matches!(rect.step, RectStep::Cols { rows: 100_000 }));
|
||||
|
||||
let mut polar = ArrayPolarCommand::new(vec![Handle::new(1)], Vec::new());
|
||||
polar.step = PolarStep::Count { center: DVec3::ZERO };
|
||||
assert!(matches!(polar.on_text_input("4294967295"), Some(CmdResult::ReportError(_))));
|
||||
|
||||
let mut cube = Array3DCommand::new(vec![Handle::new(1)]);
|
||||
assert!(matches!(cube.on_text_input("300"), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(cube.on_text_input("300"), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(cube.on_text_input("2"), Some(CmdResult::ReportError(_))));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user