Repository: square/leakcanary
File: shark/src/main/java/shark/internal/PathFinder.kt (Kotlin)

Code before:
  ) {
    val record = objectArray.readRecord()
    val nonNullElementIds = record.elementIds.filter { objectId ->
      objectId != ValueHolder.NULL_REFERENCE && graph.objectExists(objectId)
    }
    nonNullElementIds.forEachIndexed { index, elementId ->
      if (computeRetainedHeapSize) {
        updateDominatorWithSkips(parent.objectId, elementId)
      }
      val name = index.toString()
      enqueue(
          NormalNode(
              objectId = elementId,
              parent = parent,
              refFromParentType = ARRAY_ENTRY,
              refFromParentName = name
          )
      )
    }
  }

  @Suppress("ReturnCount")
  private fun State.enqueue(
    node: ReferencePathNode
  ) {
    if (node.objectId == ValueHolder.NULL_REFERENCE) {
      return
    }
    if (visitedSet.contains(node.objectId)) {
      return
    }
    // Already enqueued => shorter or equal distance
    if (toVisitSet.contains(node.objectId)) {
      return
    }

    val visitLast =
      node is LibraryLeakNode ||
          // We deprioritize thread objects because on Lollipop the thread local values are stored
          // as a field.
          (node is RootNode && node.gcRoot is ThreadObject) ||
          (node is NormalNode && node.parent is RootNode && node.parent.gcRoot is JavaFrame)

    if (toVisitLastSet.contains(node.objectId)) {
      // Already enqueued => shorter or equal distance amongst library leak ref patterns.
      if (visitLast) {
        return
      } else {
        toVisitQueue.add(node)
        toVisitSet.add(node.objectId)
        val nodeToRemove = toVisitLastQueue.first { it.objectId == node.objectId }

Review comment (suggestion):
Replace all this with `PrimitiveType.byteSizeByHprofType[field.type]`

Code after:
  }

  /**
   * Returns class hierarchy for an instance, but without it's root element, which is always
   * java.lang.Object.
   * Alternative would be calling `instanceClass.classHierarchy.toList().dropLast(1)`; this version
   * doesn't use Sequences and doesn't create extra list to drop one element.
   * Why do we want class hierarchy without java.lang.Object?
   * In pre-M there were no ref fields in java.lang.Object; and FieldIdReader wouldn't be created
   * Android M added shadow$_klass_ reference to class, so now it triggers extra record read.
   * Solution: skip heap class for java.lang.Object completely when reading the records
   */
  private fun HeapClass.classHierarchyWithoutJavaLangObject(): List<HeapClass> {
    val result = mutableListOf<HeapClass>()
    var parent: HeapClass? = this
    var nextParent = parent?.superclass
    while (parent != null && nextParent != null) {
      result += parent
      parent = nextParent
      nextParent = parent.superclass
    }
    return result
  }

  private fun HeapGraph.getRecordSize(field: FieldRecord) =
    when (field.type) {
      PrimitiveType.REFERENCE_HPROF_TYPE -> identifierByteSize
      BOOLEAN.hprofType -> 1
      CHAR.hprofType -> 2
      FLOAT.hprofType -> 4
      DOUBLE.hprofType -> 8
      BYTE.hprofType -> 1
      SHORT.hprofType -> 2
      INT.hprofType -> 4
      LONG.hprofType -> 8
      else -> throw IllegalStateException("Unknown type ${field.type}")
    }

  private fun State.visitObjectArray(
    objectArray: HeapObjectArray,
    parent: ReferencePathNode
  ) {
    val record = objectArray.readRecord()
    val nonNullElementIds = record.elementIds.filter { objectId ->
      objectId != ValueHolder.NULL_REFERENCE && graph.objectExists(objectId)
    }
    nonNullElementIds.forEachIndexed { index, elementId ->
      if (computeRetainedHeapSize) {
        updateDominatorWithSkips(parent.objectId, elementId)
      }
      val name = index.toString()