jacoco / jacoco/jacoco

After upgrading kotlin 2.3.21 -> 2.4.10, compose branch coverage drop down

Open
#2,210 0 comments 0 reactions 1 assignee View on GitHub

@Godin is already working on this.

Since Jul 20, 2026.

type: bug :bug:
Dominant language
Java
Stars
4.6k
Forks
1.2k
Avg merge
1d 9h
Merged PRs (30d)
23

Description

Steps to reproduce
  1. Checkout repo
  2. Run ./gradlew test jacocoTestReport
  3. Open build/reports/jacoco/test/html/org.example/index.source.html
Image 4. Change kotlin version to 2.3.21 5. Run `./gradlew test jacocoTestReport` and refresh browser Image
Expected behaviour

Give same amount of branch coverage

Actual behaviour

Increases number of branches, but coverage decreases

Extra info

Kotlin upgrade in our product cause significant coverage drop, which is enough to prevent upgrade to kotlin. Sometimes it was +120 branches per composable. I tried to make small as possible repro.

I wasn't sure at first whether this is the right place to report it, but similar problems were fixed here in #1616 and #1911. Let me know if it belongs elsewhere.

I have no idea if it even makes sense, but AI explained it like this:

AI Analysis

The difference is in the lambda memoization check inside the CC(remember) region (between sourceInformationMarkerStart/End). javap -c of ExampleKt.example, the check for the captured state parameter:

Kotlin 2.3.21 — an unconditional changedInstance() call, no extra branches, which roughly corresponds to:

val invalidated = $composer.changedInstance(state) or
        ($changed and 0b1110000 == 0b0100000)  // onDone

Kotlin 2.4.10 — the same capture now consults the caller-provided $changed bits before falling back to changedInstance(), adding three conditionals, which roughly corresponds to:

val invalidated = ($changed and 0b0001110 == 0b0000100 ||
        ($changed and 0b0001000 != 0 && $composer.changedInstance(state))) or
        ($changed and 0b1110000 == 0b0100000)  // onDone

The result is used as if (invalidated || rememberedValue === Empty) { create and cache the lambda } else { reuse cached }. Some of these branch outcomes depend on bits computed by the caller, so they cannot be reliably exercised by tests. It would be great if KotlinComposeFilter also filtered this pattern.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.