#[cfg(any(test, feature = "xml"))]
use std::cell::Cell;
pub const MAX_PARSE_DEPTH: usize = 128;
#[cfg(any(test, feature = "xml"))]
pub(crate) struct DepthGuard<'a>(&'a Cell<usize>);
#[cfg(any(test, feature = "xml"))]
impl<'a> DepthGuard<'a> {
pub(crate) fn enter(depth: &'a Cell<usize>) -> crate::Result<Self> {
let entered = depth.get().saturating_add(1);
depth.set(entered);
if entered > MAX_PARSE_DEPTH {
return Err(crate::Error::MaxDepthExceeded);
}
Ok(Self(depth))
}
}
#[cfg(any(test, feature = "xml"))]
impl Drop for DepthGuard<'_> {
fn drop(&mut self) {
self.0.set(self.0.get().saturating_sub(1));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
fn descend(depth: &Cell<usize>, frames: usize) -> crate::Result<()> {
let _guard = DepthGuard::enter(depth)?;
if frames > 1 {
descend(depth, frames - 1)?;
}
Ok(())
}
#[test]
fn cap_is_one_twenty_eight() {
assert_eq!(MAX_PARSE_DEPTH, 128);
}
#[test]
fn enter_succeeds_up_to_the_cap_and_restores_on_drop() {
let depth = Cell::new(0);
assert!(descend(&depth, MAX_PARSE_DEPTH).is_ok());
assert_eq!(depth.get(), 0);
}
#[test]
fn enter_fails_beyond_the_cap() {
let depth = Cell::new(0);
let result = descend(&depth, MAX_PARSE_DEPTH + 1);
assert!(matches!(result, Err(Error::MaxDepthExceeded)));
}
#[test]
fn early_error_unwinds_only_constructed_guards() {
let depth = Cell::new(0);
{
let _outer = DepthGuard::enter(&depth);
}
assert_eq!(depth.get(), 0);
let depth = Cell::new(MAX_PARSE_DEPTH);
assert!(matches!(
DepthGuard::enter(&depth),
Err(Error::MaxDepthExceeded)
));
assert_eq!(depth.get(), MAX_PARSE_DEPTH + 1);
}
}