main

square/leakcanary

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

HprofRecordReader.kt

TLDR

The HprofRecordReader.kt file is responsible for reading hprof content from an Okio BufferedSource. It is used to parse hprof dump files and extract various types of records such as string records, class records, stack trace records, and more.

Methods

readStringRecord

This method reads a string record from the hprof file.

readLoadClassRecord

This method reads a load class record from the hprof file.

readStackFrameRecord

This method reads a stack frame record from the hprof file.

readStackTraceRecord

This method reads a stack trace record from the hprof file.

readUnknownGcRootRecord

This method reads an unknown garbage collection (GC) root record from the hprof file.

readJniGlobalGcRootRecord

This method reads a JNI global GC root record from the hprof file.

readJniLocalGcRootRecord

This method reads a JNI local GC root record from the hprof file.

readJavaFrameGcRootRecord

This method reads a Java frame GC root record from the hprof file.

readNativeStackGcRootRecord

This method reads a native stack GC root record from the hprof file.

readStickyClassGcRootRecord

This method reads a sticky class GC root record from the hprof file.

readThreadBlockGcRootRecord

This method reads a thread block GC root record from the hprof file.

readMonitorUsedGcRootRecord

This method reads a monitor used GC root record from the hprof file.

readThreadObjectGcRootRecord

This method reads a thread object GC root record from the hprof file.

readInternedStringGcRootRecord

This method reads an interned string GC root record from the hprof file.

readFinalizingGcRootRecord

This method reads a finalizing GC root record from the hprof file.

readDebuggerGcRootRecord

This method reads a debugger GC root record from the hprof file.

readReferenceCleanupGcRootRecord

This method reads a reference cleanup GC root record from the hprof file.

readVmInternalGcRootRecord

This method reads a VM internal GC root record from the hprof file.

readJniMonitorGcRootRecord

This method reads a JNI monitor GC root record from the hprof file.

readUnreachableGcRootRecord

This method reads an unreachable GC root record from the hprof file.

readInstanceDumpRecord

This method reads a full instance record from the hprof file.

readHeapDumpInfoRecord

This method reads a heap dump info record from the hprof file.

readClassDumpRecord

This method reads a full class record from the hprof file.

readPrimitiveArrayDumpRecord

This method reads a full primitive array record from the hprof file.

readObjectArrayDumpRecord

This method reads a full object array record from the hprof file.

skipClassDumpHeader

This method skips the header of a class dump record in the hprof file.

skipClassDumpConstantPool

This method skips the constant pool of a class dump record in the hprof file.

skipClassDumpStaticFields

This method skips the static fields of a class dump record in the hprof file.

skipClassDumpFields

This method skips the fields of a class dump record in the hprof file.

skipInstanceDumpRecord

This method skips an instance record in the hprof file.

skipClassDumpRecord

This method skips a class dump record in the hprof file.

skipObjectArrayDumpRecord

This method skips an object array record in the hprof file.

skipPrimitiveArrayDumpRecord

This method skips a primitive array record in the hprof file.

skipHeapDumpInfoRecord

This method skips a heap dump info record in the hprof file.

skip

This method skips a specified number of bytes in the hprof file.

skipId

This method skips an ID (identifier) in the hprof file.

readUnsignedInt

This method reads an unsigned int from the hprof file.

readUnsignedByte

This method reads an unsigned byte from the hprof file.

readValue

This method reads a value in the heap dump, which can be a reference or a primitive type.

readShort

This method reads a short from the hprof file.

readInt

This method reads an int from the hprof file.

readIdArray

This method reads an array of IDs from the hprof file.

readBooleanArray

This method reads a boolean array from the hprof file.

readCharArray

This method reads a char array from the hprof file.

readString

This method reads a string from the hprof file.

readFloatArray

This method reads a float array from the hprof file.

readDoubleArray

This method reads a double array from the hprof file.

readShortArray

This method reads a short array from the hprof file.

readIntArray

This method reads an int array from the hprof file.

readLongArray

This method reads a long array from the hprof file.

readLong

This method reads a long from the hprof file.

readByte

This method reads a byte from the hprof file.

readBoolean

This method reads a boolean from the hprof file.

readByteArray

This method reads a byte array from the hprof file.

readChar

This method reads a char from the hprof file.

readFloat

This method reads a float from the hprof file.

readDouble

This method reads a double from the hprof file.

readId

This method reads an ID (identifier) from the hprof file.

readUtf8

This method reads a UTF-8 string from the hprof file.

