Skip to content

3.1.0: the level-4 points-to DDG tier now depends on --external-calls; a plain -a 4 loses 276 edges #240

Open

Description

@rahlk

Is your feature request related to a problem? Please describe.

In 3.1.0 the level-4 points-to dependence tier is only computed when --external-calls is passed. Before 3.1.0 it was unconditional.

Four runs over the same daytrader8 tree, same -a 4, only the flag varying:

run ddg prov == ["points-to"]
3.0.3 -a 4 10,430 1,134
3.1.0 -a 4 10,154 858
3.1.0 -a 4 --external-calls 10,430 1,134
3.1.0 --emit neo4j 10,430 1,134

276 points-to edges disappear from a plain -a 4 run at 3.1.0 that were present at 3.0.3.

Describe the solution you'd like

Not stated in the original issue.

Describe alternatives you've considered

Not stated in the original issue.

Additional context

Why it looks like a defect rather than a design change

--external-calls is documented as controlling whether out-of-project call targets are homed — that is, whether external_symbols is populated. Nothing in its description suggests it gates a dependence-analysis tier. A caller who wants points-to dataflow has no reason to think they need a flag about external symbol resolution, and the two runs report the same max_level either way, so nothing in the output says a tier is missing.

Downstream effect, already observable

--emit neo4j forces --external-calls, and python-sdk's in-process backend never passes it. So on 3.1.0 the graph is a strict superset of the JSON payload by exactly those 276 edges, and the same value slice reaches further over Neo4j than in process — for the same project, at the same analysis level. Three python-sdk parity tests had to move from equality to containment as a result.

That asymmetry did not exist at 3.0.3, and it is the kind that is hard to notice: both answers look plausible, and neither is empty.

Expected

Either the points-to tier is computed at -a 4 regardless of --external-calls, or — if the coupling is deliberate — the flag's documentation says so and the payload records which tiers were computed, so a consumer can tell a missing tier from an absent dependence.

Repro

codeanalyzer -i <project> -a 4 -o out-plain
codeanalyzer -i <project> -a 4 --external-calls -o out-flag

Count ddg entries whose prov is ["points-to"] in each analysis.json.

Environment

codeanalyzer-java 3.0.3 and 3.1.0, PyPI wheels with the bundled JVM, macOS 15 arm64. Measured while taking 3.1.0 up in python-sdk (#369).

Activity

  1. rahlk commented on Sep 8, 2026

    @rahlk
    CollaboratorAuthor

    Fixed in 3.1.1. Verified against the released wheel, same tree, same command as the original report:

    run ddg points-to
    3.0.3 -a 4 10,430 1,134
    3.1.0 -a 4 10,154 858
    3.1.1 -a 4 (no --external-calls) 10,420 1,134

    So the plain -a 4 payload carries the full points-to tier again, and it agrees with what --emit neo4j projects. The downstream asymmetry this caused is gone with it: python-sdk's live suite had three parity assertions weakened from equality to containment because the graph was a strict superset of the payload by 276 points-to edges, and those are going back to equality.

    (The 10,420 against 3.0.3's 10,430 is a separate and expected 10 edges, not a regression of this issue — the two releases resolve a few calls differently, which moves the level-4 port vertices with them.)

    Closing unless you would rather keep it open for the documentation half — the flag's description still does not say what it does and does not gate, which is what made this hard to spot in the first place.

  2. rahlk commented on Sep 8, 2026

    @rahlk
    CollaboratorAuthor

    Correcting one number in my previous comment: the 3.1.1 plain -a 4 payload has 10,430 ddg edges, not 10,420. The conclusion is unchanged and in fact stronger.

    I had counted with a hand-written walk that recursed into nested types but missed types declared inside callables — the same defective-walk trap that has bitten this project before. Re-counted through the SDK's own flattened index: 10,430, and the payload's edge set is now exactly equal to the graph's, set for set, not merely the same size.

    So 3.1.1 restores full parity between -a 4 and --emit neo4j:

    run ddg points-to
    3.0.3 -a 4 10,430 1,134
    3.1.0 -a 4 10,154 858
    3.1.1 -a 4 10,430 1,134

    python-sdk's three parity assertions are back to equality, and the tolerance that carried them for one release is deleted rather than left loose. One of them had a tripwire asserting the gap still existed, which is what flagged that the workaround was obsolete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions