summaryrefslogtreecommitdiffstats
path: root/vendor/pest/src/parser_state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/pest/src/parser_state.rs')
-rw-r--r--vendor/pest/src/parser_state.rs132
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());