//! The nesting guard: whether a script nests deeper than boa's parser can take. //! //! boa parses by recursive descent with no depth limit of its own, and a stack overflow aborts //! the process (it is not a panic, so `catch_unwind` can't stop it). So every script is scanned //! before boa sees it, and one that nests too deeply is refused. //! //! A scan has to know which brackets are code, and that depends on where strings, templates, //! comments and regular expressions begin and end. Most of that follows from the bytes, but a //! `/` that isn't a comment is a division or the start of a regular expression depending on //! where the parser is, which a scan can't know. A scan that guesses can be put out of phase: //! after `/'/`, the quote in the regular expression made the real nesting that followed look //! like string text (#5079). So the scan doesn't guess. It follows every reading at once, //! splitting at each such `/` into a division and a regular expression, and counts nesting on //! each reading. boa's reading is always one of them, so the deepest is at least as deep as //! boa's. A reading boa would reject is dropped at the byte where boa stops with a syntax error //! (a string or regular expression running into a line break, a regular expression's groups not //! matching), which keeps divisions from leaving stray readings behind. /// Deepest nesting of `(`, `[`, `{` and template substitutions (`${`). pub(crate) const MAX_BRACKET_DEPTH: usize = 64; /// Longest run of prefix operators (`!`, `~`, `+`, `-`). pub(crate) const MAX_PREFIX_RUN: usize = 64; /// Most `?` (conditional expressions nest without brackets). pub(crate) const MAX_CONDITIONALS: usize = 4096; /// Most readings followed at once. Readings that differ only in their counts are merged first, /// keeping the larger counts; past this, the script is refused. const MAX_READINGS: usize = 16; /// Where a reading is in the script. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] enum Mode { Code, /// In a string that this quote closes. Str(u8), /// In a template's text: after its backtick, or after a substitution's closing `}`. Template, /// In a regular expression literal: in a class (`[...]`), and the groups open outside one. Regex { class: bool, groups: usize, }, /// In a `//`, `` comment, to the end of the line. LineComment, /// In a `/* */` comment. BlockComment, } /// One way of reading the script, and what it has counted so far. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] struct Reading { mode: Mode, /// Bytes to pass over: the rest of a two-byte token, or what a backslash escapes. skip: usize, /// For each open template substitution, the braces open inside it, so the `}` that closes /// the substitution is told from one that closes a block. substitutions: Vec, depth: usize, prefix: usize, conditionals: usize, } impl Reading { fn in_code() -> Self { Self { mode: Mode::Code, skip: 0, substitutions: Vec::new(), depth: 0, prefix: 0, conditionals: 0 } } fn with_mode(&self, mode: Mode) -> Self { Self { mode, ..self.clone() } } fn over_limit(&self) -> bool { self.depth > MAX_BRACKET_DEPTH || self.prefix > MAX_PREFIX_RUN || self.conditionals > MAX_CONDITIONALS } fn open(&mut self) { self.depth = self.depth.saturating_add(1); self.prefix = 0; } fn close(&mut self) { self.depth = self.depth.saturating_sub(1); self.prefix = 0; } /// Read byte `i` of `b`. A second reading that splits off here goes to `forks`. Returns /// false when boa would stop with a syntax error at this byte, ending this reading. fn advance(&mut self, b: &[u8], i: usize, forks: &mut Vec) -> bool { if self.skip > 0 { self.skip -= 1; return true; } let Some(&c) = b.get(i) else { return true }; match self.mode { Mode::Code => { self.code(c, b, i, forks); true } Mode::Str(quote) => self.string(quote, c, b, i), Mode::Template => { self.template(c, b, i); true } Mode::Regex { class, groups } => self.regex(class, groups, c, b, i), Mode::LineComment => { let n = line_break(b, i); if n > 0 { self.mode = Mode::Code; self.skip = n - 1; } true } Mode::BlockComment => { if c == b'*' && at(b, i + 1, b"/") { self.mode = Mode::Code; self.skip = 1; } true } } } fn code(&mut self, c: u8, b: &[u8], i: usize, forks: &mut Vec) { match c { b'\'' | b'"' => { self.mode = Mode::Str(c); self.prefix = 0; } b'`' => { self.mode = Mode::Template; self.prefix = 0; } b'/' if at(b, i + 1, b"/") => self.mode = Mode::LineComment, b'/' if at(b, i + 1, b"*") => { self.mode = Mode::BlockComment; self.skip = 1; } b'/' => { // A division, or the start of a regular expression: the parser decides, so // follow both. forks.push(self.with_mode(Mode::Regex { class: false, groups: 0 })); self.prefix = 0; } b'(' | b'[' => self.open(), b'{' => { if let Some(open) = self.substitutions.last_mut() { *open = open.saturating_add(1); } self.open(); } b')' | b']' => self.close(), b'}' => { self.close(); if self.substitutions.last() == Some(&0) { self.substitutions.pop(); self.mode = Mode::Template; } else if let Some(open) = self.substitutions.last_mut() { *open = open.saturating_sub(1); } } b'!' | b'~' | b'+' | b'-' => { // `-->` begins a comment at the start of a line (Annex B); followed anywhere. if c == b'-' && at(b, i + 1, b"->") { forks.push(self.with_mode(Mode::LineComment)); } self.prefix = self.prefix.saturating_add(1); } b'?' => { self.conditionals = self.conditionals.saturating_add(1); self.prefix = 0; } // `