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.
|
// Iterative traversal avoids a Rust stack overflow on deeply nested source.
|
||||||
// Error subtrees are omitted; complete sibling declarations remain usable.
|
// Error subtrees are omitted; complete sibling declarations remain usable.
|
||||||
let mut nodes = Vec::new();
|
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 errors: Vec<(usize, usize)> = Vec::new();
|
||||||
let mut stack = vec![tree.root_node()];
|
let mut stack = vec![tree.root_node()];
|
||||||
while let Some(node) = stack.pop() {
|
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 mut cursor = node.walk();
|
||||||
let children: Vec<_> = node.named_children(&mut cursor).collect();
|
let children: Vec<_> = node.named_children(&mut cursor).collect();
|
||||||
|
for child in &children {
|
||||||
|
parents.insert(child.id(), node);
|
||||||
|
}
|
||||||
stack.extend(children.into_iter().rev());
|
stack.extend(children.into_iter().rev());
|
||||||
}
|
}
|
||||||
// Anonymous MISSING tokens (`#endif`, `;`, `}`) are not named children; find
|
// 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,
|
name,
|
||||||
kind,
|
kind,
|
||||||
owner: None,
|
owner: None,
|
||||||
scope: lexical_scope(node, source),
|
scope: lexical_scope(node, source, &parents),
|
||||||
line: node.start_position().row + 1,
|
line: node.start_position().row + 1,
|
||||||
identity,
|
identity,
|
||||||
qualifier,
|
qualifier,
|
||||||
|
|
@ -418,7 +424,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
||||||
let ty = ancestor
|
let ty = ancestor
|
||||||
.child_by_field_name("type")
|
.child_by_field_name("type")
|
||||||
.map(|n| base_type(n, source));
|
.map(|n| base_type(n, source));
|
||||||
let scope = lexical_scope(ancestor, source);
|
let scope = lexical_scope(ancestor, source, &parents);
|
||||||
let candidates: Vec<_> = doc
|
let candidates: Vec<_> = doc
|
||||||
.declarations
|
.declarations
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -431,7 +437,13 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
||||||
declaration_nodes[*i],
|
declaration_nodes[*i],
|
||||||
&declarations,
|
&declarations,
|
||||||
&doc.declarations,
|
&doc.declarations,
|
||||||
) == enclosing_function(ancestor, &declarations, &doc.declarations)
|
&parents,
|
||||||
|
) == enclosing_function(
|
||||||
|
ancestor,
|
||||||
|
&declarations,
|
||||||
|
&doc.declarations,
|
||||||
|
&parents,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -474,8 +486,8 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let owner = enclosing_function(node, &declarations, &doc.declarations);
|
let owner = enclosing_function(node, &declarations, &doc.declarations, &parents);
|
||||||
let scope = lexical_scope(node, source);
|
let scope = lexical_scope(node, source, &parents);
|
||||||
if language == "rust" && node.kind() == "use_declaration" && !has_error(node) {
|
if language == "rust" && node.kind() == "use_declaration" && !has_error(node) {
|
||||||
for (target, binding) in rust_imports(node, source) {
|
for (target, binding) in rust_imports(node, source) {
|
||||||
if owner.is_none() {
|
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) {
|
for (target, line) in cpp_vexing_calls(node, source) {
|
||||||
doc.relationships.push(Relationship {
|
doc.relationships.push(Relationship {
|
||||||
owner,
|
owner,
|
||||||
shadowed: parameter_shadows(node, &target, source),
|
shadowed: parameter_shadows(node, &target, source, &parents),
|
||||||
target,
|
target,
|
||||||
kind: EdgeKind::Calls,
|
kind: EdgeKind::Calls,
|
||||||
member: false,
|
member: false,
|
||||||
|
|
@ -580,6 +592,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
||||||
if node.kind() == "macro_invocation" {
|
if node.kind() == "macro_invocation" {
|
||||||
let context = MacroContext {
|
let context = MacroContext {
|
||||||
position: node,
|
position: node,
|
||||||
|
parents: &parents,
|
||||||
owner,
|
owner,
|
||||||
scope: &scope,
|
scope: &scope,
|
||||||
self_type: &self_type,
|
self_type: &self_type,
|
||||||
|
|
@ -632,7 +645,7 @@ pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Docume
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
doc.relationships.push(Relationship {
|
doc.relationships.push(Relationship {
|
||||||
shadowed: !member && parameter_shadows(node, &target, source),
|
shadowed: !member && parameter_shadows(node, &target, source, &parents),
|
||||||
owner,
|
owner,
|
||||||
target,
|
target,
|
||||||
member,
|
member,
|
||||||
|
|
@ -832,8 +845,20 @@ fn cpp_leaf_name(name: Node<'_>, source: &str) -> Option<String> {
|
||||||
Some(text(name, source).to_owned())
|
Some(text(name, source).to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lexical_scope(node: Node<'_>, source: &str) -> String {
|
/// The parent recorded during traversal, else tree-sitter's (O(depth)) lookup.
|
||||||
let mut parent = node.parent();
|
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();
|
let mut names = Vec::new();
|
||||||
while let Some(n) = parent {
|
while let Some(n) = parent {
|
||||||
if matches!(
|
if matches!(
|
||||||
|
|
@ -846,25 +871,26 @@ fn lexical_scope(node: Node<'_>, source: &str) -> String {
|
||||||
.unwrap_or("<anonymous>"),
|
.unwrap_or("<anonymous>"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
parent = n.parent();
|
parent = parent_of(n, parents);
|
||||||
}
|
}
|
||||||
names.reverse();
|
names.reverse();
|
||||||
names.join("::")
|
names.join("::")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enclosing_function(
|
fn enclosing_function<'tree>(
|
||||||
node: Node<'_>,
|
node: Node<'tree>,
|
||||||
ids: &HashMap<usize, usize>,
|
ids: &HashMap<usize, usize>,
|
||||||
declarations: &[Declaration],
|
declarations: &[Declaration],
|
||||||
|
parents: &HashMap<usize, Node<'tree>>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
let mut parent = node.parent();
|
let mut parent = parent_of(node, parents);
|
||||||
while let Some(n) = parent {
|
while let Some(n) = parent {
|
||||||
if let Some(&index) = ids.get(&n.id())
|
if let Some(&index) = ids.get(&n.id())
|
||||||
&& declarations[index].kind == NodeKind::Function
|
&& declarations[index].kind == NodeKind::Function
|
||||||
{
|
{
|
||||||
return Some(index);
|
return Some(index);
|
||||||
}
|
}
|
||||||
parent = n.parent();
|
parent = parent_of(n, parents);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -1023,8 +1049,13 @@ fn exported_names(node: Node<'_>, name: &str) -> Vec<String> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parameter_shadows(node: Node<'_>, name: &str, source: &str) -> bool {
|
fn parameter_shadows<'tree>(
|
||||||
let mut parent = node.parent();
|
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 {
|
while let Some(n) = parent {
|
||||||
let signature = n
|
let signature = n
|
||||||
.child_by_field_name("signature")
|
.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
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -1090,7 +1121,7 @@ fn import_bindings(node: Node<'_>, module: &str, source: &str) -> Vec<ImportBind
|
||||||
local: local.into(),
|
local: local.into(),
|
||||||
exported: exported.into(),
|
exported: exported.into(),
|
||||||
module: module.into(),
|
module: module.into(),
|
||||||
scope: lexical_scope(node, source),
|
scope: lexical_scope(node, source, &HashMap::new()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut cursor = n.walk();
|
let mut cursor = n.walk();
|
||||||
|
|
@ -1160,7 +1191,7 @@ fn rust_imports(node: Node<'_>, source: &str) -> Vec<(String, Option<ImportBindi
|
||||||
local,
|
local,
|
||||||
exported: exported.into(),
|
exported: exported.into(),
|
||||||
module: module.into(),
|
module: module.into(),
|
||||||
scope: lexical_scope(node, source),
|
scope: lexical_scope(node, source, &HashMap::new()),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
output.push((target, binding));
|
output.push((target, binding));
|
||||||
|
|
@ -1847,6 +1878,7 @@ fn rust_fields(node: Node<'_>, source: &str) -> Vec<(String, String, CppType)> {
|
||||||
|
|
||||||
struct MacroContext<'a, 'tree> {
|
struct MacroContext<'a, 'tree> {
|
||||||
position: Node<'tree>,
|
position: Node<'tree>,
|
||||||
|
parents: &'a HashMap<usize, Node<'tree>>,
|
||||||
owner: Option<usize>,
|
owner: Option<usize>,
|
||||||
scope: &'a str,
|
scope: &'a str,
|
||||||
self_type: &'a Option<String>,
|
self_type: &'a Option<String>,
|
||||||
|
|
@ -1943,7 +1975,12 @@ fn rust_macro_calls(
|
||||||
});
|
});
|
||||||
relationships.push(Relationship {
|
relationships.push(Relationship {
|
||||||
shadowed: !member
|
shadowed: !member
|
||||||
&& parameter_shadows(context.position, &target, context.source),
|
&& parameter_shadows(
|
||||||
|
context.position,
|
||||||
|
&target,
|
||||||
|
context.source,
|
||||||
|
context.parents,
|
||||||
|
),
|
||||||
owner: context.owner,
|
owner: context.owner,
|
||||||
target,
|
target,
|
||||||
member,
|
member,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue