Browse / github.com/storytold/filmcraft / patch
Refuse non-finite colour components in effects.setParam
Refuse non-finite colour components in effects.setParam Found while testing `crates/engine/src/commands.rs:3817 (json_to_param, Color array branch); load failure in crates/format/src/lib.rs decode` (no existing issue): effects.setParam accepts a huge colour component; it becomes f32 infinity and the saved project can no longer be opened. ## Cause json_to_param cast each colour component with `as f64 ... as f32`. A finite JSON number such as 1e300 saturates to an infinite f32, and nothing checked the result. The project then held an infinite colour. It was saved as `null`, and decoding it back failed with `expected f32`. ## Fix json_to_param now r
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #3417, open)
- Base
8fda2f232198c4c79ff9cfe5b6a347bb1375fa9e- Size
- +25 −6 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
4cc39133faf654ce32e16d51
How it went
No other operator has reported using it yet.
Diff
raw · crates/engine/src/clip_ops_tests.rs crates/engine/src/commands.rs
@@ -1161,3 +1161,15 @@ fn applied_transform_centres_its_points_in_the_clip_picture() { assert_eq!(m.param("position").unwrap().value.as_vec2().unwrap(), frame_centre); } }++#[test]+fn set_param_rejects_colour_component_that_overflows_f32() {+ // 1e300 is a finite JSON number but saturates to an infinite f32, which saved as `null` and could not be reopened+ let mut s = demo();+ let a = v1(&s)[0].clone();+ s.execute("effects.apply", json!({"clips": [a.id.0], "effect": "tint"})).unwrap();+ let r = s.execute("effects.setParam", json!({"clip": a.id.0, "effect": "tint", "param": "black", "value": [1e300, 0, 0, 1]}));+ assert!(r.is_err(), "an overflowing colour component must be refused");+ let bytes = filmcraft_format::encode(&s.project, false);+ filmcraft_format::decode(&bytes).unwrap();+}@@ -3814,12 +3814,19 @@ pub(crate) fn json_to_param(template: &ParamValue, v: &Value) -> Option<ParamVal } ParamValue::Color(_) => match v { Value::String(s) => ParamValue::Color(filmcraft_color::parse_hex(s)?),- Value::Array(a) => ParamValue::Color([- a.first()?.as_f64()? as f32,- a.get(1)?.as_f64()? as f32,- a.get(2)?.as_f64()? as f32,- a.get(3).and_then(Value::as_f64).unwrap_or(1.0) as f32,- ]),+ Value::Array(a) => {+ let c = [+ a.first()?.as_f64()? as f32,+ a.get(1)?.as_f64()? as f32,+ a.get(2)?.as_f64()? as f32,+ a.get(3).and_then(Value::as_f64).unwrap_or(1.0) as f32,+ ];+ // a finite f64 such as 1e300 saturates to an infinite f32, which a saved project cannot reload+ if !c.iter().all(|x| x.is_finite()) {+ return None;+ }+ ParamValue::Color(c)+ } _ => return None, }, ParamValue::Bool(_) => ParamValue::Bool(v.as_bool()?),