Skip to content

iris.cls("Pkg.X").Method() ignores the class-name string, producing false and mis-targeted CALLS edges #1260

Description

@isc-tdyar

Summary

In Python code that talks to InterSystems IRIS, iris.cls("Pkg.X").Method() is
the standard way to invoke an ObjectScript class method. CBM creates a CALLS
edge for these but resolves the target by bare method name only, discarding the
class-name string literal.

Consequences:

  • calls resolve to the wrong class when two classes share a method name
  • calls to a class that does not exist in the graph still produce an edge
  • unrelated receivers (anything.Run(...)) produce an edge to whichever class
    happens to define Run

db.classMethodValue("Pkg.X", "Method", ...), the lower-level form this API is
meant to replace, resolves correctly and class-aware. So the form the ecosystem
is being encouraged to adopt is the one handled worst.

Reproduction

Repo with two ObjectScript classes, Pkg.A and Pkg.B, each defining only:

ClassMethod Run(x As %String) As %String
{
    Quit x
}

and one caller.py:

import iris

def want_a():
    return iris.cls("Pkg.A").Run("x")

def want_b():
    return iris.cls("Pkg.B").Run("x")

def want_nonexistent():
    return iris.cls("Pkg.DoesNotExist").Run("x")

def unrelated_receiver():
    return some_random_thing.Run("x")

def via_classmethodvalue(db):
    return db.classMethodValue("Pkg.B", "Run", "x")

Query:

MATCH (a)-[r:CALLS]->(b) RETURN a.name, b.qualified_name

Observed:

  unrelated_receiver     Pkg.A.Run     <-- false edge
  via_classmethodvalue   Pkg.B.Run     <-- correct (class-aware path)
  want_a                 Pkg.A.Run     <-- correct, but only coincidentally
  want_b                 Pkg.A.Run     <-- WRONG CLASS
  want_nonexistent       Pkg.A.Run     <-- false edge

Four of the five iris.cls and bare-receiver edges are wrong. The one correct
row is the classMethodValue form.

Root cause

iris.cls(...) is not recognized as a dispatch receiver, so the call falls
through to generic bare-method-name matching. The class string sits right there
as a literal argument at the call site and is never consulted.

The machinery already exists for the sibling API.
internal/cbm/extract_calls.c:65 defines s_py_dispatch_suffixes for
classMethodValue / classMethodVoid / classMethodBoolean /
classMethodObject, resolved class-aware at :2301-2318.

Suggested fix

  1. When a call's receiver is itself a call to iris.cls / cls with a
    string-literal first argument, synthesize the callee qualified name as
    "<class-literal>.<method>" and resolve it the way classMethodValue
    already is.
  2. Suppress the generic bare-name fallback for that call node, so an
    unresolvable class yields no edge rather than a wrong one.
  3. Separately, invokeClassMethod is missing from s_py_dispatch_suffixes
    (extract_calls.c:65-66) though it is documented alongside the
    classMethod* family. One-line addition, same resolution path.

Item 2 matters most. A missing edge is recoverable; a confidently wrong edge is
not.

Scope note

This sits entirely within CBM's own extraction code, no vendored files
involved. It is independent of Issues A and C and can land on its own.

Tests

I'd add cases to the existing Python call-extraction group in
tests/test_extraction.c covering correct class selection with two same-named
methods, no edge for an unknown class literal, no edge for a non-iris.cls
receiver, and invokeClassMethod resolution.

Happy to open a PR once you confirm the approach, particularly the
"suppress the bare-name fallback" decision, since it makes CBM emit strictly
fewer edges here and I want to be sure that's the behavior you want.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions