You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
method calls through object instances/pointers are dropped in CALLER EDGES #1153
Thanks for the focused receiver-call evidence. I removed the incorrect Windows label and placed this in the high-priority 0.9.1-rc query/call-resolution queue under #594. The RED guard should cover C/C++ pointer and instance receivers independently, prove the enclosing Method remains the caller, and distinguish a missing edge from an edge incorrectly attributed to a File or Module node.
Relates to / extends #1153 (method calls through object instances/pointers dropped in caller edges).
Summary:
A CALLS caller edge is missing for a method invoked through a static factory return — SecdManager::getInstance()->secd_retrieve_pic_fpc_i2c_id(...). The callee is non-virtual (statically bound), so the edge is resolvable from the static return type of getInstance() alone, with no polymorphic/vtable analysis. This broadens #1153 from local pointer/instance receivers to factory/accessor-return receivers (getInstance()->foo(), getX()->foo()).
Impact:
query_graph caller queries and trace_path --direction upstream report 0 callers for a function that clearly has one, breaking impact analysis / reverse call-graph for the very common singleton pattern.
Where it exists (OpenGrok full:secd_retrieve_pic_fpc_i2c_id, 3/3 hits):
decl: a/b/app/src/SecdManager.h:164
def : a/b/app/src/SecdManager.cpp:2257
call: a/b/pil/src/SecdCfgHandlerThread.cpp:237
Where it's called:
Inside GetExternalPhyPresent(), via a singleton factory return:
// securityd/common/pil/src/SecdCfgHandlerThread.cpp:237
if(!SecdManager::getInstance()->secd_retrieve_pic_fpc_i2c_id(ifdh, fpc_id, pic_id)) {
}
How it was missed:
query_graph — caller query returns 0 (edge never created):
MATCH (caller)-[r:CALLS]->(f:Method)
WHERE f.name='secd_retrieve_pic_fpc_i2c_id' AND f.file_path CONTAINS 'SecdManager.cpp'
RETURN caller.name, caller.file_path, r.line -> {"rows":[],"total":0}
Also 0 with exact qualified_name, with (caller:Method), and via USAGE-incoming.
trace_path — --direction upstream (and both) reports 0 callers / in_degree=0 for the same function.
Proof it's a real drop, not a bad query:
The mirror callee direction, same anchor, returns 9 rows -> the anchor resolves fine; only the incoming edge is missing:
MATCH (f:Method)-[r:CALLS]->(c)
WHERE f.name='secd_retrieve_pic_fpc_i2c_id' AND f.file_path CONTAINS 'SecdManager.cpp'
RETURN c.name, r.line ORDER BY r.line -> 9 rows (getFpc, getPic, fpc_map, ifdo_hndl, ...)
Expected:
A CALLS edge GetExternalPhyPresent -> SecdManager::secd_retrieve_pic_fpc_i2c_id. Both the caller query and trace_path --direction upstream should list GetExternalPhyPresent (SecdCfgHandlerThread.cpp:237).
Fix scope:
Receiver is a factory-return (getInstance()->) and callee is non-virtual -> resolvable from the static return type alone (no vtable analysis). Fix should cover factory()->method() / accessor()->method() receivers, not only local pointer/instance variables.
can you please let me know when can we expexct 0.9.1-rc
Confirming this also reproduces for Python instance-variable method calls on v0.9.0 (linux-amd64, portable static binary from the GitHub release archive), so it is not limited to C/C++ pointer receivers.
Observed on a mid-size Python repo indexed with --mode full:
trace_path on StackManager.status with --direction upstream reports callers=0, despite the real call site through the instance variable.
The callee direction (--direction downstream) is accurate, including cross-file — so the anchor resolves fine and only the incoming CALLS edge is missing, matching the reports above.
So the RED guard for the 0.9.1-rc fix should cover Python instance receivers (obj = Cls(); obj.method()) in addition to the C/C++ pointer and factory-return cases.
Thank you for the additional C++ factory-return and Python instance-call examples. They broaden the receiver forms that a fix must cover. v0.11.0 is now published, but I have not verified that these exact cases are fixed there, so I cannot offer a fix-version promise or close this based on release age.
Answering your 2026-09-19 question about v0.11.0 directly: no, these cases are not fixed in v0.11.0 — and I have the root cause.
I reproduced on the v0.11.0 release binary (codebase-memory-mcp 0.11.0, windows-amd64, official archive, install.ps1 + checksum verified), clean mode=full index, and cross-checked the same case on 0.10.8.
Root cause: an export macro between class and the class name demotes the class to a Function node
MATCH (n) WHERE n.name='Worker'
["Function"] fdF.inc.worker.Worker inc/worker.h <-- should be Class
["Function"] fdF.ctl.worker.Worker ctl/worker.h
MATCH (a)-[r:DEFINES_METHOD]->(b)
n = 0 <-- no class->method ownership at all
Remove the macro and the same tree produces Class + DEFINES_METHOD + both CALLS edges:
variant
worker.h declares
Worker node label
w->isActive() edge
no macro
class Worker {
Class
lsp_base_dispatch
macro
class MYMODULE_EXPORT Worker {
Function
dropped
Identical with and without a base clause (class MYMODULE_EXPORT Worker { vs ... : public QObject {), and with and without a #include of the defining header, so the macro is the whole discriminator. This is extract_defs.c (class-declaration recognition), not c_lsp.c call resolution: the LSP side never gets a type to resolve, because no Class node exists and DEFINES_METHOD is empty.
The forward declaration is a secondary symptom, not the cause
While narrowing this I initially blamed forward declarations, because the misattributed Class node you can see in a real repo usually sits at a forward decl. The actual sequence is:
the real definition is demoted to Function (macro), and
the only Class node that survives is the one minted from class X; in some other header,
so ptr->method() resolves against a type qualified name under the wrong file prefix and finds no member. Fixing (1) removes the observable symptom; a separate guard for (2) would be worth having.
Receivers confirmed to work, so the guard does not need to cover them
Same tree, same index, only the receiver form varies:
Worker *fresh = new Worker(); fresh->isActive(); (heap)
resolves
#include "worker.h" in the caller's header, no macro
resolves
any of the above, with the export macro on the class
dropped
Local pointers, factory/accessor returns, and Python instance receivers (the case in the 2026-08-01 comment, id 5149866495) all resolve here. The macro is what breaks them.
Real-repo impact
Modules/Planning/Registration/YSRegistration, a ~1,200-file C++/Python repo:
qSlicerYSRegistrationModuleWidget.cxx has 17 workflow-> member calls across 11 distinct targets, and not one of those 17 calls produced an edge. The file does contribute 28 CALLS edges, but every one of them targets a different symbol; the only inbound edges those 11 targets have come from other files. So 100% of that file's pointer member-call edges are missing.
qSlicerOralScanWorkflow: Class node lives at qSlicerYSRegistrationModuleWidget.h (forward decl), while its methods live under ...YSRegistration.Widgets.qSlicerOralScanWorkflow.…. Different QN prefixes, so trace_path --direction inbound on isActive / showExistingFusionResult returns callers_total: 0 — a false dead-code signal.
188 class definitions in the repo take a *_EXPORT macro between class and the name; each one is a latent instance.
Whole-repo scan: of 1,181 Class nodes, 50 point at a file that only forward-declares the name while the real definition is elsewhere (vtkSlicerImplantLogic, vtkMRMLMarkupsCrownNode, vtkMRMLMarkupsImplantNode, vtkSlicerYSRegistrationLogic, vtkMRMLMarkupsAimingDisplayNode, …).
Not related to #220 — I checked the edge source label distribution and it is 100% Method, so these edges are genuinely absent, not re-attributed to a File/Module node.
Suggested RED guards
A class declaration whose head is class <IDENT> <NAME> (i.e. an uppercase macro token between the keyword and the declared name) still yields Class + DEFINES_METHOD + resolvable member calls.
A class X; forward declaration in file A never mints the Class node when a real definition of X exists anywhere in the index; the definition wins.
Both, with the caller body unchanged, so the failure isolates to declaration extraction rather than call resolution.
Happy to open a PR if a pointer to the right spot in extract_defs.c would help — but the RED test above is probably the higher-value half.
(The three-file repro above is self-contained — I can paste the exact tree, or the wider A/B matrix I used to isolate the macro, if either would help.)
@BigBrotherHui — thank you, that root cause is exactly right, and it arrived on the same day as its fix. An export macro between class and the class name made the extractor read class MYMODULE_EXPORT Worker { as a function returning class MYMODULE_EXPORT, so Worker became a Function node and w->isActive() had no class to dispatch through. That is the defect #2005 (issue #1989, by @XIYBHK) fixes, and it is now on main as c7488b0.
I ran your three files, plus a control copy without the macro, through main before the fix and through the fix:
binary
fixture
Worker
CALLS from callIt
edges
before
with macro
Function (return_type: "class MYMODULE_EXPORT")
none — only a USAGE -> Function Worker
15
before
control
Class
-> Worker.isActive (lsp_base_dispatch, 0.95)
16
after
with macro
Class
-> Worker.isActive (lsp_base_dispatch, 0.95)
16
after
control
Class
same
16
With the fix, the macro fixture's graph is identical to the control's — same labels, same edge properties, same totals. It ships in the next patch release; until then a build from main has it.
Two honest limits: this is one macro shape (a bare identifier between class and the name — the conventional export-macro form the fix targets; macros with arguments, e.g. EXPORT(x), are not covered by the zero-config rule and are the follow-up tracked on #1989), and this issue's original report listed more than one pattern. @sreehari-malapati-myprod — could you re-check your original cases against a build from main? If they all resolve, I will close this; if one does not, that one is a second cause and deserves its own reproduction here.
Retesting the other shapes on main, local pointers, heap and stack objects, and in-function Python instances all resolve. Two shapes still drop the edge:
A call through a static factory return, SecdManager::getInstance()->retrieve(…): getInstance is linked, retrieve is not.
A Python instance call at module level, manager = StackManager(); manager.status(): only the constructor is linked. The same call inside a function resolves.
The C++ static-factory case is fixed in #2466, and the cause was more specific than "factory returns":
The cross-file C++ resolver looked up SecdManager::getInstance only under the calling file's own module.
A class declared in a header and defined in a .cpp lives under that header's module, so the static call was never resolved through its declared type.
Its return type stayed unknown, so the ->secd_retrieve_pic_fpc_i2c_id(...) call after it had nothing to dispatch on.
In a single file the same code already worked, which is why this was easy to miss.
The fix resolves the class across files, but only when exactly one class carries that name. An ambiguous name stays unresolved rather than being guessed. It covers raw pointers, references, std::unique_ptr and std::shared_ptr returns. On qBittorrent it adds 489 correct edges, mostly Preferences::instance()->….
The Python module-level case (manager = StackManager(); manager.status()) has a different cause: module-level calls never joined their type resolution. That is fixed in a separate Python change. I'll post here when both are on main.
One honest limit: namespaced singletons such as RSS::Session::instance()->… still drop, because forward declarations currently create extra class nodes. That needs its own fix, and I'll track it separately.
Both remaining shapes are now fixed on main and shipped in v0.12.0:
C++ static-factory returns (SecdManager::getInstance()->retrieve(…)): fixed in cdd8c89 (fix(cpp): resolve Class::member calls across files (#1153) #2466). The cross-file C++ resolver now finds Class::member through a class declared in another file's header, as long as exactly one class carries that name. The returned type is therefore known, and the member call after it gets its CALLS edge.
Python instance calls at module level (manager = StackManager(); manager.status()): fixed in bf5ea5a. Module-level calls now join their LSP resolution in both pipeline modes, just as calls inside functions already did.
Together with the export-macro fix from #2005 (c7488b0, thanks to @XIYBHK), every shape reported here is covered, so I'm closing this issue.
One honest limit hasn't changed since my last update: namespaced singletons such as RSS::Session::instance()->… can still lose the edge, because a forward declaration currently creates an extra class node. That belongs with the C++ forward-declaration work (#1970), not here.
If any of your original cases still misses an edge on v0.12.0, please comment here and I'll reopen it.
Direct same-file calls to static functions produce zero CALLS edges (empty trace_path callees) -its basic feature issue.
For a C function that makes direct, same-translation-unit calls to static helper functions, the indexer creates no CALLS edges. trace_path --direction outbound returns an empty callees list even though the source clearly contains the calls. Reproduced on both v0.9.x and v0.12.1.
Environment
cbm versions: 0.9.x and 0.12.1 (same result)
Host: Linux, --mode full --persistence true
Source evidence (calls are unambiguous in the body) :
int s3ip_sysfs_psu_drivers_register(struct s3ip_sysfs_psu_drivers_s *drv)
{
...
ret = psu_root_create(); // direct call
if (ret < 0) { ...; return ret; }
ret = psu_sub_create(); // direct call
if (ret < 0) { psu_root_remove(); ...; return ret; } // direct call
ret = psu_temp_create(); // direct call
if (ret < 0) { psu_sub_remove(); psu_root_remove(); ...; return ret; } // direct calls
...
}
The callees are static functions defined in the same file,:
static void psu_root_remove(void) { ... } // line 914
static void psu_sub_remove(void) { ... } // line 881
Root cause (hypothesis)
Because each static helper name has multiple identical copies across the repo, the call resolver cannot prove a unique global target and drops the edge. Per the README, ambiguous targets are retained as USAGE — but here even USAGE-to-callee is absent, so the call is dropped entirely. The resolver appears to skip same-file (translation-unit) scoping: a static callee should resolve to the local definition in the same file before the global uniqueness check.
Suggested fix
For C/C++, resolve static (internal-linkage) calls against same-file definitions first, so a duplicated static name resolves to its local translation-unit target and emits a CALLS edge, instead of being dropped as ambiguous.
Impact
Any C file with static helpers whose names are duplicated elsewhere in the repo loses its intra-file call graph — trace_path, call-hierarchy, and impact analysis silently return empty results (false negatives), which is worse than an error because it looks like "no call
sreehari-malapati-myprod commented on Oct 23, 2026
Activity
DeusData commentedon Jul 18, 2026
Thanks for the focused receiver-call evidence. I removed the incorrect Windows label and placed this in the high-priority 0.9.1-rc query/call-resolution queue under #594. The RED guard should cover C/C++ pointer and instance receivers independently, prove the enclosing Method remains the caller, and distinguish a missing edge from an edge incorrectly attributed to a File or Module node.
sreehari-malapati-myprod commentedon Aug 1, 2026
@DeusData ..
Relates to / extends #1153 (method calls through object instances/pointers dropped in caller edges).
Summary:
A CALLS caller edge is missing for a method invoked through a static factory return — SecdManager::getInstance()->secd_retrieve_pic_fpc_i2c_id(...). The callee is non-virtual (statically bound), so the edge is resolvable from the static return type of getInstance() alone, with no polymorphic/vtable analysis. This broadens #1153 from local pointer/instance receivers to factory/accessor-return receivers (getInstance()->foo(), getX()->foo()).
Impact:
query_graph caller queries and trace_path --direction upstream report 0 callers for a function that clearly has one, breaking impact analysis / reverse call-graph for the very common singleton pattern.
Symbol:
secd_retrieve_pic_fpc_i2c_id — C++ method SecdManager::secd_retrieve_pic_fpc_i2c_id, non-virtual, is_entry_point=false
qualified_name = master.securityd.common.app.src.SecdManager.SecdManager.secd_retrieve_pic_fpc_i2c_id
Where it exists (OpenGrok full:secd_retrieve_pic_fpc_i2c_id, 3/3 hits):
decl: a/b/app/src/SecdManager.h:164
def : a/b/app/src/SecdManager.cpp:2257
call: a/b/pil/src/SecdCfgHandlerThread.cpp:237
Where it's called:
Inside GetExternalPhyPresent(), via a singleton factory return:
// securityd/common/pil/src/SecdCfgHandlerThread.cpp:237
if(!SecdManager::getInstance()->secd_retrieve_pic_fpc_i2c_id(ifdh, fpc_id, pic_id)) {
}
How it was missed:
query_graph — caller query returns 0 (edge never created):
MATCH (caller)-[r:CALLS]->(f:Method)
WHERE f.name='secd_retrieve_pic_fpc_i2c_id' AND f.file_path CONTAINS 'SecdManager.cpp'
RETURN caller.name, caller.file_path, r.line -> {"rows":[],"total":0}
Also 0 with exact qualified_name, with (caller:Method), and via USAGE-incoming.
trace_path — --direction upstream (and both) reports 0 callers / in_degree=0 for the same function.
Proof it's a real drop, not a bad query:
The mirror callee direction, same anchor, returns 9 rows -> the anchor resolves fine; only the incoming edge is missing:
MATCH (f:Method)-[r:CALLS]->(c)
WHERE f.name='secd_retrieve_pic_fpc_i2c_id' AND f.file_path CONTAINS 'SecdManager.cpp'
RETURN c.name, r.line ORDER BY r.line -> 9 rows (getFpc, getPic, fpc_map, ifdo_hndl, ...)
Expected:
A CALLS edge GetExternalPhyPresent -> SecdManager::secd_retrieve_pic_fpc_i2c_id. Both the caller query and trace_path --direction upstream should list GetExternalPhyPresent (SecdCfgHandlerThread.cpp:237).
Fix scope:
Receiver is a factory-return (getInstance()->) and callee is non-virtual -> resolvable from the static return type alone (no vtable analysis). Fix should cover factory()->method() / accessor()->method() receivers, not only local pointer/instance variables.
can you please let me know when can we expexct 0.9.1-rc
Limebird01 commentedon Aug 1, 2026
Confirming this also reproduces for Python instance-variable method calls on v0.9.0 (linux-amd64, portable static binary from the GitHub release archive), so it is not limited to C/C++ pointer receivers.
Minimal dummy repro:
Observed on a mid-size Python repo indexed with
--mode full:trace_pathonStackManager.statuswith--direction upstreamreports callers=0, despite the real call site through the instance variable.--direction downstream) is accurate, including cross-file — so the anchor resolves fine and only the incoming CALLS edge is missing, matching the reports above.So the RED guard for the 0.9.1-rc fix should cover Python instance receivers (
obj = Cls(); obj.method()) in addition to the C/C++ pointer and factory-return cases.DeusData commentedon Sep 19, 2026
Thank you for the additional C++ factory-return and Python instance-call examples. They broaden the receiver forms that a fix must cover. v0.11.0 is now published, but I have not verified that these exact cases are fixed there, so I cannot offer a fix-version promise or close this based on release age.
BigBrotherHui commentedon Sep 21, 2026
Answering your 2026-09-19 question about v0.11.0 directly: no, these cases are not fixed in v0.11.0 — and I have the root cause.
I reproduced on the v0.11.0 release binary (
codebase-memory-mcp 0.11.0, windows-amd64, official archive,install.ps1+ checksum verified), cleanmode=fullindex, and cross-checked the same case on 0.10.8.Root cause: an export macro between
classand the class name demotes the class to aFunctionnodeObserved:
Remove the macro and the same tree produces
Class+DEFINES_METHOD+ both CALLS edges:worker.hdeclaresWorkernode labelw->isActive()edgeclass Worker {Classlsp_base_dispatchclass MYMODULE_EXPORT Worker {FunctionIdentical with and without a base clause (
class MYMODULE_EXPORT Worker {vs... : public QObject {), and with and without a#includeof the defining header, so the macro is the whole discriminator. This isextract_defs.c(class-declaration recognition), notc_lsp.ccall resolution: the LSP side never gets a type to resolve, because noClassnode exists andDEFINES_METHODis empty.The forward declaration is a secondary symptom, not the cause
While narrowing this I initially blamed forward declarations, because the misattributed
Classnode you can see in a real repo usually sits at a forward decl. The actual sequence is:Function(macro), andClassnode that survives is the one minted fromclass X;in some other header,so
ptr->method()resolves against a type qualified name under the wrong file prefix and finds no member. Fixing (1) removes the observable symptom; a separate guard for (2) would be worth having.Receivers confirmed to work, so the guard does not need to cover them
Same tree, same index, only the receiver form varies:
this->ensureWorker()lsp_type_dispatch0.95Worker *w = this->ensureWorker(); w->isActive();(local pointer)lsp_base_dispatchWorker stack; stack.isActive();(stack object)Worker *fresh = new Worker(); fresh->isActive();(heap)#include "worker.h"in the caller's header, no macroLocal pointers, factory/accessor returns, and Python instance receivers (the case in the 2026-08-01 comment, id 5149866495) all resolve here. The macro is what breaks them.
Real-repo impact
Modules/Planning/Registration/YSRegistration, a ~1,200-file C++/Python repo:qSlicerYSRegistrationModuleWidget.cxxhas 17workflow->member calls across 11 distinct targets, and not one of those 17 calls produced an edge. The file does contribute 28 CALLS edges, but every one of them targets a different symbol; the only inbound edges those 11 targets have come from other files. So 100% of that file's pointer member-call edges are missing.qSlicerOralScanWorkflow:Classnode lives atqSlicerYSRegistrationModuleWidget.h(forward decl), while its methods live under...YSRegistration.Widgets.qSlicerOralScanWorkflow.…. Different QN prefixes, sotrace_path --direction inboundonisActive/showExistingFusionResultreturnscallers_total: 0— a false dead-code signal.*_EXPORTmacro betweenclassand the name; each one is a latent instance.Classnodes, 50 point at a file that only forward-declares the name while the real definition is elsewhere (vtkSlicerImplantLogic,vtkMRMLMarkupsCrownNode,vtkMRMLMarkupsImplantNode,vtkSlicerYSRegistrationLogic,vtkMRMLMarkupsAimingDisplayNode, …).Not related to #220 — I checked the edge source label distribution and it is 100%
Method, so these edges are genuinely absent, not re-attributed to aFile/Modulenode.Suggested RED guards
class <IDENT> <NAME>(i.e. an uppercase macro token between the keyword and the declared name) still yieldsClass+DEFINES_METHOD+ resolvable member calls.class X;forward declaration in file A never mints theClassnode when a real definition ofXexists anywhere in the index; the definition wins.Happy to open a PR if a pointer to the right spot in
extract_defs.cwould help — but the RED test above is probably the higher-value half.(The three-file repro above is self-contained — I can paste the exact tree, or the wider A/B matrix I used to isolate the macro, if either would help.)
DeusData commentedon Sep 21, 2026
@BigBrotherHui — thank you, that root cause is exactly right, and it arrived on the same day as its fix. An export macro between
classand the class name made the extractor readclass MYMODULE_EXPORT Worker {as a function returningclass MYMODULE_EXPORT, soWorkerbecame aFunctionnode andw->isActive()had no class to dispatch through. That is the defect #2005 (issue #1989, by @XIYBHK) fixes, and it is now onmainas c7488b0.I ran your three files, plus a control copy without the macro, through
mainbefore the fix and through the fix:WorkercallItreturn_type: "class MYMODULE_EXPORT")USAGE -> Function Worker-> Worker.isActive(lsp_base_dispatch, 0.95)-> Worker.isActive(lsp_base_dispatch, 0.95)With the fix, the macro fixture's graph is identical to the control's — same labels, same edge properties, same totals. It ships in the next patch release; until then a build from
mainhas it.Two honest limits: this is one macro shape (a bare identifier between
classand the name — the conventional export-macro form the fix targets; macros with arguments, e.g.EXPORT(x), are not covered by the zero-config rule and are the follow-up tracked on #1989), and this issue's original report listed more than one pattern. @sreehari-malapati-myprod — could you re-check your original cases against a build frommain? If they all resolve, I will close this; if one does not, that one is a second cause and deserves its own reproduction here.DeusData commentedon Sep 26, 2026
Thank you, @sreehari-malapati-myprod, @Limebird01 and @BigBrotherHui! The export-macro fix is on main (c7488b0) and ships in the next patch release.
Retesting the other shapes on main, local pointers, heap and stack objects, and in-function Python instances all resolve. Two shapes still drop the edge:
SecdManager::getInstance()->retrieve(…):getInstanceis linked,retrieveis not.manager = StackManager(); manager.status(): only the constructor is linked. The same call inside a function resolves.We'll track these two here.
DeusData commentedon Oct 1, 2026
Thank you again, @sreehari-malapati-myprod, @Limebird01 and @BigBrotherHui. Your reports pinned this down precisely.
The C++ static-factory case is fixed in #2466, and the cause was more specific than "factory returns":
SecdManager::getInstanceonly under the calling file's own module.->secd_retrieve_pic_fpc_i2c_id(...)call after it had nothing to dispatch on.The fix resolves the class across files, but only when exactly one class carries that name. An ambiguous name stays unresolved rather than being guessed. It covers raw pointers, references,
std::unique_ptrandstd::shared_ptrreturns. On qBittorrent it adds 489 correct edges, mostlyPreferences::instance()->….The Python module-level case (
manager = StackManager(); manager.status()) has a different cause: module-level calls never joined their type resolution. That is fixed in a separate Python change. I'll post here when both are onmain.One honest limit: namespaced singletons such as
RSS::Session::instance()->…still drop, because forward declarations currently create extra class nodes. That needs its own fix, and I'll track it separately.Merge pull request #2466 from DeusData/fix/issue-1153-instance-receivers
fix(cpp): resolve Class::member calls across files (DeusData#1153)
DeusData commentedon Oct 17, 2026
Thank you again, @sreehari-malapati-myprod, @Limebird01 and @BigBrotherHui. Your reports, and the way you narrowed them down to the exact receiver shapes, are what made these fixes possible.
Both remaining shapes are now fixed on
mainand shipped in v0.12.0:SecdManager::getInstance()->retrieve(…)): fixed in cdd8c89 (fix(cpp): resolve Class::member calls across files (#1153) #2466). The cross-file C++ resolver now findsClass::memberthrough a class declared in another file's header, as long as exactly one class carries that name. The returned type is therefore known, and the member call after it gets its CALLS edge.manager = StackManager(); manager.status()): fixed in bf5ea5a. Module-level calls now join their LSP resolution in both pipeline modes, just as calls inside functions already did.Together with the export-macro fix from #2005 (c7488b0, thanks to @XIYBHK), every shape reported here is covered, so I'm closing this issue.
One honest limit hasn't changed since my last update: namespaced singletons such as
RSS::Session::instance()->…can still lose the edge, because a forward declaration currently creates an extra class node. That belongs with the C++ forward-declaration work (#1970), not here.If any of your original cases still misses an edge on v0.12.0, please comment here and I'll reopen it.
sreehari-malapati-myprod commentedon Oct 23, 2026
Direct same-file calls to static functions produce zero CALLS edges (empty trace_path callees) -its basic feature issue.
For a C function that makes direct, same-translation-unit calls to static helper functions, the indexer creates no CALLS edges. trace_path --direction outbound returns an empty callees list even though the source clearly contains the calls. Reproduced on both v0.9.x and v0.12.1.
Environment
cbm versions: 0.9.x and 0.12.1 (same result)
Host: Linux, --mode full --persistence true
Source evidence (calls are unambiguous in the body) :
int s3ip_sysfs_psu_drivers_register(struct s3ip_sysfs_psu_drivers_s *drv)
{
...
ret = psu_root_create(); // direct call
if (ret < 0) { ...; return ret; }
}
The callees are static functions defined in the same file,:
static void psu_root_remove(void) { ... } // line 914
static void psu_sub_remove(void) { ... } // line 881
prd3://data/codebase/.local/bin# HOME=/data/codebase ./codebase-memory-mcp cli trace_path
--project 25.2.branch
--function-name " 25.2.branch.platform.s3ip-sysfs.s3ip_sysfs_frame.psu_sysfs.s3ip_sysfs_psu_drivers_register"
--direction outbound --depth 1
level=info msg=mem.init budget_mb=192775 total_ram_mb=385550
{"function":" 25.2.branch.platform.s3ip-sysfs.s3ip_sysfs_frame.psu_sysfs.s3ip_sysfs_psu_drivers_register","direction":"outbound","callees":[]}
prd3://data/codebase/.local/bin# HOME=/data/codebase ./codebase-memory-mcp cli trace_path --project 25.2.branch --function-name " 25.2.branch.platform.s3ip-sysfs.s3ip_sysfs_frame.psu_sysfs.s3ip_sysfs_psu_drivers_register" --direction both --depth 1
level=info msg=mem.init budget_mb=192775 total_ram_mb=385550
{"function":"25.2.branch.platform.s3ip-sysfs.s3ip_sysfs_frame.psu_sysfs.s3ip_sysfs_psu_drivers_register","direction":"both","callees":[],"callers":[]}
Root cause (hypothesis)
Because each static helper name has multiple identical copies across the repo, the call resolver cannot prove a unique global target and drops the edge. Per the README, ambiguous targets are retained as USAGE — but here even USAGE-to-callee is absent, so the call is dropped entirely. The resolver appears to skip same-file (translation-unit) scoping: a static callee should resolve to the local definition in the same file before the global uniqueness check.
Suggested fix
For C/C++, resolve static (internal-linkage) calls against same-file definitions first, so a duplicated static name resolves to its local translation-unit target and emits a CALLS edge, instead of being dropped as ambiguous.
Impact
Any C file with static helpers whose names are duplicated elsewhere in the repo loses its intra-file call graph — trace_path, call-hierarchy, and impact analysis silently return empty results (false negatives), which is worse than an error because it looks like "no call
sreehari-malapati-myprod commentedon Oct 23, 2026
in 0.12.1 -> more parsing errors in latrest release many files are skipped with partial parsing
codebase-memory-mcp index coverage report
project=25.2.branch skipped=3354 parse_partial=3715
in 0.9.
codebase-memory-mcp index skip report
project=25.2.branch skipped=2