feat(analysis): stage bounded JSON codec and iterative expression ownership

This commit is contained in:
Codex
2026-09-11 09:14:16 -07:00
parent cdebb49d89
commit 847b1caa7e
11 changed files with 1971 additions and 110 deletions
@@ -0,0 +1,377 @@
//! Ordinary expression ownership never consumes stack proportional to tree depth.
//! This also covers a partially decoded expression and rejected programmatic input.
use super::Expr;
use std::{fmt, mem};
impl Expr {
fn clone_shell(&self) -> Self {
let leaf = || Box::new(Self::Boolean(false));
match self {
Self::Boolean(value) => Self::Boolean(*value),
Self::Number(value) => Self::Number(value.clone()),
Self::Quantity { magnitude, unit } => Self::Quantity {
magnitude: magnitude.clone(),
unit: *unit,
},
Self::Property(id) => Self::Property(*id),
Self::Parameter(id) => Self::Parameter(*id),
Self::List(values) => {
Self::List((0..values.len()).map(|_| Self::Boolean(false)).collect())
}
Self::Unary { op, .. } => Self::Unary {
op: *op,
operand: leaf(),
},
Self::Binary { op, .. } => Self::Binary {
op: *op,
left: leaf(),
right: leaf(),
},
Self::If { .. } => Self::If {
condition: leaf(),
when_true: leaf(),
when_false: leaf(),
},
Self::Call {
definition,
arguments,
} => Self::Call {
definition: *definition,
arguments: arguments
.keys()
.map(|id| (*id, Self::Boolean(false)))
.collect(),
},
Self::Sum { .. } => Self::Sum {
values: leaf(),
initial: leaf(),
},
Self::Power { exponent, .. } => Self::Power {
base: leaf(),
exponent: *exponent,
},
Self::AsKind { kind, .. } => Self::AsKind {
kind: *kind,
value: leaf(),
},
}
}
fn detach_children(&mut self, pending: &mut Vec<Self>) {
let mut take = |child: &mut Box<Self>| {
pending.push(mem::replace(child.as_mut(), Self::Boolean(false)));
};
match self {
Self::Unary { operand, .. } => take(operand),
Self::Binary { left, right, .. } => {
take(left);
take(right);
}
Self::If {
condition,
when_true,
when_false,
} => {
take(condition);
take(when_true);
take(when_false);
}
Self::Sum { values, initial } => {
take(values);
take(initial);
}
Self::Power { base, .. } => take(base),
Self::AsKind { value, .. } => take(value),
Self::List(values) => pending.append(values),
Self::Call { arguments, .. } => {
pending.extend(mem::take(arguments).into_values());
}
Self::Boolean(_)
| Self::Number(_)
| Self::Quantity { .. }
| Self::Property(_)
| Self::Parameter(_) => {}
}
}
}
impl Drop for Expr {
fn drop(&mut self) {
let mut pending = Vec::new();
self.detach_children(&mut pending);
while let Some(mut node) = pending.pop() {
node.detach_children(&mut pending);
// node now owns only leaves/empty collections. Its ordinary drop
// reenters this method at most once, independently of original depth.
}
}
}
impl Clone for Expr {
fn clone(&self) -> Self {
let mut result = Self::Boolean(false);
let mut pending = vec![(self, &mut result)];
while let Some((source, destination)) = pending.pop() {
*destination = source.clone_shell();
match (source, destination) {
(Self::List(a), Self::List(b)) => pending.extend(a.iter().zip(b.iter_mut())),
(Self::Unary { operand: a, .. }, Self::Unary { operand: b, .. })
| (Self::Power { base: a, .. }, Self::Power { base: b, .. })
| (Self::AsKind { value: a, .. }, Self::AsKind { value: b, .. }) => {
pending.push((a, b));
}
(
Self::Binary {
left: a, right: b, ..
},
Self::Binary {
left: c, right: d, ..
},
)
| (
Self::Sum {
values: a,
initial: b,
},
Self::Sum {
values: c,
initial: d,
},
) => {
pending.push((a, c));
pending.push((b, d));
}
(
Self::If {
condition: a,
when_true: b,
when_false: c,
},
Self::If {
condition: d,
when_true: e,
when_false: f,
},
) => {
pending.push((a, d));
pending.push((b, e));
pending.push((c, f));
}
(Self::Call { arguments: a, .. }, Self::Call { arguments: b, .. }) => {
pending.extend(a.values().zip(b.values_mut()));
}
_ => {}
}
}
result
}
}
impl PartialEq for Expr {
fn eq(&self, other: &Self) -> bool {
let mut pending = vec![(self, other)];
while let Some((a, b)) = pending.pop() {
match (a, b) {
(Self::Boolean(a), Self::Boolean(b)) if a == b => {}
(Self::Number(a), Self::Number(b)) if a == b => {}
(
Self::Quantity {
magnitude: a,
unit: b,
},
Self::Quantity {
magnitude: c,
unit: d,
},
) if a == c && b == d => {}
(Self::Property(a), Self::Property(b)) if a == b => {}
(Self::Parameter(a), Self::Parameter(b)) if a == b => {}
(Self::List(a), Self::List(b)) if a.len() == b.len() => {
pending.extend(a.iter().zip(b.iter()));
}
(Self::Unary { op: a, operand: b }, Self::Unary { op: c, operand: d })
if a == c =>
{
pending.push((b, d));
}
(
Self::Binary {
op: a,
left: b,
right: c,
},
Self::Binary {
op: d,
left: e,
right: f,
},
) if a == d => {
pending.push((b, e));
pending.push((c, f));
}
(
Self::If {
condition: a,
when_true: b,
when_false: c,
},
Self::If {
condition: d,
when_true: e,
when_false: f,
},
) => {
pending.push((a, d));
pending.push((b, e));
pending.push((c, f));
}
(
Self::Call {
definition: a,
arguments: b,
},
Self::Call {
definition: c,
arguments: d,
},
) if a == c && b.len() == d.len() => {
for ((key_a, value_a), (key_b, value_b)) in b.iter().zip(d.iter()) {
if key_a != key_b {
return false;
}
pending.push((value_a, value_b));
}
}
(
Self::Sum {
values: a,
initial: b,
},
Self::Sum {
values: c,
initial: d,
},
) => {
pending.push((a, c));
pending.push((b, d));
}
(
Self::Power {
base: a,
exponent: b,
},
Self::Power {
base: c,
exponent: d,
},
) if b == d => {
pending.push((a, c));
}
(Self::AsKind { kind: a, value: b }, Self::AsKind { kind: c, value: d })
if a == c =>
{
pending.push((b, d));
}
_ => return false,
}
}
true
}
}
impl Eq for Expr {}
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
enum Task<'a> {
Node(&'a Expr),
Text(&'static str),
Scalar(&'a dyn fmt::Debug),
}
use Task::*;
let mut pending = vec![Node(self)];
while let Some(task) = pending.pop() {
match task {
Text(text) => f.write_str(text)?,
Scalar(value) => write!(f, "{value:?}")?,
Node(node) => match node {
Self::Boolean(value) => write!(f, "Boolean({value:?})")?,
Self::Number(value) => write!(f, "Number({value:?})")?,
Self::Quantity { magnitude, unit } => {
write!(f, "Quantity {{ magnitude: {magnitude:?}, unit: {unit:?} }}")?;
}
Self::Property(id) => write!(f, "Property({id:?})")?,
Self::Parameter(id) => write!(f, "Parameter({id:?})")?,
Self::List(values) => {
f.write_str("List([")?;
pending.push(Text("])"));
for (index, value) in values.iter().enumerate().rev() {
pending.push(Node(value));
if index > 0 {
pending.push(Text(", "));
}
}
}
Self::Unary { op, operand } => {
write!(f, "Unary {{ op: {op:?}, operand: ")?;
pending.extend([Text(" }"), Node(operand)]);
}
Self::Binary { op, left, right } => {
write!(f, "Binary {{ op: {op:?}, left: ")?;
pending.extend([Text(" }"), Node(right), Text(", right: "), Node(left)]);
}
Self::If {
condition,
when_true,
when_false,
} => {
f.write_str("If { condition: ")?;
pending.extend([
Text(" }"),
Node(when_false),
Text(", when_false: "),
Node(when_true),
Text(", when_true: "),
Node(condition),
]);
}
Self::Call {
definition,
arguments,
} => {
write!(f, "Call {{ definition: {definition:?}, arguments: {{")?;
pending.push(Text("} }"));
for (index, (id, value)) in arguments.iter().enumerate().rev() {
pending.extend([Node(value), Text(": "), Scalar(id)]);
if index > 0 {
pending.push(Text(", "));
}
}
}
Self::Sum { values, initial } => {
f.write_str("Sum { values: ")?;
pending.extend([
Text(" }"),
Node(initial),
Text(", initial: "),
Node(values),
]);
}
Self::Power { base, exponent } => {
f.write_str("Power { base: ")?;
pending.extend([
Text(" }"),
Scalar(exponent),
Text(", exponent: "),
Node(base),
]);
}
Self::AsKind { kind, value } => {
write!(f, "AsKind {{ kind: {kind:?}, value: ")?;
pending.extend([Text(" }"), Node(value)]);
}
},
}
}
Ok(())
}
}
@@ -0,0 +1,261 @@
//! The existing externally tagged Serde schema, with admission at each Expr edge.
//! The private remote derive is the sole wire grammar: it still rejects unknown
//! fields/variants, duplicate fields and malformed UUIDs/Exact values.
use super::{
BinaryOp, DefinitionRef, Expr, MAX_CLOSURE_NODES, MAX_EXPRESSION_DEPTH, MAX_LIST_LENGTH,
ParameterId, UnaryOp,
};
use crate::{Id, analysis_numeric::Exact};
use serde::{
Deserialize, Serialize,
de::{DeserializeSeed, MapAccess, SeqAccess, Visitor},
};
use std::{cell::Cell, collections::BTreeMap, fmt};
pub(crate) const MAX_JSON_EXPRESSION_NODES: usize = 262_144;
// These are requested segment capacities, not an allocator or RSS guarantee.
const RED_ZONE: usize = 64 * 1024;
const STACK_SEGMENT: usize = 512 * 1024;
pub(crate) fn with_stack<R>(operation: impl FnOnce() -> R) -> R {
stacker::maybe_grow(RED_ZONE, STACK_SEGMENT, operation)
}
#[derive(Clone, Copy)]
struct Meter {
active: bool,
depth: usize,
nodes: usize,
tree_nodes: usize,
maximum: usize,
}
impl Meter {
const DEFAULT: Self = Self {
active: false,
depth: 0,
nodes: 0,
tree_nodes: 0,
maximum: MAX_JSON_EXPRESSION_NODES,
};
}
thread_local! {
static DECODING: Cell<Meter> = const { Cell::new(Meter::DEFAULT) };
static ENCODING: Cell<Meter> = const { Cell::new(Meter::DEFAULT) };
}
type LocalMeter = &'static std::thread::LocalKey<Cell<Meter>>;
/// One codec operation owns the aggregate meter across all disjoint bindings.
/// Scope restoration also runs on errors and unwinding; no mutable TLS borrow is
/// held across Serde/user code or a stack switch.
pub(crate) struct Scope {
local: LocalMeter,
previous: Meter,
}
impl Scope {
pub(crate) fn decoding(maximum: usize) -> Self {
Self::new(&DECODING, maximum)
}
pub(crate) fn encoding(maximum: usize) -> Self {
Self::new(&ENCODING, maximum)
}
fn new(local: LocalMeter, maximum: usize) -> Self {
let next = Meter {
active: true,
maximum: maximum.min(MAX_JSON_EXPRESSION_NODES),
..Meter::DEFAULT
};
let previous = local.with(|meter| meter.replace(next));
Self { local, previous }
}
}
impl Drop for Scope {
fn drop(&mut self) {
self.local.with(|meter| meter.set(self.previous));
}
}
struct NodeGuard {
local: LocalMeter,
root: bool,
}
impl NodeGuard {
fn enter(local: LocalMeter) -> Result<Self, &'static str> {
local.with(|slot| {
let mut meter = slot.get();
let root = !meter.active;
if root {
meter = Meter {
active: true,
..Meter::DEFAULT
};
}
if meter.depth == 0 {
meter.tree_nodes = 0;
}
if meter.depth >= MAX_EXPRESSION_DEPTH {
return Err("Expression logical depth exceeds 128.");
}
if meter.nodes >= meter.maximum {
return Err("JSON expression allocation count exceeded.");
}
if meter.tree_nodes >= MAX_CLOSURE_NODES {
return Err("Stored expression node count exceeds 65536.");
}
meter.depth += 1;
meter.nodes += 1;
meter.tree_nodes += 1;
slot.set(meter);
Ok(Self { local, root })
})
}
}
impl Drop for NodeGuard {
fn drop(&mut self) {
self.local.with(|slot| {
let mut meter = slot.get();
meter.depth -= 1;
if self.root {
meter.active = false;
}
slot.set(meter);
});
}
}
impl<'de> Deserialize<'de> for Expr {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let _node = NodeGuard::enter(&DECODING).map_err(serde::de::Error::custom)?;
with_stack(|| WireExpr::deserialize(deserializer))
}
}
impl Serialize for Expr {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let _node = NodeGuard::enter(&ENCODING).map_err(serde::ser::Error::custom)?;
match self {
Self::List(values) if values.len() > MAX_LIST_LENGTH => {
return Err(serde::ser::Error::custom(
"Expression List exceeds 1024 items.",
));
}
Self::Call { arguments, .. } if arguments.len() > MAX_CLOSURE_NODES => {
return Err(serde::ser::Error::custom(
"Call argument count exceeds 65536.",
));
}
_ => {}
}
with_stack(|| WireExpr::serialize(self, serializer))
}
}
// A private remote derive avoids exposing an unguarded inherent deserialize on
// public Expr. Children remain Expr, so every recursive edge enters the guard.
#[derive(Serialize, Deserialize)]
#[serde(remote = "Expr", deny_unknown_fields)]
enum WireExpr {
Boolean(bool),
Number(Exact),
Quantity {
magnitude: Exact,
unit: DefinitionRef,
},
Property(Id),
Parameter(ParameterId),
List(#[serde(deserialize_with = "bounded_list")] Vec<Expr>),
Unary {
op: UnaryOp,
operand: Box<Expr>,
},
Binary {
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
},
If {
condition: Box<Expr>,
when_true: Box<Expr>,
when_false: Box<Expr>,
},
Call {
definition: DefinitionRef,
#[serde(deserialize_with = "bounded_arguments")]
arguments: BTreeMap<ParameterId, Expr>,
},
Sum {
values: Box<Expr>,
initial: Box<Expr>,
},
Power {
base: Box<Expr>,
exponent: i8,
},
AsKind {
kind: DefinitionRef,
value: Box<Expr>,
},
}
struct ListItem {
allowed: bool,
}
impl<'de> DeserializeSeed<'de> for ListItem {
type Value = Expr;
fn deserialize<D: serde::Deserializer<'de>>(self, deserializer: D) -> Result<Expr, D::Error> {
if !self.allowed {
return Err(serde::de::Error::custom(
"Expression List exceeds 1024 items.",
));
}
Expr::deserialize(deserializer)
}
}
fn bounded_list<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<Expr>, D::Error> {
struct ListVisitor;
impl<'de> Visitor<'de> for ListVisitor {
type Value = Vec<Expr>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("at most 1024 expression items")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
let mut values = Vec::new();
while let Some(value) = access.next_element_seed(ListItem {
allowed: values.len() < MAX_LIST_LENGTH,
})? {
values.push(value);
}
Ok(values)
}
}
deserializer.deserialize_seq(ListVisitor)
}
fn bounded_arguments<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<BTreeMap<ParameterId, Expr>, D::Error> {
struct Arguments;
impl<'de> Visitor<'de> for Arguments {
type Value = BTreeMap<ParameterId, Expr>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unique scoped parameter UUID keys")
}
fn visit_map<A: MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
let mut values = BTreeMap::new();
while let Some(id) = access.next_key::<ParameterId>()? {
if values.contains_key(&id) {
return Err(serde::de::Error::custom(format!(
"Duplicate parameter ID {}",
id.0
)));
}
if values.len() >= MAX_CLOSURE_NODES {
return Err(serde::de::Error::custom(
"Call argument count exceeds 65536.",
));
}
// Count/duplicate checks precede construction of the next subtree.
values.insert(id, access.next_value()?);
}
Ok(values)
}
}
deserializer.deserialize_map(Arguments)
}
-13
View File
@@ -114,8 +114,6 @@ pub enum BinaryOp {
And,
Or,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum Expr {
Boolean(bool),
Number(Exact),
@@ -142,7 +140,6 @@ pub enum Expr {
},
Call {
definition: DefinitionRef,
#[serde(deserialize_with = "unique_parameter_map")]
arguments: BTreeMap<ParameterId, Expr>,
},
Sum {
@@ -159,16 +156,6 @@ pub enum Expr {
},
}
fn unique_parameter_map<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<BTreeMap<ParameterId, Expr>, D::Error> {
crate::unique_id_map(deserializer).map(|map| {
map.into_iter()
.map(|(id, expression)| (ParameterId(id), expression))
.collect()
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AnalysisSubject {