use datavalue::OwnedDataValue;
use crate::arena::DataValue;
use crate::node::CompiledNode;
use crate::{Error, Result};
pub(crate) const MAX_ACCUMULATOR_DEPTH: u32 = 1024;
const OVERSHOOT: u32 = 256;
pub(super) struct AccumulatorDepth<'a> {
body: &'a CompiledNode,
stride: Option<u32>,
since: u32,
prev: Option<(Container<'a>, u32)>,
}
#[derive(Clone, Copy)]
enum Container<'a> {
Array(&'a [DataValue<'a>]),
Object(&'a [(&'a str, DataValue<'a>)]),
}
impl<'a> Container<'a> {
#[inline]
fn of(value: &DataValue<'a>) -> Option<Self> {
match *value {
DataValue::Array(items) if !items.is_empty() => Some(Self::Array(items)),
DataValue::Object(pairs) if !pairs.is_empty() => Some(Self::Object(pairs)),
_ => None,
}
}
#[inline]
fn is(&self, value: &DataValue<'a>) -> bool {
match (*self, *value) {
(Self::Array(a), DataValue::Array(b)) => std::ptr::eq(a, b),
(Self::Object(a), DataValue::Object(b)) => std::ptr::eq(a, b),
_ => false,
}
}
#[inline]
fn len(&self) -> usize {
match self {
Self::Array(items) => items.len(),
Self::Object(pairs) => pairs.len(),
}
}
#[inline]
fn child(&self, at: usize) -> &'a DataValue<'a> {
match *self {
Self::Array(items) => &items[at],
Self::Object(pairs) => &pairs[at].1,
}
}
}
impl<'a> AccumulatorDepth<'a> {
pub(super) fn new(body: &'a CompiledNode) -> Self {
Self {
body,
stride: None,
since: 0,
prev: None,
}
}
#[inline]
pub(super) fn step(&mut self, acc: &DataValue<'a>) -> Result<()> {
if Container::of(acc).is_none() {
self.prev = None;
self.since = 0;
return Ok(());
}
self.since += 1;
let stride = *self.stride.get_or_insert_with(|| match growth(self.body) {
Some(0) => OVERSHOOT,
Some(g) => (OVERSHOOT / g).max(1),
None => 1,
});
if self.since >= stride {
self.measure(acc)?;
}
Ok(())
}
#[inline]
pub(super) fn finish(&mut self, acc: &DataValue<'a>) -> Result<()> {
if self.since > 0 {
self.measure(acc)?;
}
Ok(())
}
#[cold]
fn measure(&mut self, acc: &DataValue<'a>) -> Result<()> {
self.since = 0;
let Some(top) = Container::of(acc) else {
self.prev = None;
return Ok(());
};
if let Some((prev, _)) = self.prev
&& prev.is(acc)
{
return Ok(());
}
match self.bound(top) {
Some(depth) => {
self.prev = Some((top, depth));
Ok(())
}
None => Err(Error::invalid_arguments(format!(
"reduce: the accumulator nests deeper than {MAX_ACCUMULATOR_DEPTH} levels"
))),
}
}
fn bound(&self, top: Container<'a>) -> Option<u32> {
let mut deepest = 0;
let mut first_new = 0;
if let (Container::Array(items), Some((Container::Array(prev_items), depth))) =
(top, self.prev)
{
for (item, prev_item) in items.iter().zip(prev_items) {
if is_container(item) {
if !same_slice(item, prev_item) {
break;
}
deepest = depth - 1;
}
first_new += 1;
}
}
for at in first_new..top.len() {
let child = top.child(at);
if !is_container(child) {
continue;
}
let depth = match self.prev {
Some((prev, depth)) if prev.is(child) => depth,
Some((prev, depth)) if at < prev.len() && same_slice(prev.child(at), child) => {
depth - 1
}
_ => self.walk(child, 1)?,
};
deepest = deepest.max(depth);
if deepest >= MAX_ACCUMULATOR_DEPTH {
return None;
}
}
Some(deepest + 1)
}
fn walk(&self, value: &DataValue<'a>, level: u32) -> Option<u32> {
if level > MAX_ACCUMULATOR_DEPTH {
return None;
}
if let Some((prev, depth)) = self.prev
&& prev.is(value)
{
return Some(depth);
}
let mut deepest = 0;
match value {
DataValue::Array(items) => {
for item in *items {
if is_container(item) {
deepest = deepest.max(self.walk(item, level + 1)?);
}
}
}
DataValue::Object(pairs) => {
for (_, item) in *pairs {
if is_container(item) {
deepest = deepest.max(self.walk(item, level + 1)?);
}
}
}
_ => return Some(0),
}
Some(deepest + 1)
}
}
pub(super) fn growth(node: &CompiledNode) -> Option<u32> {
let own: u32 = match node {
CompiledNode::Value { value, .. } => return Some(literal_depth(value)),
CompiledNode::CustomOperator(_) => return None,
CompiledNode::Array { .. } => 1,
#[cfg(feature = "templating")]
CompiledNode::StructuredObject(_) => 1,
#[cfg(feature = "error-handling")]
CompiledNode::Throw(data) => return Some(literal_depth(&data.error) + 1),
CompiledNode::Cse(_) => 0,
_ => 2,
};
let mut total = own;
let mut bounded = true;
node.visit_indexed_children(&mut |_, child| match growth(child) {
Some(g) => total = total.saturating_add(g),
None => bounded = false,
});
bounded.then_some(total)
}
fn literal_depth(value: &OwnedDataValue) -> u32 {
match value {
OwnedDataValue::Array(items) => 1 + items.iter().map(literal_depth).max().unwrap_or(0),
OwnedDataValue::Object(pairs) => {
1 + pairs
.iter()
.map(|(_, v)| literal_depth(v))
.max()
.unwrap_or(0)
}
_ => 0,
}
}
#[inline]
fn is_container(value: &DataValue<'_>) -> bool {
matches!(value, DataValue::Array(_) | DataValue::Object(_))
}
#[inline]
fn same_slice(a: &DataValue<'_>, b: &DataValue<'_>) -> bool {
match (*a, *b) {
(DataValue::Array(a), DataValue::Array(b)) => std::ptr::eq(a, b),
(DataValue::Object(a), DataValue::Object(b)) => std::ptr::eq(a, b),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn body() -> CompiledNode {
CompiledNode::synthetic_value(OwnedDataValue::Null)
}
fn nested<'a>(arena: &'a bumpalo::Bump, levels: u32) -> &'a DataValue<'a> {
let mut v: &DataValue<'_> = arena.alloc(DataValue::Null);
for _ in 0..levels {
v = arena.alloc(DataValue::Array(arena.alloc_slice_copy(&[*v])));
}
v
}
#[test]
fn scalars_and_shallow_values_pass() {
let arena = bumpalo::Bump::new();
let body = CompiledNode::synthetic_value(OwnedDataValue::Null);
let mut d = AccumulatorDepth::new(&body);
assert!(d.step(&DataValue::Null).is_ok());
assert!(
d.stride.is_none(),
"nothing worked out for a scalar accumulator"
);
assert!(d.measure(nested(&arena, 3)).is_ok());
}
#[test]
fn the_cap_is_inclusive() {
let arena = bumpalo::Bump::new();
assert!(
AccumulatorDepth::new(&body())
.measure(nested(&arena, MAX_ACCUMULATOR_DEPTH))
.is_ok()
);
assert!(
AccumulatorDepth::new(&body())
.measure(nested(&arena, MAX_ACCUMULATOR_DEPTH + 1))
.is_err()
);
}
#[test]
fn a_remembered_part_still_counts_toward_the_cap() {
let arena = bumpalo::Bump::new();
let body = body();
let mut d = AccumulatorDepth::new(&body);
let inner = nested(&arena, MAX_ACCUMULATOR_DEPTH);
assert!(d.measure(inner).is_ok());
let outer: &DataValue<'_> =
arena.alloc(DataValue::Array(arena.alloc_slice_copy(&[*inner])));
assert!(d.measure(outer).is_err());
}
#[test]
fn an_empty_container_at_the_cap_counts() {
let arena = bumpalo::Bump::new();
let mut v: &DataValue<'_> = arena.alloc(DataValue::Array(&[]));
for _ in 0..MAX_ACCUMULATOR_DEPTH - 1 {
v = arena.alloc(DataValue::Array(arena.alloc_slice_copy(&[*v])));
}
assert!(AccumulatorDepth::new(&body()).measure(v).is_ok());
let v: &DataValue<'_> = arena.alloc(DataValue::Array(arena.alloc_slice_copy(&[*v])));
assert!(AccumulatorDepth::new(&body()).measure(v).is_err());
}
#[test]
fn far_deeper_values_error_without_deep_recursion() {
let arena = bumpalo::Bump::new();
assert!(
AccumulatorDepth::new(&body())
.measure(nested(&arena, 200_000))
.is_err()
);
}
}