readUnsignedShort

This method reads an unsigned short from the hprof file.

package shark

import okio.BufferedSource
import shark.GcRoot.Debugger
import shark.GcRoot.Finalizing
import shark.GcRoot.InternedString
import shark.GcRoot.JavaFrame
import shark.GcRoot.JniGlobal
import shark.GcRoot.JniLocal
import shark.GcRoot.JniMonitor
import shark.GcRoot.MonitorUsed
import shark.GcRoot.NativeStack
import shark.GcRoot.ReferenceCleanup
import shark.GcRoot.StickyClass
import shark.GcRoot.ThreadBlock
import shark.GcRoot.ThreadObject
import shark.GcRoot.Unknown
import shark.GcRoot.Unreachable
import shark.GcRoot.VmInternal
import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
import shark.HprofRecord.LoadClassRecord
import shark.HprofRecord.StackFrameRecord
import shark.HprofRecord.StackTraceRecord
import shark.HprofRecord.StringRecord
import shark.PrimitiveType.BOOLEAN
import shark.PrimitiveType.BYTE
import shark.PrimitiveType.CHAR
import shark.PrimitiveType.DOUBLE
import shark.PrimitiveType.FLOAT
import shark.PrimitiveType.INT
import shark.PrimitiveType.LONG
import shark.PrimitiveType.SHORT
import shark.ValueHolder.BooleanHolder
import shark.ValueHolder.ByteHolder
import shark.ValueHolder.CharHolder
import shark.ValueHolder.DoubleHolder
import shark.ValueHolder.FloatHolder
import shark.ValueHolder.IntHolder
import shark.ValueHolder.LongHolder
import shark.ValueHolder.ReferenceHolder
import shark.ValueHolder.ShortHolder
import java.nio.charset.Charset

/**
 * Reads hprof content from an Okio [BufferedSource].
 *
 * Binary Dump Format reference: http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share
 * /demo/jvmti/hprof/manual.html#mozTocId848088
 *
 * The Android Hprof format differs in some ways from that reference. This parser implementation
 * is largely adapted from https://android.googlesource.com/platform/tools/base/+/studio-master-dev
 * /perflib/src/main/java/com/android/tools/perflib
 *
 * Not thread safe, should be used from a single thread.
 */
@Suppress("LargeClass", "TooManyFunctions")
class HprofRecordReader internal constructor(
  header: HprofHeader,
  private val source: BufferedSource
) {

  /**
   * How many bytes this reader has read from [source]. Can only increase.
   */
  var bytesRead = 0L
    private set

  private val identifierByteSize = header.identifierByteSize

  private val typeSizes: IntArray

  init {
    val typeSizesMap =
      PrimitiveType.byteSizeByHprofType + (PrimitiveType.REFERENCE_HPROF_TYPE to identifierByteSize)

    val maxKey = typeSizesMap.keys.max()!!

    typeSizes = IntArray(maxKey + 1) { key ->
      typeSizesMap[key] ?: 0
    }
  }

  fun sizeOf(type: Int) = typeSizes[type]

  fun readStringRecord(length: Long) = StringRecord(
    id = readId(),
    string = readUtf8(length - identifierByteSize)
  )

  fun readLoadClassRecord() = LoadClassRecord(
    classSerialNumber = readInt(),
    id = readId(),
    stackTraceSerialNumber = readInt(),
    classNameStringId = readId()
  )

  fun readStackFrameRecord() = StackFrameRecord(
    id = readId(),
    methodNameStringId = readId(),
    methodSignatureStringId = readId(),
    sourceFileNameStringId = readId(),
    classSerialNumber = readInt(),
    lineNumber = readInt()
  )

  fun readStackTraceRecord() = StackTraceRecord(
    stackTraceSerialNumber = readInt(),
    threadSerialNumber = readInt(),
    stackFrameIds = readIdArray(readInt())
  )

  fun readUnknownGcRootRecord() = Unknown(id = readId())

  fun readJniGlobalGcRootRecord() = JniGlobal(
    id = readId(),
    jniGlobalRefId = readId()
  )

  fun readJniLocalGcRootRecord() = JniLocal(
    id = readId(),
    threadSerialNumber = readInt(),
    frameNumber = readInt()
  )

  fun readJavaFrameGcRootRecord() = JavaFrame(
    id = readId(),
    threadSerialNumber = readInt(),
    frameNumber = readInt()
  )

  fun readNativeStackGcRootRecord() = NativeStack(
    id = readId(),
    threadSerialNumber = readInt()
  )

  fun readStickyClassGcRootRecord() = StickyClass(id = readId())

  fun readThreadBlockGcRootRecord() = ThreadBlock(id = readId(), threadSerialNumber = readInt())

  fun readMonitorUsedGcRootRecord() = MonitorUsed(id = readId())

  fun readThreadObjectGcRootRecord() = ThreadObject(
    id = readId(),
    threadSerialNumber = readInt(),
    stackTraceSerialNumber = readInt()
  )

  fun readInternedStringGcRootRecord() = InternedString(id = readId())

  fun readFinalizingGcRootRecord() = Finalizing(id = readId())

  fun readDebuggerGcRootRecord() = Debugger(id = readId())

  fun readReferenceCleanupGcRootRecord() = ReferenceCleanup(id = readId())

  fun readVmInternalGcRootRecord() = VmInternal(id = readId())

  fun readJniMonitorGcRootRecord() = JniMonitor(
    id = readId(),
    stackTraceSerialNumber = readInt(),
    stackDepth = readInt()
  )

  fun readUnreachableGcRootRecord() = Unreachable(id = readId())

  /**
   * Reads a full instance record after a instance dump tag.
   */
  fun readInstanceDumpRecord(): InstanceDumpRecord {
    val id = readId()
    val stackTraceSerialNumber = readInt()
    val classId = readId()
    val remainingBytesInInstance = readInt()
    val fieldValues = readByteArray(remainingBytesInInstance)
    return InstanceDumpRecord(
      id = id,
      stackTraceSerialNumber = stackTraceSerialNumber,
      classId = classId,
      fieldValues = fieldValues
    )
  }

  fun readHeapDumpInfoRecord(): HeapDumpInfoRecord {
    val heapId = readInt()
    return HeapDumpInfoRecord(heapId = heapId, heapNameStringId = readId())
  }

  /**
   * Reads a full class record after a class dump tag.
   */
  fun readClassDumpRecord(): ClassDumpRecord {
    val id = readId()
    // stack trace serial number
    val stackTraceSerialNumber = readInt()
    val superclassId = readId()
    // class loader object ID
    val classLoaderId = readId()
    // signers object ID
    val signersId = readId()
    // protection domain object ID
    val protectionDomainId = readId()
    // reserved
    readId()
    // reserved
    readId()

    // instance size (in bytes)
    // Useful to compute retained size
    val instanceSize = readInt()

    // Skip over the constant pool
    val constantPoolCount = readUnsignedShort()
    for (i in 0 until constantPoolCount) {
      // constant pool index
      skip(SHORT_SIZE)
      skip(typeSizes[readUnsignedByte()])
    }

    val staticFieldCount = readUnsignedShort()
    val staticFields = ArrayList<StaticFieldRecord>(staticFieldCount)
    for (i in 0 until staticFieldCount) {

      val nameStringId = readId()
      val type = readUnsignedByte()
      val value = readValue(type)

      staticFields.add(
        StaticFieldRecord(
          nameStringId = nameStringId,
          type = type,
          value = value
        )
      )
    }

    val fieldCount = readUnsignedShort()
    val fields = ArrayList<FieldRecord>(fieldCount)
    for (i in 0 until fieldCount) {
      fields.add(FieldRecord(nameStringId = readId(), type = readUnsignedByte()))
    }

    return ClassDumpRecord(
      id = id,
      stackTraceSerialNumber = stackTraceSerialNumber,
      superclassId = superclassId,
      classLoaderId = classLoaderId,
      signersId = signersId,
      protectionDomainId = protectionDomainId,
      instanceSize = instanceSize,
      staticFields = staticFields,
      fields = fields
    )
  }

  /**
   * Reads a full primitive array record after a primitive array dump tag.
   */
  fun readPrimitiveArrayDumpRecord(): PrimitiveArrayDumpRecord {
    val id = readId()
    val stackTraceSerialNumber = readInt()
    // length
    val arrayLength = readInt()
    return when (val type = readUnsignedByte()) {
      BOOLEAN_TYPE -> BooleanArrayDump(
        id, stackTraceSerialNumber, readBooleanArray(arrayLength)
      )
      CHAR_TYPE -> CharArrayDump(
        id, stackTraceSerialNumber, readCharArray(arrayLength)
      )
      FLOAT_TYPE -> FloatArrayDump(
        id, stackTraceSerialNumber, readFloatArray(arrayLength)
      )
      DOUBLE_TYPE -> DoubleArrayDump(
        id, stackTraceSerialNumber, readDoubleArray(arrayLength)
      )
      BYTE_TYPE -> ByteArrayDump(
        id, stackTraceSerialNumber, readByteArray(arrayLength)
      )
      SHORT_TYPE -> ShortArrayDump(
        id, stackTraceSerialNumber, readShortArray(arrayLength)
      )
      INT_TYPE -> IntArrayDump(
        id, stackTraceSerialNumber, readIntArray(arrayLength)
      )
      LONG_TYPE -> LongArrayDump(
        id, stackTraceSerialNumber, readLongArray(arrayLength)
      )
      else -> throw IllegalStateException("Unexpected type $type")
    }
  }

  /**
   * Reads a full object array record after a object array dump tag.
   */
  fun readObjectArrayDumpRecord(): ObjectArrayDumpRecord {
    val id = readId()
    // stack trace serial number
    val stackTraceSerialNumber = readInt()
    val arrayLength = readInt()
    val arrayClassId = readId()
    val elementIds = readIdArray(arrayLength)
    return ObjectArrayDumpRecord(
      id = id,
      stackTraceSerialNumber = stackTraceSerialNumber,
      arrayClassId = arrayClassId,
      elementIds = elementIds
    )
  }

  fun skipClassDumpHeader() {
    skip(INT_SIZE * 2 + identifierByteSize * 7)
    skipClassDumpConstantPool()
  }

  fun skipClassDumpConstantPool() {
    // Skip over the constant pool
    val constantPoolCount = readUnsignedShort()
    for (i in 0 until constantPoolCount) {
      // constant pool index
      skip(SHORT.byteSize)
      skip(sizeOf(readUnsignedByte()))
    }
  }

  fun skipClassDumpStaticFields() {
    val staticFieldCount = readUnsignedShort()
    for (i in 0 until staticFieldCount) {
      skip(identifierByteSize)
      val type = readUnsignedByte()
      skip(
        if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
          identifierByteSize
        } else {
          PrimitiveType.byteSizeByHprofType.getValue(type)
        }
      )
    }
  }

  fun skipClassDumpFields() {
    val fieldCount = readUnsignedShort()
    skip((identifierByteSize + 1) * fieldCount)
  }

  fun skipInstanceDumpRecord() {
    skip(identifierByteSize + INT_SIZE + identifierByteSize)
    val remainingBytesInInstance = readInt()
    skip(remainingBytesInInstance)
  }

  fun skipClassDumpRecord() {
    skip(
      identifierByteSize + INT_SIZE + identifierByteSize + identifierByteSize + identifierByteSize +
        identifierByteSize + identifierByteSize + identifierByteSize + INT_SIZE
    )
    // Skip over the constant pool
    val constantPoolCount = readUnsignedShort()
    for (i in 0 until constantPoolCount) {
      // constant pool index
      skip(SHORT_SIZE)
      skip(typeSizes[readUnsignedByte()])
    }

    val staticFieldCount = readUnsignedShort()

    for (i in 0 until staticFieldCount) {
      skip(identifierByteSize)
      val type = readUnsignedByte()
      skip(typeSizes[type])
    }

    val fieldCount = readUnsignedShort()
    skip(fieldCount * (identifierByteSize + BYTE_SIZE))
  }

  fun skipObjectArrayDumpRecord() {
    skip(identifierByteSize + INT_SIZE)
    val arrayLength = readInt()
    skip(identifierByteSize + arrayLength * identifierByteSize)
  }

  fun skipPrimitiveArrayDumpRecord() {
    skip(identifierByteSize + INT_SIZE)
    val arrayLength = readInt()
    val type = readUnsignedByte()
    skip(arrayLength * typeSizes[type])
  }

  fun skipHeapDumpInfoRecord() {
    skip(identifierByteSize + identifierByteSize)
  }

  fun skip(byteCount: Int) {
    bytesRead += byteCount
    return source.skip(byteCount.toLong())
  }

  fun skipId() {
    skip(identifierByteSize)
  }

  fun skip(byteCount: Long) {
    bytesRead += byteCount
    return source.skip(byteCount)
  }

  fun readUnsignedInt(): Long {
    return readInt().toLong() and INT_MASK
  }

  fun readUnsignedByte(): Int {
    return readByte().toInt() and BYTE_MASK
  }

  /**
   * Reads a value in the heap dump, which can be a reference or a primitive type.
   */
  fun readValue(type: Int): ValueHolder {
    return when (type) {
      PrimitiveType.REFERENCE_HPROF_TYPE -> ReferenceHolder(readId())
      BOOLEAN_TYPE -> BooleanHolder(readBoolean())
      CHAR_TYPE -> CharHolder(readChar())
      FLOAT_TYPE -> FloatHolder(readFloat())
      DOUBLE_TYPE -> DoubleHolder(readDouble())
      BYTE_TYPE -> ByteHolder(readByte())
      SHORT_TYPE -> ShortHolder(readShort())
      INT_TYPE -> IntHolder(readInt())
      LONG_TYPE -> LongHolder(readLong())
      else -> throw IllegalStateException("Unknown type $type")
    }
  }

  fun readShort(): Short {
    bytesRead += SHORT_SIZE
    return source.readShort()
  }

  fun readInt(): Int {
    bytesRead += INT_SIZE
    return source.readInt()
  }

  fun readIdArray(arrayLength: Int): LongArray {
    return LongArray(arrayLength) { readId() }
  }

  fun readBooleanArray(arrayLength: Int): BooleanArray {
    return BooleanArray(arrayLength) { readByte().toInt() != 0 }
  }

  fun readCharArray(arrayLength: Int): CharArray {
    return CharArray(arrayLength) {
      readChar()
    }
  }

  fun readString(
    byteCount: Int,
    charset: Charset
  ): String {
    bytesRead += byteCount
    return source.readString(byteCount.toLong(), charset)
  }

  fun readFloatArray(arrayLength: Int): FloatArray {
    return FloatArray(arrayLength) { readFloat() }
  }

  fun readDoubleArray(arrayLength: Int): DoubleArray {
    return DoubleArray(arrayLength) { readDouble() }
  }

  fun readShortArray(arrayLength: Int): ShortArray {
    return ShortArray(arrayLength) { readShort() }
  }

  fun readIntArray(arrayLength: Int): IntArray {
    return IntArray(arrayLength) { readInt() }
  }

  fun readLongArray(arrayLength: Int): LongArray {
    return LongArray(arrayLength) { readLong() }
  }

  fun readLong(): Long {
    bytesRead += LONG_SIZE
    return source.readLong()
  }

  fun readByte(): Byte {
    bytesRead += BYTE_SIZE
    return source.readByte()
  }

  fun readBoolean(): Boolean {
    bytesRead += BOOLEAN_SIZE
    return source.readByte()
      .toInt() != 0
  }

  fun readByteArray(byteCount: Int): ByteArray {
    bytesRead += byteCount
    return source.readByteArray(byteCount.toLong())
  }

  fun readChar(): Char {
    return readString(CHAR_SIZE, Charsets.UTF_16BE)[0]
  }

  fun readFloat(): Float {
    return Float.fromBits(readInt())
  }

  fun readDouble(): Double {
    return Double.fromBits(readLong())
  }

  fun readId(): Long {
    // As long as we don't interpret IDs, reading signed values here is fine.
    return when (identifierByteSize) {
      1 -> readByte().toLong()
      2 -> readShort().toLong()
      4 -> readInt().toLong()
      8 -> readLong()
      else -> throw IllegalArgumentException("ID Length must be 1, 2, 4, or 8")
    }
  }

  fun readUtf8(byteCount: Long): String {
    bytesRead += byteCount
    return source.readUtf8(byteCount)
  }

  fun readUnsignedShort(): Int {
    return readShort().toInt() and 0xFFFF
  }

  companion object {
    private val BOOLEAN_SIZE = BOOLEAN.byteSize
    private val CHAR_SIZE = CHAR.byteSize
    private val BYTE_SIZE = BYTE.byteSize
    private val SHORT_SIZE = SHORT.byteSize
    private val INT_SIZE = INT.byteSize
    private val LONG_SIZE = LONG.byteSize

    private val BOOLEAN_TYPE = BOOLEAN.hprofType
    private val CHAR_TYPE = CHAR.hprofType
    private val FLOAT_TYPE = FLOAT.hprofType
    private val DOUBLE_TYPE = DOUBLE.hprofType
    private val BYTE_TYPE = BYTE.hprofType
    private val SHORT_TYPE = SHORT.hprofType
    private val INT_TYPE = INT.hprofType
    private val LONG_TYPE = LONG.hprofType

    private const val INT_MASK = 0xffffffffL
    private const val BYTE_MASK = 0xff
  }
}