Skip to content

[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

Description

@gkastanis

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):

Edge (caller → callee) Actual call in body Why it's wrong
AiLogTraceSource::traceByIdDatasetLoaderInterface::load $this->logStorage()->load($id) (src/Service/AiLogTraceSource.php:90) receiver is the ai_log entity storage (Drupal core EntityStorageInterface::load), not the dataset loader
LabelStore::loadForTraceDatasetLoaderInterface::load $storage->load(reset($ids)) where $storage = getStorage('ai_eval_review_label') (src/Service/LabelStore.php, loadForTrace()) receiver is the review-label entity storage, not the dataset loader
build_viewer.load_logDatasetSourceInterface::load 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::load correct (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 graph
interface DatasetLoaderInterface { public function load(string $ref): array; }

final class TraceSource {
  public function traceById(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.
  • $storage->load() / $this->logStorage()->load() (core/framework methods not in the graph) must not bind to a project load() 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.
  • 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.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/normalStandard review queue; useful PR with ordinary maintainer urgency.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions