main

square/leakcanary

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

InternalSharedExpanderHelpers.kt

TLDR

This file contains three classes: InternalSharedHashMapReferenceReader, InternalSharedArrayListReferenceReader, and InternalSharedLinkedListReferenceReader. These classes implement the VirtualInstanceReferenceReader interface and provide methods for reading references from different data structures.

Classes

InternalSharedHashMapReferenceReader

This class provides methods for reading references from a HashMap. It takes in various parameters like class names, field names, boolean flags, match criteria, and a function to identify the declaring class id. It implements the VirtualInstanceReferenceReader interface with methods matches and read. The matches method checks if an instance matches the specified criteria, while the read method reads the references of the HashMap.

InternalSharedArrayListReferenceReader

This class provides methods for reading references from an ArrayList. It takes in parameters like class names, object ids, field names, and size (optional). It also implements the VirtualInstanceReferenceReader interface with methods matches and read. The matches method checks if an instance matches the specified criteria, while the read method reads the references of the ArrayList.

InternalSharedLinkedListReferenceReader

This class provides methods for reading references from a LinkedList. It takes in parameters like object ids, field names, and class names. Similar to the previous classes, it implements the VirtualInstanceReferenceReader interface with methods matches and read. The matches method checks if an instance matches the specified criteria, while the read method reads the references of the LinkedList.

package shark.internal

import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader
import shark.HeapObject.HeapInstance
import shark.HeapValue
import shark.Reference
import shark.Reference.LazyDetails
import shark.ReferenceLocationType.ARRAY_ENTRY

internal class InternalSharedHashMapReferenceReader(
  private val className: String,
  private val tableFieldName: String,
  private val nodeClassName: String,
  private val nodeNextFieldName: String,
  private val nodeKeyFieldName: String,
  private val nodeValueFieldName: String,
  private val keyName: String,
  private val keysOnly: Boolean,
  private val matches: (HeapInstance) -> Boolean,
  private val declaringClassId: (HeapInstance) -> (Long)
) : VirtualInstanceReferenceReader {
  override fun matches(instance: HeapInstance): Boolean {
    return matches.invoke(instance)
  }

  override fun read(source: HeapInstance): Sequence<Reference> {
    val table = source[className, tableFieldName]!!.valueAsObjectArray
    return if (table != null) {
      val entries = table.readElements().mapNotNull { entryRef ->
        if (entryRef.isNonNullReference) {
          val entry = entryRef.asObject!!.asInstance!!
          generateSequence(entry) { node ->
            node[nodeClassName, nodeNextFieldName]!!.valueAsInstance
          }
        } else {
          null
        }
      }.flatten()

      val declaringClassId = declaringClassId(source)

      val createKeyRef: (HeapValue) -> Reference? = { key ->
        if (key.isNonNullReference) {
          Reference(
            valueObjectId = key.asObjectId!!,
            isLowPriority = false,
            lazyDetailsResolver = {
              LazyDetails(
                // All entries are represented by the same key name, e.g. "key()"
                name = keyName,
                locationClassObjectId = declaringClassId,
                locationType = ARRAY_ENTRY,
                isVirtual = true,
                matchedLibraryLeak = null
              )
            }
          )
        } else null
      }

      if (keysOnly) {
        entries.mapNotNull { entry ->
          val key = entry[nodeClassName, nodeKeyFieldName]!!.value
          createKeyRef(key)
        }
      } else {
        entries.flatMap { entry ->
          val key = entry[nodeClassName, nodeKeyFieldName]!!.value
          val keyRef = createKeyRef(key)
          val value = entry[nodeClassName, nodeValueFieldName]!!.value
          val valueRef = if (value.isNonNullReference) {
            Reference(
              valueObjectId = value.asObjectId!!,
              isLowPriority = false,
              lazyDetailsResolver = {
                val keyAsString = key.asObject?.asInstance?.readAsJavaString()?.let { "\"$it\"" }
                val keyAsName =
                  keyAsString ?: key.asObject?.toString() ?: "null"
                LazyDetails(
                  name = keyAsName,
                  locationClassObjectId = declaringClassId,
                  locationType = ARRAY_ENTRY,
                  isVirtual = true,
                  matchedLibraryLeak = null
                )
              }
            )
          } else null
          if (keyRef != null && valueRef != null) {
            sequenceOf(keyRef, valueRef)
          } else if (keyRef != null) {
            sequenceOf(keyRef)
          } else if (valueRef != null) {
            sequenceOf(valueRef)
          } else {
            emptySequence()
          }
        }
      }
    } else {
      emptySequence()
    }
  }
}

internal class InternalSharedArrayListReferenceReader(
  private val className: String,
  private val classObjectId: Long,
  private val elementArrayName: String,
  private val sizeFieldName: String?
) : VirtualInstanceReferenceReader {

  override fun matches(instance: HeapInstance): Boolean {
    return instance.instanceClassId == classObjectId
  }

  override fun read(source: HeapInstance): Sequence<Reference> {
    val instanceClassId = source.instanceClassId
    val elementFieldRef =
      source[className, elementArrayName]!!.valueAsObjectArray ?: return emptySequence()

    val elements = if (sizeFieldName != null) {
      val size = source[className, sizeFieldName]!!.value.asInt!!
      elementFieldRef.readElements().take(size)
    } else {
      elementFieldRef.readElements()
    }
    return elements.withIndex()
      .mapNotNull { (index, elementValue) ->
        if (elementValue.isNonNullReference) {
          Reference(
            valueObjectId = elementValue.asObjectId!!,
            isLowPriority = false,
            lazyDetailsResolver = {
              LazyDetails(
                name = "$index",
                locationClassObjectId = instanceClassId,
                locationType = ARRAY_ENTRY,
                isVirtual = true,
                matchedLibraryLeak = null
              )
            }
          )
        } else {
          null
        }
      }
  }
}

internal class InternalSharedLinkedListReferenceReader(
  private val classObjectId: Long,
  private val headFieldName: String,
  private val nodeClassName: String,
  private val nodeNextFieldName: String,
  private val nodeElementFieldName: String
) : VirtualInstanceReferenceReader {

  override fun matches(instance: HeapInstance): Boolean {
    return instance.instanceClassId == classObjectId
  }

  override fun read(source: HeapInstance): Sequence<Reference> {
    val instanceClassId = source.instanceClassId
    // head may be null, in that case we generate an empty sequence.
    val firstNode = source["java.util.LinkedList", headFieldName]!!.valueAsInstance
    val visitedNodes = mutableSetOf<Long>()
    if (firstNode != null) {
      visitedNodes += firstNode.objectId
    }
    return generateSequence(firstNode) { node ->
      val nextNode = node[nodeClassName, nodeNextFieldName]!!.valueAsInstance
      if (nextNode != null && visitedNodes.add(nextNode.objectId)) {
        nextNode
      } else {
        null
      }
    }
      .withIndex()
      .mapNotNull { (index, node) ->
        val itemObjectId = node[nodeClassName, nodeElementFieldName]!!.value.asObjectId
        itemObjectId?.run {
          Reference(
            valueObjectId = this,
            isLowPriority = false,
            lazyDetailsResolver = {
              LazyDetails(
                // All entries are represented by the same key name, e.g. "key()"
                name = "$index",
                locationClassObjectId = instanceClassId,
                locationType = ARRAY_ENTRY,
                isVirtual = true,
                matchedLibraryLeak = null
              )
            }
          )
        }
      }
  }
}