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
[bug] PHP: framework/core method calls ($storage->load()) become false-positive CALLS edges to same-named project methods — same class as the (fixed) Perl #476 #606
Version: codebase-memory-mcp v0.8.1 (Linux amd64, release binary) Project: real-world Drupal 11 / PHP module, 2,869 nodes / 7,069 edges Public fixture: the repo is public at https://git.drupalcode.org/project/ai_eval (clone + index_repository, full mode)
Summary
On PHP, the generic short-name call resolver wires framework / core method calls to unrelated project methods that merely share a method name, ignoring the receiver's type. The real callee (a Drupal core method like EntityStorageInterface::load()) is not a node in the graph — it's an external dependency — so the resolver picks the only same-named definition it can see (a project method) and fabricates a CALLS edge.
This is the same root cause already fixed for Perl in #476/#477 ("the textual resolver … falls back to a generic short-name matcher with no language or call-kind awareness … wires framework method calls … to unrelated project subs that merely share a name"), and the same name-vs-receiver defect reported for the recursion detector in #599. The fix in #466 does not cover this case: #466 ranks among same-named definition nodes and reports ties, but here there is no tie — the correct target is out-of-graph entirely, so #466 has nothing to prefer.
Repro
MATCH (caller)-[:CALLS]->(m)
WHERE m.qualified_name ENDS WITH 'DatasetLoaderInterface.load'
RETURN caller.qualified_name
Three representative false positives (verified against source — none of these call the dataset loader):
a Python function in skills/view-results/scripts/build_viewer.py
cross-language edge: a Python function cannot call a PHP interface method
Inconsistency worth noting: the resolver gets EvalRunner::doStartRun/runTarget → DatasetLoaderInterface::loadcorrect (those genuinely call $this->datasetLoader->load()) while getting AiLogTraceSource/LabelStore wrong — so identical-looking ->load() call sites resolve to the right or wrong target non-deterministically from the consumer's point of view.
Minimal example
// Project interface — the only `load()` definition node in the graphinterface DatasetLoaderInterface { publicfunctionload(string$ref): array; }
finalclass TraceSource {
publicfunctiontraceById(int$id): ?array {
// receiver is a Drupal core EntityStorageInterface — NOT in the graph.// Today this emits a phantom CALLS edge to DatasetLoaderInterface::load.return$this->logStorage()->load($id);
}
}
Expected
A CALLS edge should be emitted only when the call resolves to the same method on a compatible receiver type. When the receiver type is unknown/external, prefer no edge (or a low-confidence/unresolved marker) over a fabricated edge to a same-named project method.
Cross-language edges (Python caller → PHP callee) should never be produced.
Impact
trace_path (inbound) and any query_graph over CALLS return phantom callers for the affected methods, so caller-set / impact-analysis answers are wrong for the most common Drupal/PHP idiom (->load(), and by extension ->save(), ->get(), ->create(), ->delete() — all core storage/entity methods). PHP call-graph coverage on the tracker is currently thin (only Blade #258 and a vendor-exclude #234, both closed), so this is offered as a clean, public PHP/Drupal fixture for the same fix #476 shipped for Perl.
Version: codebase-memory-mcp v0.8.1 (Linux amd64, release binary)
Project: real-world Drupal 11 / PHP module, 2,869 nodes / 7,069 edges
Public fixture: the repo is public at
https://git.drupalcode.org/project/ai_eval(clone +index_repository, full mode)Summary
On PHP, the generic short-name call resolver wires framework / core method calls to unrelated project methods that merely share a method name, ignoring the receiver's type. The real callee (a Drupal core method like
EntityStorageInterface::load()) is not a node in the graph — it's an external dependency — so the resolver picks the only same-named definition it can see (a project method) and fabricates aCALLSedge.This is the same root cause already fixed for Perl in #476/#477 ("the textual resolver … falls back to a generic short-name matcher with no language or call-kind awareness … wires framework method calls … to unrelated project subs that merely share a name"), and the same name-vs-receiver defect reported for the recursion detector in #599. The fix in #466 does not cover this case: #466 ranks among same-named definition nodes and reports ties, but here there is no tie — the correct target is out-of-graph entirely, so #466 has nothing to prefer.
Repro
Three representative false positives (verified against source — none of these call the dataset loader):
AiLogTraceSource::traceById→DatasetLoaderInterface::load$this->logStorage()->load($id)(src/Service/AiLogTraceSource.php:90)EntityStorageInterface::load), not the dataset loaderLabelStore::loadForTrace→DatasetLoaderInterface::load$storage->load(reset($ids))where$storage = getStorage('ai_eval_review_label')(src/Service/LabelStore.php,loadForTrace())build_viewer.load_log→DatasetSourceInterface::loadskills/view-results/scripts/build_viewer.pyInconsistency worth noting: the resolver gets
EvalRunner::doStartRun/runTarget → DatasetLoaderInterface::loadcorrect (those genuinely call$this->datasetLoader->load()) while gettingAiLogTraceSource/LabelStorewrong — so identical-looking->load()call sites resolve to the right or wrong target non-deterministically from the consumer's point of view.Minimal example
Expected
CALLSedge should be emitted only when the call resolves to the same method on a compatible receiver type. When the receiver type is unknown/external, prefer no edge (or a low-confidence/unresolved marker) over a fabricated edge to a same-named project method.$storage->load()/$this->logStorage()->load()(core/framework methods not in the graph) must not bind to a projectload()purely on short-name match — exactly the call-kind awareness that Perl call graph polluted by false-positive CALLS edges (builtins, framework method calls, config strings) #476 added for Perl, applied to PHP.Impact
trace_path(inbound) and anyquery_graphoverCALLSreturn phantom callers for the affected methods, so caller-set / impact-analysis answers are wrong for the most common Drupal/PHP idiom (->load(), and by extension->save(),->get(),->create(),->delete()— all core storage/entity methods). PHP call-graph coverage on the tracker is currently thin (only Blade #258 and a vendor-exclude #234, both closed), so this is offered as a clean, public PHP/Drupal fixture for the same fix #476 shipped for Perl.Related
self_recursive/unguarded_recursionmatch calls by bare name →super().save()and same-named wrappers are false positives #599 — same name-vs-receiver root cause, recursion detector