Counts complexity metrics by iterating over all descendants of `node`. Uses an explicit stack instead of recursion (NASA Power of 10, Rule 1). The nesting depth tracks how many nesting-type ancestors enclose each node. `source` is needed to extract method/macro names for unchecked-call and assertion detection. Pass an empty slice to skip name-based matching.
(
node: TsNode<'_>,
config: &ComplexityConfig,
source: &[u8],
)
| 60 | /// `source` is needed to extract method/macro names for unchecked-call and |
| 61 | /// assertion detection. Pass an empty slice to skip name-based matching. |
| 62 | pub fn count_complexity( |
| 63 | node: TsNode<'_>, |
| 64 | config: &ComplexityConfig, |
| 65 | source: &[u8], |
| 66 | ) -> ComplexityMetrics { |
| 67 | const MAX_ITERATIONS: usize = 500_000; |
| 68 | debug_assert!( |
| 69 | !config.branch_types.is_empty() || !config.loop_types.is_empty(), |
| 70 | "count_complexity called with config that has no branch or loop types" |
| 71 | ); |
| 72 | debug_assert!( |
| 73 | node.child_count() > 0, |
| 74 | "count_complexity called on a node with no children" |
| 75 | ); |
| 76 | let mut metrics = ComplexityMetrics::default(); |
| 77 | |
| 78 | // Stack: (tree-sitter node, current nesting depth) |
| 79 | let mut stack: Vec<(TsNode<'_>, u32)> = Vec::new(); |
| 80 | |
| 81 | // Seed with direct children of the function node. Earlier revisions used |
| 82 | // `node.child(i)` in a `for i in 0..N` loop — tree-sitter's `child(i)` |
| 83 | // is O(i) because it walks sibling links from the first child, so the |
| 84 | // seed loop alone was O(N²) for high-fanout nodes (e.g. the giant |
| 85 | // `switch` in `kernel/bpf/verifier.c` with thousands of cases). Use a |
| 86 | // cursor for O(1) per step. |
| 87 | push_children(&mut stack, node, 0); |
| 88 | |
| 89 | let mut iterations: usize = 0; |
| 90 | |
| 91 | while let Some((current, depth)) = stack.pop() { |
| 92 | iterations += 1; |
| 93 | if iterations >= MAX_ITERATIONS { |
| 94 | break; |
| 95 | } |
| 96 | |
| 97 | let kind = current.kind(); |
| 98 | |
| 99 | // Classify the node. |
| 100 | if config.branch_types.contains(&kind) { |
| 101 | metrics.branches += 1; |
| 102 | } |
| 103 | if config.loop_types.contains(&kind) { |
| 104 | metrics.loops += 1; |
| 105 | } |
| 106 | if config.return_types.contains(&kind) { |
| 107 | metrics.returns += 1; |
| 108 | } |
| 109 | |
| 110 | // Unsafe blocks. |
| 111 | if config.unsafe_types.contains(&kind) { |
| 112 | metrics.unsafe_blocks += 1; |
| 113 | } |
| 114 | |
| 115 | // Unchecked operator types (e.g. non_null_assertion_expression, `!!`). |
| 116 | if config.unchecked_types.contains(&kind) { |
| 117 | metrics.unchecked_calls += 1; |
| 118 | } |
| 119 |