main

square/leakcanary

Last updated at: 29/12/2023 09:38

HeapAnalysis.kt

TLDR

The provided file, "HeapAnalysis.kt", contains a Kotlin class hierarchy for representing the result of an analysis performed by a "HeapAnalyzer". The hierarchy includes the following classes:

  • "HeapAnalysis": The base class representing the result of the analysis. It includes properties for the heap dump file, creation time, dump duration, and analysis duration.
  • "HeapAnalysisFailure": A subclass of HeapAnalysis, representing the case when the analysis did not complete successfully. It includes an exception object that wraps the actual exception thrown.
  • "HeapAnalysisSuccess": A subclass of HeapAnalysis, representing the case when the analysis was successful. It includes additional properties such as metadata, application leaks, library leaks, and unreachable objects.
  • "Leak": The base class for representing a leak found by the HeapAnalyzer. It includes properties for leak traces and retained heap byte size.
  • "LibraryLeak": A subclass of Leak, representing a known leak in library code. It includes properties for the leak traces, pattern, and description.
  • "ApplicationLeak": A subclass of Leak, representing a leak found in the application code. It includes properties for the leak traces.

Classes

HeapAnalysis

The base class representing the result of an analysis performed by a HeapAnalyzer. It includes properties for the heap dump file, creation time, dump duration, and analysis duration.

HeapAnalysisFailure

A subclass of HeapAnalysis, representing the case when the analysis did not complete successfully. It includes an exception object that wraps the actual exception thrown.

HeapAnalysisSuccess

A subclass of HeapAnalysis, representing the case when the analysis was successful. It includes additional properties such as metadata, application leaks, library leaks, and unreachable objects.

Leak

The base class for representing a leak found by the HeapAnalyzer. It includes properties for leak traces and retained heap byte size.

LibraryLeak

A subclass of Leak, representing a known leak in library code. It includes properties for the leak traces, pattern, and description.

ApplicationLeak

A subclass of Leak, representing a leak found in the application code. It includes properties for the leak traces.

Methods

No methods in the provided file.

package shark

import shark.internal.createSHA1Hash
import java.io.File
import java.io.Serializable

/**
 * The result of an analysis performed by [HeapAnalyzer], either a [HeapAnalysisSuccess] or a
 * [HeapAnalysisFailure]. This class is serializable however there are no guarantees of forward
 * compatibility.
 */
sealed class HeapAnalysis : Serializable {
  /**
   * The hprof file that was analyzed.
   */
  abstract val heapDumpFile: File

  /**
   * The [System.currentTimeMillis] when this [HeapAnalysis] instance was created.
   */
  abstract val createdAtTimeMillis: Long

  /**
   * Total time spent dumping the heap.
   */
  abstract val dumpDurationMillis: Long

  /**
   * Total time spent analyzing the heap.
   */
  abstract val analysisDurationMillis: Long

  companion object {
    private const val serialVersionUID: Long = -8657286725869987172
    const val DUMP_DURATION_UNKNOWN: Long = -1
  }
}

/**
 * The analysis performed by [HeapAnalyzer] did not complete successfully.
 */
data class HeapAnalysisFailure(
  override val heapDumpFile: File,
  override val createdAtTimeMillis: Long,
  override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
  override val analysisDurationMillis: Long,
  /**
   * An exception wrapping the actual exception that was thrown.
   */
  val exception: HeapAnalysisException
) : HeapAnalysis() {

  override fun toString(): String {
    return """====================================
HEAP ANALYSIS FAILED

You can report this failure at https://github.com/square/leakcanary/issues
Please provide the stacktrace, metadata and the heap dump file.
====================================
STACKTRACE

$exception====================================
METADATA

Build.VERSION.SDK_INT: ${androidSdkInt()}
Build.MANUFACTURER: ${androidManufacturer()}
LeakCanary version: ${leakCanaryVersion()}
Analysis duration: $analysisDurationMillis ms
Heap dump file path: ${heapDumpFile.absolutePath}
Heap dump timestamp: $createdAtTimeMillis
===================================="""
  }

  companion object {
    private const val serialVersionUID: Long = 8483254400637792414
  }
}

/**
 * The result of a successful heap analysis performed by [HeapAnalyzer].
 */
data class HeapAnalysisSuccess(
  override val heapDumpFile: File,
  override val createdAtTimeMillis: Long,
  override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
  override val analysisDurationMillis: Long,
  val metadata: Map<String, String>,
  /**
   * The list of [ApplicationLeak] found in the heap dump by [HeapAnalyzer].
   */
  val applicationLeaks: List<ApplicationLeak>,
  /**
   * The list of [LibraryLeak] found in the heap dump by [HeapAnalyzer].
   */
  val libraryLeaks: List<LibraryLeak>,
  val unreachableObjects: List<LeakTraceObject>
) : HeapAnalysis() {
  /**
   * The list of [Leak] found in the heap dump by [HeapAnalyzer], ie all [applicationLeaks] and
   * all [libraryLeaks] in one list.
   */
  val allLeaks: Sequence<Leak>
    get() = applicationLeaks.asSequence() + libraryLeaks.asSequence()

  override fun toString(): String {
    return """====================================
HEAP ANALYSIS RESULT
====================================
${applicationLeaks.size} APPLICATION LEAKS

References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
${
      if (applicationLeaks.isNotEmpty()) "\n" + applicationLeaks.joinToString(
        "\n\n"
      ) + "\n" else ""
    }====================================
${libraryLeaks.size} LIBRARY LEAKS

A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
${
      if (libraryLeaks.isNotEmpty()) "\n" + libraryLeaks.joinToString(
        "\n\n"
      ) + "\n" else ""
    }====================================
${unreachableObjects.size} UNREACHABLE OBJECTS

An unreachable object is still in memory but LeakCanary could not find a strong reference path
from GC roots.
${
      if (unreachableObjects.isNotEmpty()) "\n" + unreachableObjects.joinToString(
        "\n\n"
      ) + "\n" else ""
    }====================================
METADATA

Please include this in bug reports and Stack Overflow questions.
${
      if (metadata.isNotEmpty()) "\n" + metadata.map { "${it.key}: ${it.value}" }.joinToString(
        "\n"
      ) else ""
    }
Analysis duration: $analysisDurationMillis ms
Heap dump file path: ${heapDumpFile.absolutePath}
Heap dump timestamp: $createdAtTimeMillis
Heap dump duration: ${if (dumpDurationMillis != DUMP_DURATION_UNKNOWN) "$dumpDurationMillis ms" else "Unknown"}
===================================="""
  }

  companion object {
    private const val serialVersionUID: Long = 130453013437459642
  }
}

