main

square/leakcanary

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

ChainingInstanceReferenceReader.kt

TLDR

The ChainingInstanceReferenceReader.kt file is a Kotlin file that defines the ChainingInstanceReferenceReader class. It is a reference reader that first delegates expanding to a list of virtual reference readers until one matches or none, and then proceeds with a field reference reader. It allows for correct tracking of visited objects and proper computation of dominators and retained size.

Classes

ChainingInstanceReferenceReader

The ChainingInstanceReferenceReader class is a reference reader that reads references from a heap instance. It takes a list of virtual reference readers and a field reference reader as parameters. It implements the ReferenceReader interface for HeapInstance.

Interface: ChainingInstanceReferenceReader.VirtualInstanceReferenceReader

The VirtualInstanceReferenceReader interface extends the ReferenceReader for HeapInstance. It defines two methods:

  • matches(instance: HeapInstance): This method determines whether this virtual instance reference reader can expand the provided heap instance.
  • read(instance: HeapInstance): This method reads references from the provided heap instance.

Interface: ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory

The OptionalFactory interface defines a method create(graph: HeapGraph) that creates a new VirtualInstanceReferenceReader based on the classes in the heap graph. It may return a different ReferenceReader implementation depending on the version of a class present in the heap dump, or it may return null if the class is missing.

Interface: ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.ChainFactory

The ChainFactory interface defines a method createFor(graph: HeapGraph) that creates a list of VirtualInstanceReferenceReaders based on the classes in the heap graph and their implementation. The list elements will process references in order in the ChainingInstanceReferenceReader.

package shark

import shark.HeapObject.HeapInstance

/**
 * A [ReferenceReader] that first delegates expanding to [virtualRefReaders] in order until one
 * matches (or none), and then always proceeds with [fieldRefReader]. This means any
 * synthetic ref will be on the shortest path, but we still explore the entire data structure so
 * that we correctly track which objects have been visited and correctly compute dominators and
 * retained size.
 */
class ChainingInstanceReferenceReader(
  private val virtualRefReaders: List<VirtualInstanceReferenceReader>,
  private val fieldRefReader: FieldInstanceReferenceReader
) : ReferenceReader<HeapInstance> {

  override fun read(source: HeapInstance): Sequence<Reference> {
    val virtualRefs = expandVirtualRefs(source)
    // Note: always forwarding to fieldRefReader means we may navigate the structure twice
    // which increases IO reads. However this is a trade-of that allows virtualRef impls to
    // focus on a subset of references and more importantly it means we still get a proper
    // calculation of retained size as we don't skip any instance.
    val fieldRefs = fieldRefReader.read(source)
    return virtualRefs + fieldRefs
  }

  private fun expandVirtualRefs(instance: HeapInstance): Sequence<Reference> {
    for (expander in virtualRefReaders) {
      if (expander.matches(instance)) {
        return expander.read(instance)
      }
    }
    return emptySequence()
  }

  /**
   * Same as [ReferenceReader] but [read] is only invoked when [matches] returns
   * true. [matches] should return false if this [VirtualInstanceReferenceReader] implementation isn't
   * able to expand the provided instance, in which case [ChainingInstanceReferenceReader] will delegate
   * to the next [VirtualInstanceReferenceReader] implementation.
   */
  interface VirtualInstanceReferenceReader : ReferenceReader<HeapInstance> {
    fun matches(instance: HeapInstance): Boolean

    /**
     * May create a new [VirtualInstanceReferenceReader], depending on what's in the heap graph.
     * [OptionalFactory] implementations might return a different [ReferenceReader]
     * depending on which version of a class is present in the heap dump, or they might return null if
     * that class is missing.
     */
    fun interface OptionalFactory {
      fun create(graph: HeapGraph): VirtualInstanceReferenceReader?
    }

    /**
     * Creates a list of [VirtualInstanceReferenceReader] where the content of the list depends on
     * the classes in the heap graph and their implementation. This is a chain as
     * [VirtualInstanceReferenceReader] elements in the list will process references in order in
     * [ChainingInstanceReferenceReader].
     */
    fun interface ChainFactory {
      fun createFor(graph: HeapGraph): List<VirtualInstanceReferenceReader>
    }
  }
}