Skip to content

fix/neo4j: lossless field, import & call-graph projection (#156, #157, #158) + relative-path build fix - #159

Merged
rahlk merged 2 commits into
mainfrom
fix/issues-156-157-158
Jun 23, 2026
Merged

rahlk merged 2 commits into
mainfrom
fix/issues-156-157-158

Conversation

@rahlk

@rahlk rahlk commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Fixes #156, #157, #158, all in the --emit neo4j projection path, plus a coupled build-path papercut found while reproducing #158.

#156 — every JField collapses into one node

projectField keyed the node id by Field.getName(), which is always null for a field declaration (the IR keys fields by their variables list). All fields of a class collapsed to a single <fqn>#field#null node under the j_field_id unique constraint. Now keyed by the joined variable names with a positional fallback.

  • daytrader8: #field#null 122 → 0; distinct JField ids → 339.

#157 — imports lose the type name

Single-type imports were stripped to a :JPackage, and multiple imports from one package collapsed to one edge. Now non-wildcard, non-static imports link to a :JType (materializing an external ghost when it isn't a project type); wildcard/static-member imports keep :JPackage; and the full path rides on every J_IMPORTS edge. Adds path to the J_IMPORTS schema and regenerates schema.neo4j.json.

  • daytrader8: J_IMPORTS → JType 0 → 1470, → JPackage 771 → 9 (just wildcards).

#158 — J_CALLS undercount

The call-graph vertex signature rewrites constructors from <init>(...) to the simple class name for readability, while the :JCallable node id keeps the raw <init>(...) signature — so every constructor call edge was gated out. Key J_CALLS endpoints off callable_declaration, which preserves the symbol-table signature verbatim (verified: zero ambiguity conflicts).

  • daytrader8: J_CALLS 1562 → 1715 (~97% of call-graph edges with both endpoints present). The remainder are synthetic/implicit callables with no source node (implicit default ctors, access$ accessors, bridge methods, anonymous classes) — correctly dropped.

Note: the "287 edges / 14%" reported in #158 against 2.4.0 does not reproduce on current HEAD — projection was already at ~75%; this recovers the constructor edges to ~97%.

Relative-path build fix (found while reproducing #158)

  • A relative --input doubled the project path: the build runs with its working dir at the project root, but gradleBuild passed a relative -p (maven was already absolutized, gradle wasn't), resolving to <root>/<root> → build fails → 0 application classes. Normalized to absolute once at the build entry point.
  • That failure was then swallowed: buildProjectAndStreamClassFiles returns an empty list (never null) on failure, but ScopeUtils only guarded == null, so analysis proceeded with an empty scope and died with a cryptic WALA "Could not create entrypoint callsites". Now guards emptiness and reports the real cause.

Tests

rahlk added 2 commits June 22, 2026 21:53
, #158)

Three bugs in the `--emit neo4j` projection (GraphProjector) that silently
dropped IR data the `--emit json` backend preserves:

- #156: every :JField shared the id `<fqn>#field#null` because the node was
  keyed by Field.getName(), which is always null for a field declaration (the
  IR keys fields by their `variables` list). All fields of a class collapsed to
  one node under the j_field_id unique constraint. Key the id by the joined
  variable names, with a positional-index fallback.

- #157: single-type imports were stripped to a :JPackage and multiple imports
  from one package collapsed to a single edge, losing the type name. Link
  non-wildcard, non-static imports to a :JType (materializing an external ghost
  when it is not a project type), keep :JPackage for wildcard/static-member
  imports, and carry the full import `path` on every J_IMPORTS edge. Adds the
  `path` property to the J_IMPORTS schema and regenerates schema.neo4j.json.

- #158: constructor call edges were gated out because the call-graph vertex
  `signature` rewrites <init>/<clinit> to the simple class name for readability,
  while the :JCallable node id keeps the raw <init> signature. Key J_CALLS
  endpoints off `callable_declaration`, which preserves the symbol-table
  signature verbatim. On daytrader8 this recovers all resolvable constructor
  edges (J_CALLS 1562 -> 1715; ~97% of call-graph edges with both endpoints
  present). The remainder are synthetic/implicit callables with no source node
  (implicit default ctors, access$ accessors, bridge methods, anonymous
  classes), which are correctly dropped.

Adds GraphProjectorCallGraphTest covering constructor resolution, gating of
unresolved targets, and the signature fallback.
…ailures

Two coupled papercuts in the level-2 build path:

- A relative `--input` doubled the project path. The build runs with its working
  directory set to the project root, but gradleBuild passed the raw (relative)
  `-p <projectPath>` (maven was already absolutized, gradle was not), so it
  resolved against the root to `<root>/<root>`, the build failed, and zero
  application classes loaded. Normalize the path to absolute once at the
  buildProjectAndStreamClassFiles entry point so every downstream tool is
  consistent.

- A failed build was swallowed: buildProjectAndStreamClassFiles returns an empty
  list (never null) on failure, but ScopeUtils only guarded `== null`, so
  analysis proceeded with an empty scope and died later with a cryptic WALA
  "Could not create entrypoint callsites" error. Guard on emptiness too and
  report the real cause (build failed / no compiled classes / bad input path).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--emit neo4j: all JField nodes share id <fqn>#field#null, collapsing every field of a class into one node

1 participant