/**
 * A leak found by [HeapAnalyzer], either an [ApplicationLeak] or a [LibraryLeak].
 */
sealed class Leak : Serializable {

  /**
   * Group of leak traces which share the same leak signature.
   */
  abstract val leakTraces: List<LeakTrace>

  /**
   * Sum of [LeakTrace.retainedHeapByteSize] for all elements in [leakTraces].
   * Null if the retained heap size was not computed.
   */
  val totalRetainedHeapByteSize: Int?
    get() = if (leakTraces.first().retainedHeapByteSize == null) {
      null
    } else {
      leakTraces.sumBy { it.retainedHeapByteSize!! }
    }

  /**
   * Sum of [LeakTrace.retainedObjectCount] for all elements in [leakTraces].
   * Null if the retained heap size was not computed.
   */
  val totalRetainedObjectCount: Int?
    get() = if (leakTraces.first().retainedObjectCount == null) {
      null
    } else {
      leakTraces.sumBy { it.retainedObjectCount!! }
    }

  /**
   * A unique SHA1 hash that represents this group of leak traces.
   *
   * For [ApplicationLeak] this is based on [LeakTrace.signature] and for [LibraryLeak] this is
   * based on [LibraryLeak.pattern].
   */
  abstract val signature: String

  abstract val shortDescription: String

  override fun toString(): String {
    return (if (totalRetainedHeapByteSize != null) "$totalRetainedHeapByteSize bytes retained by leaking objects\n" else "") +
      (if (leakTraces.size > 1) "Displaying only 1 leak trace out of ${leakTraces.size} with the same signature\n" else "") +
      "Signature: $signature\n" +
      leakTraces.first()
  }

  companion object {
    private const val serialVersionUID: Long = -2287572510360910916
  }
}

/**
 * A leak found by [HeapAnalyzer], where the only path to the leaking object required going
 * through a reference matched by [pattern], as provided to a [LibraryLeakReferenceMatcher]
 * instance. This is a known leak in library code that is beyond your control.
 */
data class LibraryLeak(
  override val leakTraces: List<LeakTrace>,
  /**
   * The pattern that matched one of the references in each of [leakTraces], as provided to a
   * [LibraryLeakReferenceMatcher] instance.
   */
  val pattern: ReferencePattern,
  /**
   * A description that conveys what we know about this library leak.
   */
  val description: String
) : Leak() {
  override val signature: String
    get() = pattern.toString().createSHA1Hash()

  override val shortDescription: String
    get() = pattern.toString()

  override fun toString(): String {
    return """Leak pattern: $pattern
Description: $description
${super.toString()}
"""
  }

  companion object {
    private const val serialVersionUID: Long = 3943636164568681903
  }
}

/**
 * A leak found by [HeapAnalyzer] in your application.
 */
data class ApplicationLeak(
  override val leakTraces: List<LeakTrace>
) : Leak() {
  override val signature: String
    get() = leakTraces.first().signature

  override val shortDescription: String
    get() {
      val leakTrace = leakTraces.first()
      return leakTrace.suspectReferenceSubpath.firstOrNull()?.let { firstSuspectReferencePath ->
        val referenceName = firstSuspectReferencePath.referenceGenericName
        firstSuspectReferencePath.originObject.classSimpleName + "." + referenceName
      } ?: leakTrace.leakingObject.className
    }

  // Override required to avoid the default toString() from data classes
  override fun toString(): String {
    return super.toString()
  }

  companion object {
    private const val serialVersionUID: Long = 524928276700576863
  }
}

private fun androidSdkInt(): Int {
  return try {
    val versionClass = Class.forName("android.os.Build\$VERSION")
    val sdkIntField = versionClass.getDeclaredField("SDK_INT")
    sdkIntField.get(null) as Int
  } catch (e: Exception) {
    -1
  }
}

private fun androidManufacturer(): String {
  return try {
    val buildClass = Class.forName("android.os.Build")
    val manufacturerField = buildClass.getDeclaredField("MANUFACTURER")
    manufacturerField.get(null) as String
  } catch (e: Exception) {
    "Unknown"
  }
}

private fun leakCanaryVersion(): String {
  return try {
    val versionHolderClass = Class.forName("leakcanary.internal.InternalLeakCanary")
    val versionField = versionHolderClass.getDeclaredField("version")
    versionField.isAccessible = true
    versionField.get(null) as String
  } catch (e: Exception) {
    "Unknown"
  }
}