mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-02 01:37:32 +08:00
* fix(cpp): macro invocations are not calls; local objects call their constructor A function-like macro invocation (`TRACE_POINT(1)`) parses as a call, and the resolver bound it to a same-named free function in another translation unit — a caller and a callee that never existed (#1838). And local object initialization (`Widget w;` / `Widget w(1);` / `Widget w{1};`) reached the class node instead of a constructor, or nothing at all for the default form (#1839). Extraction (wasm walker and the Rust kernel, in parity): - every `preproc_function_def` becomes a `constant` node whose signature is the directive, so the index knows which names are macros; - a C++ declaration emits one `calls` ref per constructed object, shaped `ns::T::T/<arity>`; pointer, reference and function declarators, `extern`, and array element braces construct nothing; the #1035 `instantiates` ref is unchanged; - constructors carry their parameter list as the signature. Resolution: - `cpp-macro-visibility.ts` walks the translation unit (root file plus in-repo includes, each file once) over per-file summaries of function-like defines, undefs and includes, evaluating what each file can decide itself (literals, its own defines, the include-guard idiom). A call whose name is definitely a macro at that point, or a name that only macros bear, resolves to nothing. A macro that exists in one build configuration only (CMSIS's `__DSB()` under the ARM compilers, a target's HAL compat header) keeps the call to the function the other configuration compiles; a wrapper macro that calls its own name is how that function gets called and hides nothing. - `cpp-constructor.ts` resolves the constructor refs: the type in the lexical namespaces of the call site, then the single overload whose parameter count admits the arguments; two admitting overloads, an initializer_list overload, or an aggregate yield no edge. - a `#define` constant is never a `calls` target and does not count toward the same-name ceiling. Measured on a betaflight tree (4,017 files, wasm arm): 153 `calls` edges lost — all fabricated (`MIN`/`MAX` bound to a CLI enum member, macros misparsed as functions, a cross-vendor `SWAPBYTE`) — and 34 constructor edges gained; indexing 34s vs 30s. Fixes #1838 Fixes #1839 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(cpp): resolve local constructors and macro visibility (#1839) Local construction lacked constructor calls, while macro invocations could bind unrelated functions. Resolve constructors using lexical types and overload signatures, preserving separately declared defaults and array element construction. Evaluate macro directives in translation-unit order, respecting reinclusion, guards and pragma once. Mirror extraction in Rust and WASM, preserve type dependencies, and distinguish them in CLI output. Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
danusha2345
Claude Fable 5.1
parent
5eaa6fedd5
commit
fa25883ab0
@@ -908,6 +908,18 @@ impl<'t> Walker<'t> {
|
||||
let kind = node.kind();
|
||||
let mut skip_children = false;
|
||||
|
||||
// C/C++ function-like macros become `constant` nodes carrying the
|
||||
// directive as their signature — a value, never a callee (#1838).
|
||||
// Mirrors tree-sitter.ts visitNode.
|
||||
if kind == "preproc_function_def" {
|
||||
if let Some(name_node) = node.child_by_field_name("name") {
|
||||
let name = self.text(name_node).to_string();
|
||||
let signature = Some(self.text(node).trim().to_string());
|
||||
self.create_node("constant", &name, node, Extra { signature, ..Extra::default() });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// C++ namespace blocks: prefix-only, no node (#1291/#1093). Anonymous
|
||||
// namespaces fall through to the generic walk.
|
||||
if self.variant == Variant::Cpp && kind == "namespace_definition" {
|
||||
@@ -929,7 +941,10 @@ impl<'t> Walker<'t> {
|
||||
|
||||
self.maybe_capture_fn_refs(node);
|
||||
|
||||
if kind == "function_definition" {
|
||||
if self.is_cpp_constructor_declaration(node) {
|
||||
self.extract_method(node);
|
||||
skip_children = true;
|
||||
} else if kind == "function_definition" {
|
||||
// functionTypes for both; cpp's methodTypes also lists it, so
|
||||
// inside a class-like scope it extracts as a method.
|
||||
if self.inside_class_like() && self.variant == Variant::Cpp {
|
||||
@@ -1016,6 +1031,7 @@ impl<'t> Walker<'t> {
|
||||
|
||||
let extra = Extra {
|
||||
docstring: preceding_docstring(node, self.src),
|
||||
signature: self.constructor_signature(node),
|
||||
visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None },
|
||||
return_type: self.return_type_of(node),
|
||||
..Extra::default()
|
||||
@@ -1052,6 +1068,7 @@ impl<'t> Walker<'t> {
|
||||
|
||||
let extra = Extra {
|
||||
docstring: preceding_docstring(node, self.src),
|
||||
signature: self.constructor_signature(node),
|
||||
visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None },
|
||||
is_abstract: if self.variant == Variant::Cpp && self.is_cpp_pure_virtual_method_decl(node) {
|
||||
Some(true)
|
||||
@@ -1544,27 +1561,117 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
|
||||
/// isCppStackConstruction (#1035).
|
||||
fn is_cpp_stack_construction(&self, node: Node) -> bool {
|
||||
let Some(type_node) = node.child_by_field_name("type") else { return false };
|
||||
fn is_cpp_constructor_declaration(&self, node: Node<'t>) -> bool {
|
||||
if self.variant != Variant::Cpp || node.kind() != "declaration" || node.child_by_field_name("type").is_some() {
|
||||
return false;
|
||||
}
|
||||
let Some(owner) = node.parent().and_then(|p| p.parent()) else { return false };
|
||||
if !matches!(owner.kind(), "class_specifier" | "struct_specifier" | "union_specifier") { return false; }
|
||||
let Some(decl) = node.child_by_field_name("declarator") else { return false };
|
||||
decl.kind() == "function_declarator" && decl.child_by_field_name("declarator").map(|n| self.text(n))
|
||||
== owner.child_by_field_name("name").map(|n| self.text(n))
|
||||
}
|
||||
|
||||
/// cppExtractor.getSignature (languages/c-cpp.ts): a C++ constructor
|
||||
/// definition or class-body prototype carries its parameter
|
||||
/// list as the signature so a local `T obj(args)` can pick the overload
|
||||
/// by arity (#1839); a trailing semicolon marks a prototype. Macro-shaped definitions whose real name was
|
||||
/// recovered from an argument are excluded.
|
||||
fn constructor_signature(&self, node: Node<'t>) -> Option<String> {
|
||||
if self.variant != Variant::Cpp || (node.kind() != "function_definition" && !self.is_cpp_constructor_declaration(node)) {
|
||||
return None;
|
||||
}
|
||||
if node.child_by_field_name("type").is_some() || self.recover_cpp_macro_defined_name(node).is_some() {
|
||||
return None;
|
||||
}
|
||||
let params = node.child_by_field_name("declarator")?.child_by_field_name("parameters")?;
|
||||
Some(format!("{}{}", self.text(params), if node.kind() == "declaration" { ";" } else { "" }))
|
||||
}
|
||||
|
||||
/// cppStackConstructions (tree-sitter.ts, #1035 / #1839): whether the
|
||||
/// declaration constructs with arguments (→ `instantiates`), and one
|
||||
/// arity per constructed object (→ `calls T::T/arity`). `extern`,
|
||||
/// pointer / reference / function declarators construct nothing; an
|
||||
/// array's braces hold elements, not constructor arguments.
|
||||
fn cpp_stack_constructions(&self, node: Node<'t>) -> (bool, Vec<usize>) {
|
||||
let none = (false, Vec::new());
|
||||
let Some(type_node) = node.child_by_field_name("type") else { return none };
|
||||
if !matches!(
|
||||
type_node.kind(),
|
||||
"type_identifier" | "template_type" | "qualified_identifier"
|
||||
) {
|
||||
return false;
|
||||
return none;
|
||||
}
|
||||
let mut instantiates = false;
|
||||
let mut arities = Vec::new();
|
||||
for i in 0..node.named_child_count() {
|
||||
let Some(child) = node.named_child(i) else { continue };
|
||||
if child.kind() == "storage_class_specifier" && self.text(child) == "extern" {
|
||||
return none;
|
||||
}
|
||||
if matches!(child.kind(), "identifier" | "array_declarator") {
|
||||
if child.kind() == "array_declarator" && !self.cpp_object_array(child) { continue; }
|
||||
arities.push(0);
|
||||
continue;
|
||||
}
|
||||
if child.kind() != "init_declarator" {
|
||||
continue;
|
||||
}
|
||||
if let Some(value) = child.child_by_field_name("value") {
|
||||
if matches!(value.kind(), "argument_list" | "initializer_list") {
|
||||
return true;
|
||||
let Some(declarator) = child.child_by_field_name("declarator") else { continue };
|
||||
if !matches!(declarator.kind(), "identifier" | "array_declarator") {
|
||||
continue;
|
||||
}
|
||||
let Some(value) = child.child_by_field_name("value") else { continue };
|
||||
if !matches!(value.kind(), "argument_list" | "initializer_list") {
|
||||
continue;
|
||||
}
|
||||
instantiates = true;
|
||||
if declarator.kind() == "identifier" {
|
||||
let count = (0..value.named_child_count())
|
||||
.filter(|&j| value.named_child(j).map(|n| n.kind() != "comment").unwrap_or(false))
|
||||
.count();
|
||||
arities.push(count);
|
||||
} else if self.cpp_object_array(declarator) {
|
||||
let mut dimensions = Vec::new();
|
||||
let mut array = Some(declarator);
|
||||
while let Some(n) = array.filter(|n| n.kind() == "array_declarator") {
|
||||
dimensions.insert(0, n.child_by_field_name("size").map(|s| self.text(s))
|
||||
.filter(|s| s.bytes().all(|b| b.is_ascii_digit()))
|
||||
.and_then(|s| s.parse::<usize>().ok()).filter(|&n| n <= 9_007_199_254_740_991));
|
||||
array = n.child_by_field_name("declarator");
|
||||
}
|
||||
Self::cpp_array_arities(value, &dimensions, &mut arities);
|
||||
}
|
||||
}
|
||||
false
|
||||
(instantiates, arities)
|
||||
}
|
||||
|
||||
fn cpp_array_arities(list: Node<'t>, dimensions: &[Option<usize>], arities: &mut Vec<usize>) {
|
||||
let entries: Vec<_> = (0..list.named_child_count()).filter_map(|j| list.named_child(j))
|
||||
.filter(|n| n.kind() != "comment").collect();
|
||||
let mut elided = false;
|
||||
for entry in &entries {
|
||||
if dimensions.len() > 1 {
|
||||
if entry.kind() == "initializer_list" { Self::cpp_array_arities(*entry, &dimensions[1..], arities); }
|
||||
else { elided = true; }
|
||||
} else {
|
||||
arities.push(if entry.kind() == "initializer_list" {
|
||||
(0..entry.named_child_count()).filter_map(|j| entry.named_child(j))
|
||||
.filter(|n| n.kind() != "comment").count()
|
||||
} else { 1 });
|
||||
}
|
||||
}
|
||||
if !elided && (entries.is_empty() || dimensions[0].map(|n| n > entries.len()).unwrap_or(false)) {
|
||||
arities.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn cpp_object_array(&self, node: Node<'t>) -> bool {
|
||||
let mut element = node.child_by_field_name("declarator");
|
||||
while element.map(|n| n.kind() == "array_declarator").unwrap_or(false) {
|
||||
element = element.and_then(|n| n.child_by_field_name("declarator"));
|
||||
}
|
||||
element.map(|n| n.kind() == "identifier").unwrap_or(false)
|
||||
}
|
||||
|
||||
/// recordCppFnPtrBinding (tree-sitter.ts:5089).
|
||||
@@ -1660,6 +1767,11 @@ impl<'t> Walker<'t> {
|
||||
fn visit_for_calls_and_structure(&mut self, node: Node<'t>) {
|
||||
stack_guard!();
|
||||
let kind = node.kind();
|
||||
// A function-like macro defined inside a body is still a macro (#1838).
|
||||
if kind == "preproc_function_def" {
|
||||
self.visit_node(node);
|
||||
return;
|
||||
}
|
||||
self.maybe_capture_fn_refs(node);
|
||||
|
||||
if kind == "call_expression" {
|
||||
@@ -1668,12 +1780,25 @@ impl<'t> Walker<'t> {
|
||||
self.extract_instantiation(node);
|
||||
}
|
||||
|
||||
// C++ stack construction `Calculator calc(0)` / `Widget w{1,2}` (#1035).
|
||||
if kind == "declaration"
|
||||
&& self.variant == Variant::Cpp
|
||||
&& self.is_cpp_stack_construction(node)
|
||||
{
|
||||
self.extract_instantiation(node);
|
||||
// C++ stack construction `Calculator calc(0)` / `Widget w{1,2}` (#1035),
|
||||
// plus one constructor ref `ns::T::T/arity` per constructed object (#1839).
|
||||
if kind == "declaration" && self.variant == Variant::Cpp {
|
||||
let (instantiates, arities) = self.cpp_stack_constructions(node);
|
||||
if instantiates {
|
||||
self.extract_instantiation(node);
|
||||
}
|
||||
if !arities.is_empty() && !self.stack.is_empty() {
|
||||
if let Some(type_node) = node.child_by_field_name("type") {
|
||||
let from = self.top_row();
|
||||
let class_name = strip_cpp_template_args(self.text(type_node));
|
||||
if let Some(name) = class_name.split("::").filter(|s| !s.is_empty()).last() {
|
||||
let calls = edge_kind_index("calls").unwrap();
|
||||
for arity in arities {
|
||||
self.push_ref_at(from, &format!("{class_name}::{name}/{arity}"), calls, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// C++ local fn-pointer bindings: declarations and branch reassignments.
|
||||
|
||||
Reference in New Issue
Block a user