semantic/semantic_index/
extraction_budget.rs1use crate::parser::ParseBudget;
5
6#[derive(Debug, thiserror::Error)]
7pub enum ExtractionBudgetError {
8 #[error("semantic analysis interrupted")]
9 Interrupted,
10 #[error("semantic analysis extraction budget exceeded: {0}")]
11 Exceeded(&'static str),
12}
13
14pub(super) const MAX_SOURCE_BYTES: usize = 1 << 20;
15const MAX_NODES: usize = 32_768;
16const MAX_DEPTH: usize = 128;
17const MAX_SPAN_BYTES: usize = 16 << 20;
18const MAX_ANCESTOR_BYTES: usize = 16 << 20;
19
20pub(super) fn admit(
25 root: tree_sitter::Node<'_>,
26 budget: &ParseBudget,
27) -> Result<(), ExtractionBudgetError> {
28 let mut cursor = root.walk();
29 let mut depth = 0usize;
30 let mut nodes = 0usize;
31 let mut span_bytes = 0usize;
32 let mut ancestor_bytes = 0usize;
33 let mut parent_name_bytes = 0usize;
34 loop {
35 if budget.interrupted() {
36 return Err(ExtractionBudgetError::Interrupted);
37 }
38 let node = cursor.node();
39 nodes = nodes.saturating_add(1);
40 span_bytes = span_bytes.saturating_add(node.byte_range().len());
41 ancestor_bytes = ancestor_bytes.saturating_add(parent_name_bytes);
42 if nodes > MAX_NODES {
43 return Err(ExtractionBudgetError::Exceeded("AST node count"));
44 }
45 if depth > MAX_DEPTH {
46 return Err(ExtractionBudgetError::Exceeded("AST depth"));
47 }
48 if span_bytes > MAX_SPAN_BYTES {
49 return Err(ExtractionBudgetError::Exceeded("repeated subtree spans"));
50 }
51 if ancestor_bytes > MAX_ANCESTOR_BYTES {
52 return Err(ExtractionBudgetError::Exceeded("ancestor names"));
53 }
54 if cursor.goto_first_child() {
55 parent_name_bytes = parent_name_bytes.saturating_add(name_bytes(node));
56 depth += 1;
57 continue;
58 }
59 loop {
60 if cursor.goto_next_sibling() {
61 break;
62 }
63 if !cursor.goto_parent() {
64 return Ok(());
65 }
66 parent_name_bytes = parent_name_bytes.saturating_sub(name_bytes(cursor.node()));
67 depth -= 1;
68 }
69 }
70}
71
72fn name_bytes(node: tree_sitter::Node<'_>) -> usize {
73 ["name", "type", "receiver"]
76 .into_iter()
77 .filter_map(|field| node.child_by_field_name(field))
78 .fold(0usize, |total, child| {
79 total.saturating_add(child.byte_range().len())
80 })
81}