From 93cadc8994b8aaf0870e00db0f92f5be33ff6b13 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 11 Oct 2026 01:48:17 +0700 Subject: [PATCH] Review #997: one anchor-reference parser, for Reshape area type too `text.reshapeArea` read its anchors with the lenient `parse_refs`, so a malformed reference among good ones moved only the good ones. It now uses `checked_refs` as well, and `parse_refs` (no other user) goes. Test: a mixed list fails without moving anything. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/select.rs | 12 +++--------- crates/engine/src/cmd/typecmd.rs | 2 +- crates/engine/src/tests_typearea.rs | 3 +++ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/engine/src/cmd/select.rs b/crates/engine/src/cmd/select.rs index 6c2a3437..319a7b33 100644 --- a/crates/engine/src/cmd/select.rs +++ b/crates/engine/src/cmd/select.rs @@ -294,15 +294,9 @@ fn key(s: &mut Session, p: &Value) -> Result { ok() } -/// `[[subpath, anchor]…]` anchor references (malformed entries are left out). -pub(crate) fn parse_refs(v: Option<&Value>) -> Vec<(usize, usize)> { - v.and_then(Value::as_array) - .map(|a| a.iter().filter_map(|x| Some((x.get(0)?.as_u64()? as usize, x.get(1)?.as_u64()? as usize))).collect()) - .unwrap_or_default() -} - -/// Parse an explicitly supplied anchor list without dropping invalid entries. -fn checked_refs(v: Option<&Value>, cmd: &str) -> Result> { +/// `[[subpath, anchor]…]` anchor references of `cmd`: a malformed entry fails rather than being +/// left out, which would act on other anchors than the ones given. +pub(crate) fn checked_refs(v: Option<&Value>, cmd: &str) -> Result> { let refs = v.and_then(Value::as_array).ok_or_else(|| bad(cmd, "`anchors` must be an array of [subpath, anchor] pairs"))?; refs.iter() .map(|v| { diff --git a/crates/engine/src/cmd/typecmd.rs b/crates/engine/src/cmd/typecmd.rs index 2395b3f5..5692b6ad 100644 --- a/crates/engine/src/cmd/typecmd.rs +++ b/crates/engine/src/cmd/typecmd.rs @@ -628,7 +628,7 @@ fn area_options(s: &mut Session, p: &Value) -> Result { fn reshape_area(s: &mut Session, p: &Value) -> Result { const C: &str = "text.reshapeArea"; let id = id_param(p, "id").ok_or_else(|| bad(C, "missing id"))?; - let mut refs = super::select::parse_refs(p.get("anchors")); + let mut refs = super::select::checked_refs(p.get("anchors"), C)?; refs.sort_unstable(); refs.dedup(); if refs.is_empty() { diff --git a/crates/engine/src/tests_typearea.rs b/crates/engine/src/tests_typearea.rs index 946fcd88..9c59eac5 100644 --- a/crates/engine/src/tests_typearea.rs +++ b/crates/engine/src/tests_typearea.rs @@ -236,6 +236,9 @@ fn reshape_area_takes_untrusted_params() { json!({"id": id.0, "dx": 1, "dy": 1}), json!({"id": id.0, "anchors": [], "dx": 1, "dy": 1}), json!({"id": id.0, "anchors": [["a", 1], [0], "x"], "dx": 1, "dy": 1}), + // A malformed reference among good ones fails rather than moving only the good ones. + json!({"id": id.0, "anchors": [[0, 2], [0, "3"]], "dx": 1, "dy": 1}), + json!({"id": id.0, "anchors": [[0, 2], [0, 3, 1]], "dx": 1, "dy": 1}), json!({"id": id.0, "anchors": [[0, 9]], "dx": 1, "dy": 1}), json!({"id": id.0, "anchors": [[0, 1], [7, 0]], "dx": 1, "dy": 1}), json!({"id": id.0, "anchors": [[0, 2]], "dy": 1}),