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:
Christopher OverbeckandClaude Opus 5 committed 2026-09-17 10:33:18 -05:00
1 parent 38aa2ee34d
commit a3d65e2fc8
7 files changed
+223 -15

No files matched your search

+41
View File
@@ -283,6 +283,20 @@ fn surveyor(value_rad: f64, prec: usize) -> String {
/// A leading `-` is a sign, but the `-` inside `5'-9"` is a separator — after
/// feet there is nothing left to subtract from.
pub fn parse_length(text: &str) -> Option<f64> {
parse_length_unbounded(text).filter(|value| typed_value_in_range(*value))
}
/// Largest magnitude a typed length or angle may have. Rust's float parser
/// accepts `inf`, `nan` and overflowing exponents; geometry built from those,
/// or from values whose differences overflow, breaks downstream tolerance and
/// intersection math.
pub const MAX_TYPED_MAGNITUDE: f64 = 1e15;
pub fn typed_value_in_range(value: f64) -> bool {
value.is_finite() && value.abs() <= MAX_TYPED_MAGNITUDE
}
fn parse_length_unbounded(text: &str) -> Option<f64> {
let text = text.trim();
let (sign, rest) = match text.strip_prefix('-') {
Some(rest) => (-1.0, rest.trim_start()),
@@ -339,6 +353,10 @@ fn parse_inches(text: &str) -> Option<f64> {
/// number is read in whatever convention the drawing is set to, so what the
/// readout shows can be typed straight back.
pub fn parse_angle(text: &str) -> Option<f64> {
parse_angle_unbounded(text).filter(|value| typed_value_in_range(*value))
}
fn parse_angle_unbounded(text: &str) -> Option<f64> {
let text = text.trim();
if text.is_empty() {
return None;
@@ -679,11 +697,16 @@ pub fn parse_f64(value: &str) -> Option<f64> {
.ok()
.or_else(|| parse_length(t))
.or_else(|| parse_angle_deg(t))
.filter(|value| typed_value_in_range(*value))
}
/// Parse an angle string the panel displayed via AUNITS back to DEGREES:
/// "30", "30°"/"30d", DMS "30°15'20.5\"", grads "33.33g", radians "0.52r".
pub fn parse_angle_deg(value: &str) -> Option<f64> {
parse_angle_deg_unbounded(value).filter(|value| typed_value_in_range(*value))
}
fn parse_angle_deg_unbounded(value: &str) -> Option<f64> {
let s = value.trim();
if s.is_empty() {
return None;
@@ -1161,6 +1184,24 @@ pub(crate) fn polyline_segment_fill(
}
}
#[cfg(test)]
mod typed_range_tests {
use super::*;
#[test]
fn non_finite_and_huge_typed_values_are_rejected() {
for text in ["inf", "-inf", "nan", "1e400", "1e308"] {
assert_eq!(parse_length(text), None, "length {text}");
assert_eq!(parse_angle(text), None, "angle {text}");
assert_eq!(parse_f64(text), None, "property {text}");
assert_eq!(parse_angle_deg(text), None, "angle degrees {text}");
}
assert_eq!(parse_length("1e16"), None);
assert_eq!(parse_length("1e15"), Some(1e15));
assert_eq!(parse_length("-5'-6\""), Some(-66.0));
}
}
#[cfg(test)]
mod length_format_tests {
use super::*;
+28 -1
View File
@@ -80,7 +80,14 @@ fn boundary_edges(points: &[[f64; 3]]) -> Vec<Edge> {
}
fn kernel_tolerance(points: &[[f64; 3]]) -> f64 {
cadkernel::space::coplanarity_tolerance(points).max(1.0e-12)
let tolerance = cadkernel::space::coplanarity_tolerance(points);
// Corners whose extent overflows f64 give an infinite tolerance, which
// `Tolerance::new` rejects by panicking.
if tolerance.is_finite() {
tolerance.max(1.0e-12)
} else {
1.0e-12
}
}
fn extrusion_body(
@@ -366,3 +373,23 @@ impl Transformable for Solid {
);
}
}
#[cfg(test)]
mod overflow_tests {
use super::*;
use acadrust::types::Vector3;
/// Corners 1e308 apart overflow the coplanarity tolerance to infinity,
/// which the kernel's `Tolerance::new` rejects by panicking.
#[test]
fn corners_with_overflowing_extent_do_not_panic() {
let solid = Solid::new(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1e308, 0.0, 0.0),
Vector3::new(0.0, 1e308, 0.0),
Vector3::new(1e308, 1e308, 0.0),
);
let _ = wcs_bounds(&solid);
let _ = solid_geometry(&solid);
}
}