Skip to content

Kotlin: missing call coverage can produce misleading HIGH concern overload #43

Description

@tuannx

Problem and reproduction

Kotlin's structural graph can produce an unsupported HIGH concern-overload finding when internal calls are not collected. This is a demonstrated coverage/calibration problem for the fixture, not evidence that the parser violates its documented import/inheritance contract or that the real application has only one responsibility.

Reproduction configuration: language=kotlin, algorithm=pkg, exclude_tests=true, use_cache=false, use_llm=false. With MCP, retrieve graph, architecture, smells and metrics through get_full_result in the same session.

Observed with v0.3.0 source on main 1b3532b:

  • Three file imports become nine entity edges across a class and its two methods, including an empty method. These are file-import attributions, not nine resolved usages.
  • A single-computation class has 41 methods and 40 explicit internal calls. The graph records none; its component receives HIGH with 42 entities and zero internal edges.
  • Adding exactly those calls removes the finding.
  • An independent source-import cycle remains detectable. This issue does not dispute the independently evidenced cycle or cover Listener/Domain hub findings.

MultiplayerArt Android at 31327fc159374b947822bff85d314626ba0d4d28 reproduces 1,793 entities / 27,909 edges: 27,842 import, 65 implements, 2 extends, no call edges. Mainactivity receives HIGH with 194 entities / 0 internal edges.

Draft reproduction PR #44 contains the standalone example and a deliberately failing regression test (no xfail). At publication: 3 characterization tests pass and the coverage-guard test fails; the focused suite has 129 passed / 1 deliberately failed. It is not a production fix.

Completed experiments

  • Minimal Java runtime smoke on CheerpJ: CI run, 3/3 Ubuntu jobs passed. This alone did not prove Kotlin semantics.
  • Real Kotlin compile + resolved-source-call experiment: CI run, 2/2 Ubuntu jobs passed, commit 7a9189ebf725546270424c63fb4bdd78513fc3d2 on codex/cheerpj-ci-poc.

Semantic PoC source and instructions:

  • Pinned Kotlin K2 compiler 2.0.21, JDK 17, and an IR compiler plugin collect calls before JVM lowering, including caller/callee, signatures, file, line and provenance.
  • Separate bytecode inspection verifies emitted JVM calls; bytecode is not substituted for source semantics.
  • Native JVM and CheerpJ produce identical 42 resolved source-call records, including 40 internal calls and correctly distinguished Int/Long overloads.
  • A negative fixture produces COMPILATION_ERROR with unresolved-symbol diagnostics.
  • Enriching the fixture's tree-sitter graph with actual compiler edges removes HIGH for the fixed Ui component. Component membership is held constant to isolate the detector from recovery heuristics.
Measurement, two Ubuntu jobs Result
Native successful compile + semantic extraction 2.95–3.00 s
CheerpJ successful compile + semantic extraction 30.51–31.60 s
Entire job, including setup 99–107 s
Downloaded compiler/dependency JAR sizes on disk 65,514,736 bytes (~65.5 MB)
Sampled peak sum of harness/descendant RSS ~1.7 GiB

RSS sums shared pages potentially more than once, covers the harness rather than installation, and is not a separate native-versus-CheerpJ memory comparison. Compile timings exclude runtime initialization and the later negative compilation. Raw JSON and verification artifacts are attached to the run. No dependency cache was restored, but runners include preinstalled tooling.

Limits: builtins-only fixtures, explicit stdlib and -no-jdk; not Android/project-classpath proof. This is a compiler-plugin backend, not Kotlin Analysis API Standalone. Constructors, callable references, complete inline/lambda behavior, dynamic dispatch targets and Java–Kotlin interop remain unvalidated. No production graph integration has shipped.

Proposed direction

Keep tree-sitter as the default lightweight structural path. Add optional native-JVM semantic enrichment, preferably consuming an artifact from an existing Kotlin build so CI does not compile the project twice. CheerpJ works for the tested semantics but is about 10x slower for the successful compile stage here; do not make it the default CI backend.

Installing a JDK is necessary for compiler jobs but does not establish analysis correctness: compiler version, source sets, JDK/Android APIs and dependency classpath must also match. Structural-only jobs should not acquire JVM/browser dependencies.

Implementation plan — separate delivery PRs

1. Coverage contract and detector guard

  • Represent supported/collected relations, parse failures, unresolved/ambiguous calls and analysis scope explicitly.
  • Qualify or suppress unsupported cohesion conclusions with a visible insufficient coverage result; an empty smell list alone must not imply a clean assessment.
  • Keep independently supported import-cycle detection available.
  • Update test: reproduce Kotlin cohesion false positive with failing regression #44's expected-behavior test and characterization assertions as the guard lands; do not hide the regression with xfail or blanket suppression.

2. Graph attribution and compatibility

  • Distinguish file import attribution from resolved entity usage; retain source location and backend provenance.
  • Introduce stable symbol IDs including scope/signature before merging overloads. The PoC's name-only mapping is restricted to uniquely named fixture methods.
  • Preserve coverage/provenance through graph merge, filtering, cache, serialization and MCP full results.
  • Version graph/metric semantics when edge counting changes; prevent invalid comparisons against older baselines.

3. Optional native semantic backend

  • Wrap compiler execution behind a backend interface; pin compatible compiler/plugin versions and package the backend ahead of CI execution.
  • Accept source sets, compiler options and explicit classpath, including JDK/Android dependencies when needed.
  • Support existing-build semantic artifacts; avoid redundant compilation when artifacts match source/configuration.
  • Preserve diagnostics and fail/qualify clearly on compile errors, missing dependencies, timeout or partial coverage.
  • Batch work by module; cache by source, dependency/classpath, compiler-option and backend fingerprints with dependency-aware invalidation.
  • Prototype modes such as structural-only / selective semantic / full semantic without promising compiler-complete coverage.

4. CI and acceptance gates

  • Set up a pinned JDK only in compiler/semantic jobs; keep structural-only jobs lightweight.
  • Test native compile and semantic extraction, not merely Java process startup.
  • Require the 40-call fixture, overload disambiguation, negative compilation and missing-classpath tests.
  • Add Java-source and Java–Kotlin interop fixtures before claiming Java-related analysis correctness; Kotlin success is not proof of a Java semantic backend.
  • Add extension, receiver, shadowing, lambda/inline and source-set coverage fixtures; track unsupported cases explicitly.
  • Validate on a real Android component with its actual build classpath.
  • Benchmark fresh CI setup, initialization, analysis, cache-hit/invalidation paths and memory separately; establish budgets from measurements rather than extrapolating the 3-second fixture result.
  • Run self-analysis against the relevant baseline before each implementation push/PR update and explain metric/component/dependency/smell/A2A deltas.

Completion criteria

The unsupported HIGH regression is addressed with explicit coverage semantics; supported compiler edges are accurate and attributable; compile/classpath failures remain visible; structural-only analysis retains its lightweight dependency path. Java/interop support is claimed only after dedicated tests pass. Keep this issue open while implementation is pending; the successful PoC does not itself fix production analysis.

Activity

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions