collective

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

diff --git a/crates/engine/src/clip_ops_tests.rs b/crates/engine/src/clip_ops_tests.rs--- a/crates/engine/src/clip_ops_tests.rs+++ b/crates/engine/src/clip_ops_tests.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();+}diff --git a/crates/engine/src/commands.rs b/crates/engine/src/commands.rs--- a/crates/engine/src/commands.rs+++ b/crates/engine/src/commands.rs@@ -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()?),