Repository: square/leakcanary
File: shark/shark-android/src/main/java/shark/AndroidObjectInspectors.kt (Kotlin)

Code before:
    override fun inspect(reporter: ObjectReporter) {
      reporter.whenInstanceOf("androidx.compose.runtime.Recomposer") { instance ->
        val stateFlow =
          instance["androidx.compose.runtime.Recomposer", "_state"]!!.valueAsInstance!!
        val state = stateFlow["kotlinx.coroutines.flow.StateFlowImpl", "_state"]?.valueAsInstance
        if (state != null) {
          val stateName = state["java.lang.Enum", "name"]!!.valueAsInstance!!.readAsJavaString()!!
          val label = "Recomposer is in state $stateName"
          when (stateName) {
            "ShutDown", "ShuttingDown" -> leakingReasons += label
            "Inactive", "InactivePendingWork" -> labels += label
            "PendingWork", "Idle" -> notLeakingReasons += label
          }
        }
      }
    }
  },

  COMPOSITION_IMPL {
    override fun inspect(reporter: ObjectReporter) {
      reporter.whenInstanceOf("androidx.compose.runtime.CompositionImpl") { instance ->
        // Try the old "disposed" boolean field first (older Compose versions)
        val disposedField = instance["androidx.compose.runtime.CompositionImpl", "disposed"]
        if (disposedField != null) {
          val disposed = disposedField.value.asBoolean
          if (disposed != null && disposed) {
            leakingReasons += "Composition disposed"
          } else {
            notLeakingReasons += "Composition not disposed"
          }
        } else {
          // Try the new "state" field (newer Compose versions)
          // State is an int with constants: RUNNING=0, DEACTIVATED=1, INCONSISTENT=2, DISPOSED=3
          val stateField = instance["androidx.compose.runtime.CompositionImpl", "state"]
          if (stateField != null) {
            val stateValue = stateField.value.asInt
            if (stateValue != null) {
              when (stateValue) {
                3 -> leakingReasons += "Composition disposed" // DISPOSED state
                0 -> notLeakingReasons += "Composition running" // RUNNING state
                1 -> labels += "Composition deactivated" // DEACTIVATED state
                2 -> labels += "Composition inconsistent" // INCONSISTENT state
                else -> labels += "Composition state: $stateValue"
              }
            } else {
              labels += "Composition state field exists but value is null"
            }
          } else {
            // Neither field exists - can't determine composition state
            labels += "Composition disposal state unknown (no disposed or state field)"
          }

Review comment (suggestion):
Remove `disposed != null` and instead use `asBoolean!!` above. If `asBoolean` returned null it would mean that the field exists but is not a boolean, which should never happen here. Let's not treat that case as "composition not disposed"

Code after:
    override fun inspect(reporter: ObjectReporter) {
      reporter.whenInstanceOf("androidx.compose.runtime.Recomposer") { instance ->
        val stateFlow =
          instance["androidx.compose.runtime.Recomposer", "_state"]!!.valueAsInstance!!
        val state = stateFlow["kotlinx.coroutines.flow.StateFlowImpl", "_state"]?.valueAsInstance
        if (state != null) {
          val stateName = state["java.lang.Enum", "name"]!!.valueAsInstance!!.readAsJavaString()!!
          val label = "Recomposer is in state $stateName"
          when (stateName) {
            "ShutDown", "ShuttingDown" -> leakingReasons += label
            "Inactive", "InactivePendingWork" -> labels += label
            "PendingWork", "Idle" -> notLeakingReasons += label
          }
        }
      }
    }
  },

  COMPOSITION_IMPL {
    override fun inspect(reporter: ObjectReporter) {
      reporter.whenInstanceOf("androidx.compose.runtime.CompositionImpl") { instance ->
        // Try the old "disposed" boolean field first (older Compose versions)
        val disposedField = instance["androidx.compose.runtime.CompositionImpl", "disposed"]
        if (disposedField != null) {
          if (disposedField.value.asBoolean!!) {
            leakingReasons += "Composition disposed"
          } else {
            notLeakingReasons += "Composition not disposed"
          }
        } else {
          // Try the new "state" field (newer Compose versions)
          // State is an int with constants: RUNNING=0, DEACTIVATED=1, INCONSISTENT=2, DISPOSED=3
          val stateField = instance["androidx.compose.runtime.CompositionImpl", "state"]
          if (stateField != null) {
            when (val stateValue = stateField.value.asInt!!) {
              3 -> leakingReasons += "Composition disposed" // DISPOSED state
              0 -> notLeakingReasons += "Composition running" // RUNNING state
              1 -> labels += "Composition deactivated" // DEACTIVATED state
              2 -> labels += "Composition inconsistent" // INCONSISTENT state
              else -> labels += "Composition state: $stateValue"
            }
          } else {
            // Neither field exists - can't determine composition state
            labels += "Composition disposal state unknown (no disposed or state field)"
          }
        }
      }
    }
  },

  ANIMATOR {