diff options
Diffstat (limited to 'vendor/pest/src/parser_state.rs')
-rw-r--r-- | vendor/pest/src/parser_state.rs | 132 |
1 files changed, 105 insertions, 27 deletions
diff --git a/vendor/pest/src/parser_state.rs b/vendor/pest/src/parser_state.rs index 09bc92710..a4422e866 100644 --- a/vendor/pest/src/parser_state.rs +++ b/vendor/pest/src/parser_state.rs @@ -7,15 +7,21 @@ // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. -use std::ops::Range; -use std::rc::Rc; - -use error::{Error, ErrorVariant}; -use iterators::{pairs, QueueableToken}; -use position::{self, Position}; -use span::Span; -use stack::Stack; -use RuleType; +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::rc::Rc; +use alloc::vec; +use alloc::vec::Vec; +use core::num::NonZeroUsize; +use core::ops::Range; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use crate::error::{Error, ErrorVariant}; +use crate::iterators::{pairs, QueueableToken}; +use crate::position::{self, Position}; +use crate::span::Span; +use crate::stack::Stack; +use crate::RuleType; /// The current lookahead status of a [`ParserState`]. /// @@ -47,6 +53,49 @@ pub enum MatchDir { TopToBottom, } +static CALL_LIMIT: AtomicUsize = AtomicUsize::new(0); + +/// Sets the maximum call limit for the parser state +/// to prevent stack overflows or excessive execution times +/// in some grammars. +/// If set, the calls are tracked as a running total +/// over all non-terminal rules that can nest closures +/// (which are passed to transform the parser state). +/// +/// # Arguments +/// +/// * `limit` - The maximum number of calls. If None, +/// the number of calls is unlimited. +pub fn set_call_limit(limit: Option<NonZeroUsize>) { + CALL_LIMIT.store(limit.map(|f| f.get()).unwrap_or(0), Ordering::Relaxed); +} + +#[derive(Debug)] +struct CallLimitTracker { + current_call_limit: Option<(usize, usize)>, +} + +impl Default for CallLimitTracker { + fn default() -> Self { + let limit = CALL_LIMIT.load(Ordering::Relaxed); + let current_call_limit = if limit > 0 { Some((0, limit)) } else { None }; + Self { current_call_limit } + } +} + +impl CallLimitTracker { + fn limit_reached(&self) -> bool { + self.current_call_limit + .map_or(false, |(current, limit)| current >= limit) + } + + fn increment_depth(&mut self) { + if let Some((current, _)) = &mut self.current_call_limit { + *current += 1; + } + } +} + /// The complete state of a [`Parser`]. /// /// [`Parser`]: trait.Parser.html @@ -60,6 +109,7 @@ pub struct ParserState<'i, R: RuleType> { attempt_pos: usize, atomicity: Atomicity, stack: Stack<Span<'i>>, + call_tracker: CallLimitTracker, } /// Creates a `ParserState` from a `&str`, supplying it to a closure `f`. @@ -83,16 +133,23 @@ where Ok(pairs::new(Rc::new(state.queue), input, 0, len)) } Err(mut state) => { - state.pos_attempts.sort(); - state.pos_attempts.dedup(); - state.neg_attempts.sort(); - state.neg_attempts.dedup(); - - Err(Error::new_from_pos( + let variant = if state.reached_call_limit() { + ErrorVariant::CustomError { + message: "call limit reached".to_owned(), + } + } else { + state.pos_attempts.sort(); + state.pos_attempts.dedup(); + state.neg_attempts.sort(); + state.neg_attempts.dedup(); ErrorVariant::ParsingError { positives: state.pos_attempts.clone(), negatives: state.neg_attempts.clone(), - }, + } + }; + + Err(Error::new_from_pos( + variant, // TODO(performance): Guarantee state.attempt_pos is a valid position position::Position::new(input, state.attempt_pos).unwrap(), )) @@ -111,7 +168,6 @@ impl<'i, R: RuleType> ParserState<'i, R> { /// let input = ""; /// let state: Box<pest::ParserState<&str>> = pest::ParserState::new(input); /// ``` - #[allow(clippy::new_ret_no_self)] pub fn new(input: &'i str) -> Box<Self> { Box::new(ParserState { position: Position::from_start(input), @@ -122,6 +178,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { attempt_pos: 0, atomicity: Atomicity::NonAtomic, stack: Stack::new(), + call_tracker: Default::default(), }) } @@ -168,6 +225,20 @@ impl<'i, R: RuleType> ParserState<'i, R> { self.atomicity } + #[inline] + fn inc_call_check_limit(mut self: Box<Self>) -> ParseResult<Box<Self>> { + if self.call_tracker.limit_reached() { + return Err(self); + } + self.call_tracker.increment_depth(); + Ok(self) + } + + #[inline] + fn reached_call_limit(&self) -> bool { + self.call_tracker.limit_reached() + } + /// Wrapper needed to generate tokens. This will associate the `R` type rule to the closure /// meant to match the rule. /// @@ -193,6 +264,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { where F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>, { + self = self.inc_call_check_limit()?; let actual_pos = self.position.pos(); let index = self.queue.len(); @@ -357,12 +429,13 @@ impl<'i, R: RuleType> ParserState<'i, R> { /// assert_eq!(pairs.len(), 0); /// ``` #[inline] - pub fn sequence<F>(self: Box<Self>, f: F) -> ParseResult<Box<Self>> + pub fn sequence<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>> where F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>, { + self = self.inc_call_check_limit()?; let token_index = self.queue.len(); - let initial_pos = self.position.clone(); + let initial_pos = self.position; let result = f(self); @@ -406,10 +479,11 @@ impl<'i, R: RuleType> ParserState<'i, R> { /// assert_eq!(result.unwrap().position().pos(), 0); /// ``` #[inline] - pub fn repeat<F>(self: Box<Self>, mut f: F) -> ParseResult<Box<Self>> + pub fn repeat<F>(mut self: Box<Self>, mut f: F) -> ParseResult<Box<Self>> where F: FnMut(Box<Self>) -> ParseResult<Box<Self>>, { + self = self.inc_call_check_limit()?; let mut result = f(self); loop { @@ -447,10 +521,11 @@ impl<'i, R: RuleType> ParserState<'i, R> { /// assert!(result.is_ok()); /// ``` #[inline] - pub fn optional<F>(self: Box<Self>, f: F) -> ParseResult<Box<Self>> + pub fn optional<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>> where F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>, { + self = self.inc_call_check_limit()?; match f(self) { Ok(state) | Err(state) => Ok(state), } @@ -728,6 +803,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { where F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>, { + self = self.inc_call_check_limit()?; let initial_lookahead = self.lookahead; self.lookahead = if is_positive { @@ -742,7 +818,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { } }; - let initial_pos = self.position.clone(); + let initial_pos = self.position; let result = f(self.checkpoint()); @@ -795,6 +871,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { where F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>, { + self = self.inc_call_check_limit()?; let initial_atomicity = self.atomicity; let should_toggle = self.atomicity != atomicity; @@ -839,17 +916,18 @@ impl<'i, R: RuleType> ParserState<'i, R> { /// assert_eq!(result.unwrap().position().pos(), 1); /// ``` #[inline] - pub fn stack_push<F>(self: Box<Self>, f: F) -> ParseResult<Box<Self>> + pub fn stack_push<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>> where F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>, { - let start = self.position.clone(); + self = self.inc_call_check_limit()?; + let start = self.position; let result = f(self); match result { Ok(mut state) => { - let end = state.position.clone(); + let end = state.position; state.stack.push(start.span(&end)); Ok(state) } @@ -955,7 +1033,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { return Ok(self); } - let mut position = self.position.clone(); + let mut position = self.position; let result = { let mut iter_b2t = self.stack[range].iter(); let matcher = |span: &Span| position.match_string(span.as_str()); @@ -1016,7 +1094,7 @@ impl<'i, R: RuleType> ParserState<'i, R> { /// ``` #[inline] pub fn stack_match_pop(mut self: Box<Self>) -> ParseResult<Box<Self>> { - let mut position = self.position.clone(); + let mut position = self.position; let mut result = true; while let Some(span) = self.stack.pop() { result = position.match_string(span.as_str()); |