fix/audit-findings #5
1 changed files with 57 additions and 20 deletions
perf(source): record parents during traversal instead of re-walking from the root
tree-sitter's `Node::parent` re-descends from the root, so each call is O(depth), and `enclosing_function`, `lexical_scope` and `parameter_shadows` walked ancestors through it for every call site and declaration: extraction time grew quadratically with nesting depth (sample profile: >90% of time in ts_node_parent under those three). The main traversal now records each node's parent, and the three ancestor walks look it up (falling back to `Node::parent` for nodes the traversal skips, so results are unchanged). Measured on one C++ file with a call nested N blocks deep (release, macOS arm64): N=2.5k/5k/10k/20k took 0.26/1.05/4.24/16.8 s before; after, 20k takes 0.02 s with the same nodes and call edge. The full suite (111 tests) passes unchanged.
commit
f9804976bc
|
|
@ -242,6 +242,9 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
// Iterative traversal avoids a Rust stack overflow on deeply nested source.
|
||||
// Error subtrees are omitted; complete sibling declarations remain usable.
|
||||
let mut nodes = Vec::new();
|
||||
// Parent of every traversed node. tree-sitter's `Node::parent` re-descends from
|
||||
// the root (O(depth)), so ancestor walks through it were quadratic in nesting.
|
||||
let mut parents: HashMap<usize, Node<'_>> = HashMap::new();
|
||||
let mut errors: Vec<(usize, usize)> = Vec::new();
|
||||
let mut stack = vec![tree.root_node()];
|
||||
while let Some(node) = stack.pop() {
|
||||
|
|
@ -312,6 +315,9 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
}
|
||||
let mut cursor = node.walk();
|
||||
let children: Vec<_> = node.named_children(&mut cursor).collect();
|
||||
for child in &children {
|
||||
parents.insert(child.id(), node);
|
||||
}
|
||||
stack.extend(children.into_iter().rev());
|
||||
}
|
||||
// Anonymous MISSING tokens (`#endif`, `;`, `}`) are not named children; find
|
||||
|
|
@ -395,7 +401,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
name,
|
||||
kind,
|
||||
owner: None,
|
||||
scope: lexical_scope(node, source),
|
||||
scope: lexical_scope(node, source, &parents),
|
||||
line: node.start_position().row + 1,
|
||||
identity,
|
||||
qualifier,
|
||||
|
|
@ -418,7 +424,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
let ty = ancestor
|
||||
.child_by_field_name("type")
|
||||
.map(|n| base_type(n, source));
|
||||
let scope = lexical_scope(ancestor, source);
|
||||
let scope = lexical_scope(ancestor, source, &parents);
|
||||
let candidates: Vec<_> = doc
|
||||
.declarations
|
||||
.iter()
|
||||
|
|
@ -431,7 +437,13 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
declaration_nodes[*i],
|
||||
&declarations,
|
||||
&doc.declarations,
|
||||
) == enclosing_function(ancestor, &declarations, &doc.declarations)
|
||||
&parents,
|
||||
) == enclosing_function(
|
||||
ancestor,
|
||||
&declarations,
|
||||
&doc.declarations,
|
||||
&parents,
|
||||
)
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
|
|
@ -474,8 +486,8 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
{
|
||||
continue;
|
||||
}
|
||||
let owner = enclosing_function(node, &declarations, &doc.declarations);
|
||||
let scope = lexical_scope(node, source);
|
||||
let owner = enclosing_function(node, &declarations, &doc.declarations, &parents);
|
||||
let scope = lexical_scope(node, source, &parents);
|
||||
if language == "rust" && node.kind() == "use_declaration" && !has_error(node) {
|
||||
for (target, binding) in rust_imports(node, source) {
|
||||
if owner.is_none() {
|
||||
|
|
@ -539,7 +551,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
for (target, line) in cpp_vexing_calls(node, source) {
|
||||
doc.relationships.push(Relationship {
|
||||
owner,
|
||||
shadowed: parameter_shadows(node, &target, source),
|
||||
shadowed: parameter_shadows(node, &target, source, &parents),
|
||||
target,
|
||||
kind: EdgeKind::Calls,
|
||||
member: false,
|
||||
|
|
@ -580,6 +592,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
if node.kind() == "macro_invocation" {
|
||||
let context = MacroContext {
|
||||
position: node,
|
||||
parents: &parents,
|
||||
owner,
|
||||
scope: &scope,
|
||||
self_type: &self_type,
|
||||
|
|
@ -632,7 +645,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
|||
None
|
||||
};
|
||||
doc.relationships.push(Relationship {
|
||||
shadowed: !member && parameter_shadows(node, &target, source),
|
||||
shadowed: !member && parameter_shadows(node, &target, source, &parents),
|
||||
owner,
|
||||
target,
|
||||
member,
|
||||
|
|
@ -832,8 +845,20 @@ fn cpp_leaf_name(name: Node<'_>, source: &str) -> Option<String> {
|
|||
Some(text(name, source).to_owned())
|
||||
}
|
||||
|
||||
fn lexical_scope(node: Node<'_>, source: &str) -> String {
|
||||
let mut parent = node.parent();
|
||||
/// The parent recorded during traversal, else tree-sitter's (O(depth)) lookup.
|
||||
fn parent_of<'tree>(
|
||||
node: Node<'tree>,
|
||||
parents: &HashMap<usize, Node<'tree>>,
|
||||
) -> Option<Node<'tree>> {
|
||||
parents.get(&node.id()).copied().or_else(|| node.parent())
|
||||
}
|
||||
|
||||
fn lexical_scope<'tree>(
|
||||
node: Node<'tree>,
|
||||
source: &str,
|
||||
parents: &HashMap<usize, Node<'tree>>,
|
||||
) -> String {
|
||||
let mut parent = parent_of(node, parents);
|
||||
let mut names = Vec::new();
|
||||
while let Some(n) = parent {
|
||||
if matches!(
|
||||
|
|
@ -846,25 +871,26 @@ fn lexical_scope(node: Node<'_>, source: &str) -> String {
|
|||
.unwrap_or("<anonymous>"),
|
||||
);
|
||||
}
|
||||
parent = n.parent();
|
||||
parent = parent_of(n, parents);
|
||||
}
|
||||
names.reverse();
|
||||
names.join("::")
|
||||
}
|
||||
|
||||
fn enclosing_function(
|
||||
node: Node<'_>,
|
||||
fn enclosing_function<'tree>(
|
||||
node: Node<'tree>,
|
||||
ids: &HashMap<usize, usize>,
|
||||
declarations: &[Declaration],
|
||||
parents: &HashMap<usize, Node<'tree>>,
|
||||
) -> Option<usize> {
|
||||
let mut parent = node.parent();
|
||||
let mut parent = parent_of(node, parents);
|
||||
while let Some(n) = parent {
|
||||
if let Some(&index) = ids.get(&n.id())
|
||||
&& declarations[index].kind == NodeKind::Function
|
||||
{
|
||||
return Some(index);
|
||||
}
|
||||
parent = n.parent();
|
||||
parent = parent_of(n, parents);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -1023,8 +1049,13 @@ fn exported_names(node: Node<'_>, name: &str) -> Vec<String> {
|
|||
Vec::new()
|
||||
}
|
||||
|
||||
fn parameter_shadows(node: Node<'_>, name: &str, source: &str) -> bool {
|
||||
let mut parent = node.parent();
|
||||
fn parameter_shadows<'tree>(
|
||||
node: Node<'tree>,
|
||||
name: &str,
|
||||
source: &str,
|
||||
parents: &HashMap<usize, Node<'tree>>,
|
||||
) -> bool {
|
||||
let mut parent = parent_of(node, parents);
|
||||
while let Some(n) = parent {
|
||||
let signature = n
|
||||
.child_by_field_name("signature")
|
||||
|
|
@ -1058,7 +1089,7 @@ fn parameter_shadows(node: Node<'_>, name: &str, source: &str) -> bool {
|
|||
);
|
||||
}
|
||||
}
|
||||
parent = n.parent();
|
||||
parent = parent_of(n, parents);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -1090,7 +1121,7 @@ fn import_bindings(node: Node<'_>, module: &str, source: &str) -> Vec<ImportBind
|
|||
local: local.into(),
|
||||
exported: exported.into(),
|
||||
module: module.into(),
|
||||
scope: lexical_scope(node, source),
|
||||
scope: lexical_scope(node, source, &HashMap::new()),
|
||||
});
|
||||
}
|
||||
let mut cursor = n.walk();
|
||||
|
|
@ -1160,7 +1191,7 @@ fn rust_imports(node: Node<'_>, source: &str) -> Vec<(String, Option<ImportBindi
|
|||
local,
|
||||
exported: exported.into(),
|
||||
module: module.into(),
|
||||
scope: lexical_scope(node, source),
|
||||
scope: lexical_scope(node, source, &HashMap::new()),
|
||||
})
|
||||
};
|
||||
output.push((target, binding));
|
||||
|
|
@ -1847,6 +1878,7 @@ fn rust_fields(node: Node<'_>, source: &str) -> Vec<(String, String, CppType)> {
|
|||
|
||||
struct MacroContext<'a, 'tree> {
|
||||
position: Node<'tree>,
|
||||
parents: &'a HashMap<usize, Node<'tree>>,
|
||||
owner: Option<usize>,
|
||||
scope: &'a str,
|
||||
self_type: &'a Option<String>,
|
||||
|
|
@ -1943,7 +1975,12 @@ fn rust_macro_calls(
|
|||
});
|
||||
relationships.push(Relationship {
|
||||
shadowed: !member
|
||||
&& parameter_shadows(context.position, &target, context.source),
|
||||
&& parameter_shadows(
|
||||
context.position,
|
||||
&target,
|
||||
context.source,
|
||||
context.parents,
|
||||
),
|
||||
owner: context.owner,
|
||||
target,
|
||||
member,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue