main

square/leakcanary

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

ClassFieldsReader.kt

TLDR

The ClassFieldsReader class in the file shark-graph/src/main/java/shark/internal/ClassFieldsReader.kt is responsible for reading fields of a class in a heap dump. It provides methods for reading static fields, instance fields, and determining if a class has reference fields.

Methods

classDumpStaticFields

Reads and returns the static fields of a class.

classDumpFields

Reads and returns the instance fields of a class.

classDumpHasReferenceFields

Checks if a class has any reference fields and returns a boolean indicating the result.

Classes

ClassFieldsReader

The ClassFieldsReader class is responsible for reading the fields of a class in a heap dump. It provides methods for reading static fields, instance fields, and determining if a class has reference fields.

package shark.internal

import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import shark.PrimitiveType
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
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 shark.internal.IndexedObject.IndexedClass

internal class ClassFieldsReader(
  private val identifierByteSize: Int,
  private val classFieldBytes: ByteArray
) {

  fun classDumpStaticFields(indexedClass: IndexedClass): List<StaticFieldRecord> {
    return read(initialPosition = indexedClass.fieldsIndex) {
      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
          )
        )
      }
      staticFields
    }
  }

  fun classDumpFields(indexedClass: IndexedClass): List<FieldRecord> {
    return read(initialPosition = indexedClass.fieldsIndex) {
      skipStaticFields()

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

  fun classDumpHasReferenceFields(indexedClass: IndexedClass): Boolean {
    return read(initialPosition = indexedClass.fieldsIndex) {
      skipStaticFields()
      val fieldCount = readUnsignedShort()
      for (i in 0 until fieldCount) {
        position += identifierByteSize
        val type = readUnsignedByte()
        if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
          return@read true
        }
      }
      return@read false
    }
  }

  private fun <R> read(
    initialPosition: Int,
    block: ReadInFlight.() -> R
  ): R {
    val readInFlight = ReadInFlight()
    readInFlight.position = initialPosition
    return readInFlight.run(block)
  }

  private inner class ReadInFlight {
    var position = 0

    fun skipStaticFields() {
      val staticFieldCount = readUnsignedShort()
      for (i in 0 until staticFieldCount) {
        position += identifierByteSize
        val type = readUnsignedByte()
        position += if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
          identifierByteSize
        } else {
          PrimitiveType.byteSizeByHprofType.getValue(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 readByte(): Byte {
      return classFieldBytes[position++]
    }

    fun readInt(): Int {
      return (classFieldBytes[position++].toInt() and 0xff shl 24) or
        (classFieldBytes[position++].toInt() and 0xff shl 16) or
        (classFieldBytes[position++].toInt() and 0xff shl 8) or
        (classFieldBytes[position++].toInt() and 0xff)
    }

    fun readLong(): Long {
      return (classFieldBytes[position++].toLong() and 0xff shl 56) or
        (classFieldBytes[position++].toLong() and 0xff shl 48) or
        (classFieldBytes[position++].toLong() and 0xff shl 40) or
        (classFieldBytes[position++].toLong() and 0xff shl 32) or
        (classFieldBytes[position++].toLong() and 0xff shl 24) or
        (classFieldBytes[position++].toLong() and 0xff shl 16) or
        (classFieldBytes[position++].toLong() and 0xff shl 8) or
        (classFieldBytes[position++].toLong() and 0xff)
    }

    fun readShort(): Short {
      return ((classFieldBytes[position++].toInt() and 0xff shl 8) or
        (classFieldBytes[position++].toInt() and 0xff)).toShort()
    }

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

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

    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 readBoolean(): Boolean {
      return readByte()
        .toInt() != 0
    }

    fun readChar(): Char {
      return readShort().toChar()
    }

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

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

  companion object {
    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
  }
}