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

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions