Repository: square/leakcanary
File: shark-graph/src/main/java/shark/HeapObject.kt (Kotlin)

Code before:
          .flatten()
    }

    fun readAllFieldsOfReferenceType(): MutableList<HeapField> {
      var fieldReader: FieldValuesReader? = null
      val result = mutableListOf<HeapField>()
      for (heapClass in instanceClass.classHierarchy.toList()) {
        for (fieldRecord in heapClass.readRecord().fields) {
          // Initialize field reader if it's not yet initialized. Replaces `lazy` without synchronization
          if (fieldReader == null) {
            fieldReader = hprofGraph.createFieldValuesReader(readRecord())
          }

          // Skip all fields that are not references
          if (fieldRecord.type != PrimitiveType.REFERENCE_HPROF_TYPE) {
            fieldReader.skipValue(fieldRecord)
          } else {
            val fieldValue = ReferenceHolder(fieldReader.readId())
            if (!fieldValue.isNull) {
              val fieldName = hprofGraph.fieldName(heapClass.objectId, fieldRecord)
              result.add(HeapField(heapClass, fieldName, HeapValue(hprofGraph, fieldValue)))
            }
          }
        }
      }
      return result
    }

    /**
     * If this [HeapInstance] is an instance of the [String] class, returns a [String] instance
     * with content that matches the string in the heap dump. Otherwise returns null.
     *
     * This may trigger IO reads.
     */
    fun readAsJavaString(): String? {
      if (instanceClassName != "java.lang.String") {
        return null
      }

      // JVM strings don't have a count field.
      val count = this["java.lang.String", "count"]?.value?.asInt
      if (count == 0) {
        return ""
      }

      // Prior to API 26 String.value was a char array.
      // Since API 26 String.value is backed by native code. The vast majority of strings in a
      // heap dump are backed by a byte array, but we still find a few backed by a char array.
      when (val valueRecord =
        this["java.lang.String", "value"]!!.value.asObject!!.readRecord()) {
        is CharArrayDump -> {

Review comment (suggestion):
Some thoughts:

- Exposing this ultra specific method as a public API is not ideal
- The method is named `readAllFieldsOfReferenceType()` but it only returns the non null ref fields.
- The MutableList return type is also not idea (I get that it's to avoid the extra copy because we're sorting right after calling this)
- The `fieldReader` creation isn't as lazy as could be: if the instance's class hierarchy has no reference, we're reading the record (=> IO reads) and creating the reader for nothing. Instead, you could track how many bytes to skip as you're finding non REFERENCE types, when then when you find the first one you read the record, create the reader and skip.

A key question here is: what exactly was costly / what's bringing a ton of benefit?

- Is it avoiding to call `readRecord()` when unnecessary?
- Is it removing the `by lazy {` which is unnecessarily synchronized?
- Is it avoiding to lookup some field names unnecessarily?
- Is it avoiding to create XHolder instances unnecessarily?

Depending on the answer, maybe we should add this instead:

```
fun readFieldsLazy(): Sequence<LazyHeapField>
```

We'd still be creating N LazyHeapField and only using a subset, but that's it.

Then `LazyHeapField` can have a `declaringClass`, a lazy `name` (triggers hprofGraph name in memory lookup), and a `type` and a lazy `HeapValue` (triggers record read for the first field for which we need it). It'll also need a private `position` field. The key idea is that you can create that sequence just from the class hierarchy records and nothing else. 

Code after:
                .fields.asSequence()
                .map { fieldRecord ->
                  val fieldName = hprofGraph.fieldName(heapClass.objectId, fieldRecord)
                  val fieldValue = fieldReader.readValue(fieldRecord)
                  HeapField(heapClass, fieldName, HeapValue(hprofGraph, fieldValue))
                }
          }
          .flatten()
    }

    /**
     * If this [HeapInstance] is an instance of the [String] class, returns a [String] instance
     * with content that matches the string in the heap dump. Otherwise returns null.
     *
     * This may trigger IO reads.
     */
    fun readAsJavaString(): String? {
      if (instanceClassName != "java.lang.String") {
        return null
      }

      // JVM strings don't have a count field.
      val count = this["java.lang.String", "count"]?.value?.asInt
      if (count == 0) {
        return ""
      }

      // Prior to API 26 String.value was a char array.
      // Since API 26 String.value is backed by native code. The vast majority of strings in a
      // heap dump are backed by a byte array, but we still find a few backed by a char array.
      when (val valueRecord =
        this["java.lang.String", "value"]!!.value.asObject!!.readRecord()) {
        is CharArrayDump -> {
          // < API 23
          // As of Marshmallow, substrings no longer share their parent strings' char arrays
          // eliminating the need for String.offset
          // https://android-review.googlesource.com/#/c/83611/
          val offset = this["java.lang.String", "offset"]?.value?.asInt

          val chars = if (count != null && offset != null) {
            // Handle heap dumps where all primitive arrays have been replaced with empty arrays,
            // e.g. with HprofPrimitiveArrayStripper
            val toIndex = if (offset + count > valueRecord.array.size) {
              valueRecord.array.size
            } else offset + count
            valueRecord.array.copyOfRange(offset, toIndex)
          } else {
            valueRecord.array
          }
          return String(chars)
        }