Refuse to save documents containing NaN or infinite numbers

Found while testing crates/format/src/lib.rs:256 (to_json/write_json/save_bytes) and :323 (parse_json)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-21 01:27:12 +02:00
co-authored by Claude Haiku 5.5
parent d9c924c3ba
commit 503b6d606d
+186
View File
@@ -252,13 +252,184 @@ fn image_index<S: Serializer>(images: &&BTreeMap<String, ImageData>, s: S) -> Re
s.collect_seq(images.iter().map(|(hash, i)| Entry { hash, width: i.width, height: i.height }))
}
/// A serializer that writes nothing and fails on the first NaN or infinite float. JSON has no
/// such numbers (`serde_json` writes `null`, which the reader rejects), so a document holding
/// one is refused instead of saved as an unreadable file.
struct FiniteCheck;
impl serde::ser::Error for FiniteCheckError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self(msg.to_string())
}
}
#[derive(Debug)]
struct FiniteCheckError(String);
impl std::fmt::Display for FiniteCheckError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for FiniteCheckError {}
macro_rules! check_skip {
($($name:ident($ty:ty)),* $(,)?) => {
$(fn $name(self, _: $ty) -> Result<(), FiniteCheckError> { Ok(()) })*
};
}
impl Serializer for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
type SerializeSeq = FiniteCheck;
type SerializeTuple = FiniteCheck;
type SerializeTupleStruct = FiniteCheck;
type SerializeTupleVariant = FiniteCheck;
type SerializeMap = FiniteCheck;
type SerializeStruct = FiniteCheck;
type SerializeStructVariant = FiniteCheck;
check_skip!(
serialize_bool(bool),
serialize_i8(i8),
serialize_i16(i16),
serialize_i32(i32),
serialize_i64(i64),
serialize_u8(u8),
serialize_u16(u16),
serialize_u32(u32),
serialize_u64(u64),
serialize_i128(i128),
serialize_u128(u128),
serialize_char(char),
serialize_str(&str),
serialize_bytes(&[u8])
);
fn serialize_f32(self, v: f32) -> Result<(), FiniteCheckError> {
if v.is_finite() { Ok(()) } else { Err(FiniteCheckError(format!("cannot save a non-finite number ({v})"))) }
}
fn serialize_f64(self, v: f64) -> Result<(), FiniteCheckError> {
if v.is_finite() { Ok(()) } else { Err(FiniteCheckError(format!("cannot save a non-finite number ({v})"))) }
}
fn serialize_none(self) -> Result<(), FiniteCheckError> {
Ok(())
}
fn serialize_some<T: ?Sized + Serialize>(self, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(self)
}
fn serialize_unit(self) -> Result<(), FiniteCheckError> {
Ok(())
}
fn serialize_unit_struct(self, _: &'static str) -> Result<(), FiniteCheckError> {
Ok(())
}
fn serialize_unit_variant(self, _: &'static str, _: u32, _: &'static str) -> Result<(), FiniteCheckError> {
Ok(())
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(self)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(self, _: &'static str, _: u32, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(self)
}
fn serialize_seq(self, _: Option<usize>) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
fn serialize_tuple(self, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
fn serialize_tuple_struct(self, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
fn serialize_tuple_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
fn serialize_map(self, _: Option<usize>) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
fn serialize_struct(self, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
fn serialize_struct_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
}
}
macro_rules! check_elements {
($($trait:ident::$method:ident),* $(,)?) => {
$(impl serde::ser::$trait for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn $method<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
}
})*
};
}
check_elements!(
SerializeSeq::serialize_element,
SerializeTuple::serialize_element,
SerializeTupleStruct::serialize_field,
SerializeTupleVariant::serialize_field
);
impl serde::ser::SerializeMap for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn serialize_key<T: ?Sized + Serialize>(&mut self, k: &T) -> Result<(), FiniteCheckError> {
k.serialize(FiniteCheck)
}
fn serialize_value<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
}
}
impl serde::ser::SerializeStruct for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
}
}
impl serde::ser::SerializeStructVariant for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
}
}
/// Fail when the document holds a number JSON cannot represent.
fn ensure_finite(doc: &Document) -> Result<(), FormatError> {
DocumentJson::new(doc).serialize(FiniteCheck).map_err(io_err)
}
/// Serialize the document (without images) to the JSON value stored in `document.json`.
pub fn to_json(doc: &Document) -> Result<Value, FormatError> {
ensure_finite(doc)?;
serde_json::to_value(DocumentJson::new(doc)).map_err(io_err)
}
/// Write the document (without images) as `document.json` content to `w`.
pub fn write_json(doc: &Document, w: impl Write) -> Result<(), FormatError> {
ensure_finite(doc)?;
serde_json::to_writer(w, &DocumentJson::new(doc)).map_err(io_err)
}
@@ -808,6 +979,21 @@ mod tests {
assert!(load_bytes(&json).is_ok());
}
#[test]
fn non_finite_numbers_are_refused_at_save() {
let mut d = Document::new("NaN");
let p = d.first_page().unwrap();
let r = d.create(p, NodeKind::Rectangle, "R", 1.0, 2.0, 3.0, 4.0).unwrap();
assert!(save_bytes(&d, None).is_ok());
d.get_mut(r).unwrap().width = f64::NAN;
let e = save_bytes(&d, None).unwrap_err();
assert!(e.to_string().contains("non-finite"), "{e}");
d.get_mut(r).unwrap().width = 3.0;
d.get_mut(r).unwrap().opacity = f32::INFINITY;
let e = save_bytes(&d, None).unwrap_err();
assert!(e.to_string().contains("non-finite"), "{e}");
}
#[test]
fn garbage_is_an_error() {
assert!(load_bytes(b"not a zip").is_err());