Reject lossy float-to-integer coercion during deserialization
Only allow float-to-integer conversion when the float is finite and has
no fractional part (e.g. 3.0 → 3). Previously, values like 3.7 were
silently truncated to 3 and non-finite floats saturated to i128 bounds.
change zsokzlkonuxlpultytxvnypvuorxnuxs
commit a657f74f872d9bfc980e8cbcfb1ee3381b67d0b5
author Alpha Chen <alpha@kejadlen.dev>
date
parent qvosxrlu
diff --git a/src/de.rs b/src/de.rs
index 1f12e8b..6ebdfb3 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -902,7 +902,16 @@ impl<'a> ValueDeserializer<'a> {
     fn to_i128(&self) -> Result<i128> {
         match self.value {
             KdlValue::Integer(i) => Ok(*i),
-            KdlValue::Float(f) => Ok(*f as i128),
+            KdlValue::Float(f) => {
+                if f.is_finite() && f.fract() == 0.0 {
+                    Ok(*f as i128)
+                } else {
+                    Err(Error::TypeMismatch {
+                        expected: "integer (float has fractional part or is non-finite)",
+                        got: format!("{f}"),
+                    })
+                }
+            }
             other => Err(Error::TypeMismatch {
                 expected: "integer",
                 got: format!("{other:?}"),
diff --git a/tests/integration.rs b/tests/integration.rs
index e350271..32a74aa 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -1082,6 +1082,8 @@ fn field_deserialize_enum_multi_children_error() {
 deser_field!(deserialize_f32_from_integer, value: f32, "value 3", 3.0f32);
 deser_field!(deserialize_f64_from_integer, value: f64, "value 42", 42.0f64);
 deser_field!(deserialize_int_from_float, count: i32, "count 3.0", 3);
+deser_field_err!(deserialize_int_from_fractional_float, count: i32, "count 3.7");
+deser_field_err!(deserialize_int_from_negative_frac, count: i32, "count -1.5");
 deser_field!(value_deserializer_any_integer, val: i128, "val 42", 42i128);
 deser_field!(value_deserializer_any_bool, val: bool, "val #true", true);
 deser_field!(value_deserializer_null_option, optional: Option<i32>, "optional #null", None);