Reject legacy workspace stacks beyond the layout depth limit

This commit is contained in:
dotCooCoo
2026-10-27 07:44:59 -07:00
parent 01c904b041
commit 9dbcd99e80
2 changed files with 43 additions and 2 deletions
+8 -2
View File
@@ -25,6 +25,9 @@ use super::{Snapshot, VERSION};
use crate::dock::{Layout, PanelKind, Workspace};
use crate::state::UiState;
#[cfg(test)]
mod tests;
#[derive(Deserialize)]
enum OldSplitSize {
Ratio(f32),
@@ -166,7 +169,7 @@ fn node(old: OldNode, depth: usize) -> Result<Node<PanelKind>, String> {
first: Box::new(node(*a, depth.saturating_add(1))?),
second: Box::new(node(*b, depth.saturating_add(1))?),
},
OldNode::Stack { entries } => column(entries)?,
OldNode::Stack { entries } => column(entries, depth)?,
})
}
@@ -183,7 +186,7 @@ struct Frame {
}
/// A column of stacked frames as tab groups split top to bottom (see the module docs).
fn column(entries: Vec<OldStackEntry>) -> Result<Node<PanelKind>, String> {
fn column(entries: Vec<OldStackEntry>, depth: usize) -> Result<Node<PanelKind>, String> {
let mut frames: Vec<Frame> = Vec::new();
// Tabs of closed frames above the first open one.
let mut waiting: Vec<PanelKind> = Vec::new();
@@ -199,6 +202,9 @@ fn column(entries: Vec<OldStackEntry>) -> Result<Node<PanelKind>, String> {
}
let active = entry.active.min(panels.len().saturating_sub(1));
if entry.open {
if depth.saturating_add(frames.len()) > craft_ui::docking::MAX_DEPTH {
return Err("workspace dock tree exceeds supported limits".into());
}
let height = entry.height.filter(|h| h.is_finite() && *h > 0.0).map(|h| h.min(MAX_FRAME));
frames.push(Frame { panels, active, height });
} else if let Some(above) = frames.last_mut() {
@@ -0,0 +1,35 @@
use super::*;
fn stack(count: usize) -> OldNode {
OldNode::Stack {
entries: (0..count).map(|_| OldStackEntry { panel: Some(PanelKind::Info), panels: Vec::new(), active: 0, open: true, height: None }).collect(),
}
}
#[test]
fn stacked_frames_respect_the_remaining_tree_depth() {
let limit = craft_ui::docking::MAX_DEPTH;
assert!(node(stack(limit + 1), 0).is_ok());
assert!(node(stack(limit + 2), 0).is_err());
assert!(node(stack(1), limit).is_ok());
assert!(node(stack(2), limit).is_err());
assert!(node(stack(limit), 2).is_err());
}
#[test]
fn oversized_legacy_stack_load_does_not_abort() {
const CHILD: &str = "EFFECTCRAFT_TEST_LEGACY_STACK_CHILD";
if std::env::var_os(CHILD).is_some() {
let entry = serde_json::json!({"panel": "Info", "open": true});
let value = serde_json::json!({"workspaces": {"Oversized": {"Stack": {"entries": vec![entry; 20_000]}}}});
assert!(serde_json::to_vec(&value).unwrap().len() < super::super::MAX_BYTES);
assert!(migrate(value, &UiState::default()).is_err());
return;
}
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", "workspace_state::legacy::tests::oversized_legacy_stack_load_does_not_abort", "--nocapture"])
.env(CHILD, "1")
.output()
.unwrap();
assert!(output.status.success(), "{}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
}