main

square/leakcanary

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

JavaLocalReferenceReader.kt

TLDR

This file contains the implementation of the JavaLocalReferenceReader class, which is responsible for reading Java local references in a heap graph.

Classes

JavaLocalReferenceReader

The JavaLocalReferenceReader class implements the VirtualInstanceReferenceReader interface and is used to read Java local references in a heap graph. It has the following properties and methods:

  • graph: A reference to the HeapGraph used by the reader.
  • referenceMatchers: A list of reference matchers used to filter the references.
  • threadClassObjectIds: A set of object IDs representing the Thread class and its subclasses.
  • threadNameReferenceMatchers: A map of thread names to reference matchers.
  • matches(instance: HeapInstance): A method that determines whether a given HeapInstance matches the conditions for being a Java local reference.
  • read(source: HeapInstance): A method that reads the references from a given HeapInstance.
package shark

import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader
import shark.HeapObject.HeapInstance
import shark.Reference.LazyDetails
import shark.ReferenceLocationType.LOCAL
import shark.ReferencePattern.JavaLocalPattern
import shark.internal.JavaFrames
import shark.internal.ThreadObjects

internal class JavaLocalReferenceReader(
  val graph: HeapGraph,
  referenceMatchers: List<ReferenceMatcher>
) : VirtualInstanceReferenceReader {

  private val threadClassObjectIds: Set<Long> =
    graph.findClassByName(Thread::class.java.name)?.let { threadClass ->
      setOf(threadClass.objectId) + (threadClass.subclasses
        .map { it.objectId }
        .toSet())
    }?: emptySet()

  private val threadNameReferenceMatchers: Map<String, ReferenceMatcher>

  init {
    val threadNames = mutableMapOf<String, ReferenceMatcher>()
    referenceMatchers.filterFor(graph).forEach { referenceMatcher ->
      val pattern = referenceMatcher.pattern
      if (pattern is JavaLocalPattern) {
        threadNames[pattern.threadName] = referenceMatcher
      }
    }
    this.threadNameReferenceMatchers = threadNames
  }

  override fun matches(instance: HeapInstance): Boolean {
    return instance.instanceClassId in threadClassObjectIds &&
      ThreadObjects.getByThreadObjectId(graph, instance.objectId) != null
  }

  override fun read(source: HeapInstance): Sequence<Reference> {
    val referenceMatcher =  source[Thread::class, "name"]?.value?.readAsJavaString()?.let { threadName ->
      threadNameReferenceMatchers[threadName]
    }

    if (referenceMatcher is IgnoredReferenceMatcher) {
      return emptySequence()
    }
    val threadClassId = source.instanceClassId
    return JavaFrames.getByThreadObjectId(graph, source.objectId)?.let { frames ->
      frames.asSequence().map { frame ->
        Reference(
          valueObjectId = frame.id,
          // Java Frames always have low priority because their path is harder to understand
          // for developers
          isLowPriority = true,
          lazyDetailsResolver = {
            LazyDetails(
              // Unfortunately Android heap dumps do not include stack trace data, so
              // JavaFrame.frameNumber is always -1 and we cannot know which method is causing the
              // reference to be held.
              name = "",
              locationClassObjectId = threadClassId,
              locationType = LOCAL,
              matchedLibraryLeak = referenceMatcher as LibraryLeakReferenceMatcher?,
              isVirtual = true
            )
          }
        )
      }
    } ?: emptySequence()
  }
}