xref: /aosp_15_r20/external/leakcanary2/shark-hprof/src/main/java/shark/HprofRecordReader.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package shark
2 
3 import okio.BufferedSource
4 import shark.GcRoot.Debugger
5 import shark.GcRoot.Finalizing
6 import shark.GcRoot.InternedString
7 import shark.GcRoot.JavaFrame
8 import shark.GcRoot.JniGlobal
9 import shark.GcRoot.JniLocal
10 import shark.GcRoot.JniMonitor
11 import shark.GcRoot.MonitorUsed
12 import shark.GcRoot.NativeStack
13 import shark.GcRoot.ReferenceCleanup
14 import shark.GcRoot.StickyClass
15 import shark.GcRoot.ThreadBlock
16 import shark.GcRoot.ThreadObject
17 import shark.GcRoot.Unknown
18 import shark.GcRoot.Unreachable
19 import shark.GcRoot.VmInternal
20 import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
21 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
22 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
23 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
24 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
25 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
26 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
27 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
28 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
29 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
30 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
31 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
32 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
33 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
34 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
35 import shark.HprofRecord.LoadClassRecord
36 import shark.HprofRecord.StackFrameRecord
37 import shark.HprofRecord.StackTraceRecord
38 import shark.HprofRecord.StringRecord
39 import shark.PrimitiveType.BOOLEAN
40 import shark.PrimitiveType.BYTE
41 import shark.PrimitiveType.CHAR
42 import shark.PrimitiveType.DOUBLE
43 import shark.PrimitiveType.FLOAT
44 import shark.PrimitiveType.INT
45 import shark.PrimitiveType.LONG
46 import shark.PrimitiveType.SHORT
47 import shark.ValueHolder.BooleanHolder
48 import shark.ValueHolder.ByteHolder
49 import shark.ValueHolder.CharHolder
50 import shark.ValueHolder.DoubleHolder
51 import shark.ValueHolder.FloatHolder
52 import shark.ValueHolder.IntHolder
53 import shark.ValueHolder.LongHolder
54 import shark.ValueHolder.ReferenceHolder
55 import shark.ValueHolder.ShortHolder
56 import java.nio.charset.Charset
57 
58 /**
59  * Reads hprof content from an Okio [BufferedSource].
60  *
61  * Binary Dump Format reference: http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share
62  * /demo/jvmti/hprof/manual.html#mozTocId848088
63  *
64  * The Android Hprof format differs in some ways from that reference. This parser implementation
65  * is largely adapted from https://android.googlesource.com/platform/tools/base/+/studio-master-dev
66  * /perflib/src/main/java/com/android/tools/perflib
67  *
68  * Not thread safe, should be used from a single thread.
69  */
70 @Suppress("LargeClass", "TooManyFunctions")
71 class HprofRecordReader internal constructor(
72   header: HprofHeader,
73   private val source: BufferedSource
74 ) {
75 
76   /**
77    * How many bytes this reader has read from [source]. Can only increase.
78    */
79   var bytesRead = 0L
80     private set
81 
82   private val identifierByteSize = header.identifierByteSize
83 
84   private val typeSizes: IntArray
85 
86   init {
87     val typeSizesMap =
88       PrimitiveType.byteSizeByHprofType + (PrimitiveType.REFERENCE_HPROF_TYPE to identifierByteSize)
89 
90     val maxKey = typeSizesMap.keys.max()!!
91 
92     typeSizes = IntArray(maxKey + 1) { key ->
93       typeSizesMap[key] ?: 0
94     }
95   }
96 
97   fun sizeOf(type: Int) = typeSizes[type]
98 
99   fun readStringRecord(length: Long) = StringRecord(
100     id = readId(),
101     string = readUtf8(length - identifierByteSize)
102   )
103 
104   fun readLoadClassRecord() = LoadClassRecord(
105     classSerialNumber = readInt(),
106     id = readId(),
107     stackTraceSerialNumber = readInt(),
108     classNameStringId = readId()
109   )
110 
111   fun readStackFrameRecord() = StackFrameRecord(
112     id = readId(),
113     methodNameStringId = readId(),
114     methodSignatureStringId = readId(),
115     sourceFileNameStringId = readId(),
116     classSerialNumber = readInt(),
117     lineNumber = readInt()
118   )
119 
120   fun readStackTraceRecord() = StackTraceRecord(
121     stackTraceSerialNumber = readInt(),
122     threadSerialNumber = readInt(),
123     stackFrameIds = readIdArray(readInt())
124   )
125 
126   fun readUnknownGcRootRecord() = Unknown(id = readId())
127 
128   fun readJniGlobalGcRootRecord() = JniGlobal(
129     id = readId(),
130     jniGlobalRefId = readId()
131   )
132 
133   fun readJniLocalGcRootRecord() = JniLocal(
134     id = readId(),
135     threadSerialNumber = readInt(),
136     frameNumber = readInt()
137   )
138 
139   fun readJavaFrameGcRootRecord() = JavaFrame(
140     id = readId(),
141     threadSerialNumber = readInt(),
142     frameNumber = readInt()
143   )
144 
145   fun readNativeStackGcRootRecord() = NativeStack(
146     id = readId(),
147     threadSerialNumber = readInt()
148   )
149 
150   fun readStickyClassGcRootRecord() = StickyClass(id = readId())
151 
152   fun readThreadBlockGcRootRecord() = ThreadBlock(id = readId(), threadSerialNumber = readInt())
153 
154   fun readMonitorUsedGcRootRecord() = MonitorUsed(id = readId())
155 
156   fun readThreadObjectGcRootRecord() = ThreadObject(
157     id = readId(),
158     threadSerialNumber = readInt(),
159     stackTraceSerialNumber = readInt()
160   )
161 
162   fun readInternedStringGcRootRecord() = InternedString(id = readId())
163 
164   fun readFinalizingGcRootRecord() = Finalizing(id = readId())
165 
166   fun readDebuggerGcRootRecord() = Debugger(id = readId())
167 
168   fun readReferenceCleanupGcRootRecord() = ReferenceCleanup(id = readId())
169 
170   fun readVmInternalGcRootRecord() = VmInternal(id = readId())
171 
172   fun readJniMonitorGcRootRecord() = JniMonitor(
173     id = readId(),
174     stackTraceSerialNumber = readInt(),
175     stackDepth = readInt()
176   )
177 
178   fun readUnreachableGcRootRecord() = Unreachable(id = readId())
179 
180   /**
181    * Reads a full instance record after a instance dump tag.
182    */
183   fun readInstanceDumpRecord(): InstanceDumpRecord {
184     val id = readId()
185     val stackTraceSerialNumber = readInt()
186     val classId = readId()
187     val remainingBytesInInstance = readInt()
188     val fieldValues = readByteArray(remainingBytesInInstance)
189     return InstanceDumpRecord(
190       id = id,
191       stackTraceSerialNumber = stackTraceSerialNumber,
192       classId = classId,
193       fieldValues = fieldValues
194     )
195   }
196 
197   fun readHeapDumpInfoRecord(): HeapDumpInfoRecord {
198     val heapId = readInt()
199     return HeapDumpInfoRecord(heapId = heapId, heapNameStringId = readId())
200   }
201 
202   /**
203    * Reads a full class record after a class dump tag.
204    */
205   fun readClassDumpRecord(): ClassDumpRecord {
206     val id = readId()
207     // stack trace serial number
208     val stackTraceSerialNumber = readInt()
209     val superclassId = readId()
210     // class loader object ID
211     val classLoaderId = readId()
212     // signers object ID
213     val signersId = readId()
214     // protection domain object ID
215     val protectionDomainId = readId()
216     // reserved
217     readId()
218     // reserved
219     readId()
220 
221     // instance size (in bytes)
222     // Useful to compute retained size
223     val instanceSize = readInt()
224 
225     // Skip over the constant pool
226     val constantPoolCount = readUnsignedShort()
227     for (i in 0 until constantPoolCount) {
228       // constant pool index
229       skip(SHORT_SIZE)
230       skip(typeSizes[readUnsignedByte()])
231     }
232 
233     val staticFieldCount = readUnsignedShort()
234     val staticFields = ArrayList<StaticFieldRecord>(staticFieldCount)
235     for (i in 0 until staticFieldCount) {
236 
237       val nameStringId = readId()
238       val type = readUnsignedByte()
239       val value = readValue(type)
240 
241       staticFields.add(
242         StaticFieldRecord(
243           nameStringId = nameStringId,
244           type = type,
245           value = value
246         )
247       )
248     }
249 
250     val fieldCount = readUnsignedShort()
251     val fields = ArrayList<FieldRecord>(fieldCount)
252     for (i in 0 until fieldCount) {
253       fields.add(FieldRecord(nameStringId = readId(), type = readUnsignedByte()))
254     }
255 
256     return ClassDumpRecord(
257       id = id,
258       stackTraceSerialNumber = stackTraceSerialNumber,
259       superclassId = superclassId,
260       classLoaderId = classLoaderId,
261       signersId = signersId,
262       protectionDomainId = protectionDomainId,
263       instanceSize = instanceSize,
264       staticFields = staticFields,
265       fields = fields
266     )
267   }
268 
269   /**
270    * Reads a full primitive array record after a primitive array dump tag.
271    */
272   fun readPrimitiveArrayDumpRecord(): PrimitiveArrayDumpRecord {
273     val id = readId()
274     val stackTraceSerialNumber = readInt()
275     // length
276     val arrayLength = readInt()
277     return when (val type = readUnsignedByte()) {
278       BOOLEAN_TYPE -> BooleanArrayDump(
279         id, stackTraceSerialNumber, readBooleanArray(arrayLength)
280       )
281       CHAR_TYPE -> CharArrayDump(
282         id, stackTraceSerialNumber, readCharArray(arrayLength)
283       )
284       FLOAT_TYPE -> FloatArrayDump(
285         id, stackTraceSerialNumber, readFloatArray(arrayLength)
286       )
287       DOUBLE_TYPE -> DoubleArrayDump(
288         id, stackTraceSerialNumber, readDoubleArray(arrayLength)
289       )
290       BYTE_TYPE -> ByteArrayDump(
291         id, stackTraceSerialNumber, readByteArray(arrayLength)
292       )
293       SHORT_TYPE -> ShortArrayDump(
294         id, stackTraceSerialNumber, readShortArray(arrayLength)
295       )
296       INT_TYPE -> IntArrayDump(
297         id, stackTraceSerialNumber, readIntArray(arrayLength)
298       )
299       LONG_TYPE -> LongArrayDump(
300         id, stackTraceSerialNumber, readLongArray(arrayLength)
301       )
302       else -> throw IllegalStateException("Unexpected type $type")
303     }
304   }
305 
306   /**
307    * Reads a full object array record after a object array dump tag.
308    */
309   fun readObjectArrayDumpRecord(): ObjectArrayDumpRecord {
310     val id = readId()
311     // stack trace serial number
312     val stackTraceSerialNumber = readInt()
313     val arrayLength = readInt()
314     val arrayClassId = readId()
315     val elementIds = readIdArray(arrayLength)
316     return ObjectArrayDumpRecord(
317       id = id,
318       stackTraceSerialNumber = stackTraceSerialNumber,
319       arrayClassId = arrayClassId,
320       elementIds = elementIds
321     )
322   }
323 
324   fun skipClassDumpHeader() {
325     skip(INT_SIZE * 2 + identifierByteSize * 7)
326     skipClassDumpConstantPool()
327   }
328 
329   fun skipClassDumpConstantPool() {
330     // Skip over the constant pool
331     val constantPoolCount = readUnsignedShort()
332     for (i in 0 until constantPoolCount) {
333       // constant pool index
334       skip(SHORT.byteSize)
335       skip(sizeOf(readUnsignedByte()))
336     }
337   }
338 
339   fun skipClassDumpStaticFields() {
340     val staticFieldCount = readUnsignedShort()
341     for (i in 0 until staticFieldCount) {
342       skip(identifierByteSize)
343       val type = readUnsignedByte()
344       skip(
345         if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
346           identifierByteSize
347         } else {
348           PrimitiveType.byteSizeByHprofType.getValue(type)
349         }
350       )
351     }
352   }
353 
354   fun skipClassDumpFields() {
355     val fieldCount = readUnsignedShort()
356     skip((identifierByteSize + 1) * fieldCount)
357   }
358 
359   fun skipInstanceDumpRecord() {
360     skip(identifierByteSize + INT_SIZE + identifierByteSize)
361     val remainingBytesInInstance = readInt()
362     skip(remainingBytesInInstance)
363   }
364 
365   fun skipClassDumpRecord() {
366     skip(
367       identifierByteSize + INT_SIZE + identifierByteSize + identifierByteSize + identifierByteSize +
368         identifierByteSize + identifierByteSize + identifierByteSize + INT_SIZE
369     )
370     // Skip over the constant pool
371     val constantPoolCount = readUnsignedShort()
372     for (i in 0 until constantPoolCount) {
373       // constant pool index
374       skip(SHORT_SIZE)
375       skip(typeSizes[readUnsignedByte()])
376     }
377 
378     val staticFieldCount = readUnsignedShort()
379 
380     for (i in 0 until staticFieldCount) {
381       skip(identifierByteSize)
382       val type = readUnsignedByte()
383       skip(typeSizes[type])
384     }
385 
386     val fieldCount = readUnsignedShort()
387     skip(fieldCount * (identifierByteSize + BYTE_SIZE))
388   }
389 
390   fun skipObjectArrayDumpRecord() {
391     skip(identifierByteSize + INT_SIZE)
392     val arrayLength = readInt()
393     skip(identifierByteSize + arrayLength * identifierByteSize)
394   }
395 
396   fun skipPrimitiveArrayDumpRecord() {
397     skip(identifierByteSize + INT_SIZE)
398     val arrayLength = readInt()
399     val type = readUnsignedByte()
400     skip(arrayLength * typeSizes[type])
401   }
402 
403   fun skipHeapDumpInfoRecord() {
404     skip(identifierByteSize + identifierByteSize)
405   }
406 
407   fun skip(byteCount: Int) {
408     bytesRead += byteCount
409     return source.skip(byteCount.toLong())
410   }
411 
412   fun skipId() {
413     skip(identifierByteSize)
414   }
415 
416   fun skip(byteCount: Long) {
417     bytesRead += byteCount
418     return source.skip(byteCount)
419   }
420 
421   fun readUnsignedInt(): Long {
422     return readInt().toLong() and INT_MASK
423   }
424 
425   fun readUnsignedByte(): Int {
426     return readByte().toInt() and BYTE_MASK
427   }
428 
429   /**
430    * Reads a value in the heap dump, which can be a reference or a primitive type.
431    */
432   fun readValue(type: Int): ValueHolder {
433     return when (type) {
434       PrimitiveType.REFERENCE_HPROF_TYPE -> ReferenceHolder(readId())
435       BOOLEAN_TYPE -> BooleanHolder(readBoolean())
436       CHAR_TYPE -> CharHolder(readChar())
437       FLOAT_TYPE -> FloatHolder(readFloat())
438       DOUBLE_TYPE -> DoubleHolder(readDouble())
439       BYTE_TYPE -> ByteHolder(readByte())
440       SHORT_TYPE -> ShortHolder(readShort())
441       INT_TYPE -> IntHolder(readInt())
442       LONG_TYPE -> LongHolder(readLong())
443       else -> throw IllegalStateException("Unknown type $type")
444     }
445   }
446 
447   fun readShort(): Short {
448     bytesRead += SHORT_SIZE
449     return source.readShort()
450   }
451 
452   fun readInt(): Int {
453     bytesRead += INT_SIZE
454     return source.readInt()
455   }
456 
457   fun readIdArray(arrayLength: Int): LongArray {
458     return LongArray(arrayLength) { readId() }
459   }
460 
461   fun readBooleanArray(arrayLength: Int): BooleanArray {
462     return BooleanArray(arrayLength) { readByte().toInt() != 0 }
463   }
464 
465   fun readCharArray(arrayLength: Int): CharArray {
466     return CharArray(arrayLength) {
467       readChar()
468     }
469   }
470 
471   fun readString(
472     byteCount: Int,
473     charset: Charset
474   ): String {
475     bytesRead += byteCount
476     return source.readString(byteCount.toLong(), charset)
477   }
478 
479   fun readFloatArray(arrayLength: Int): FloatArray {
480     return FloatArray(arrayLength) { readFloat() }
481   }
482 
483   fun readDoubleArray(arrayLength: Int): DoubleArray {
484     return DoubleArray(arrayLength) { readDouble() }
485   }
486 
487   fun readShortArray(arrayLength: Int): ShortArray {
488     return ShortArray(arrayLength) { readShort() }
489   }
490 
491   fun readIntArray(arrayLength: Int): IntArray {
492     return IntArray(arrayLength) { readInt() }
493   }
494 
495   fun readLongArray(arrayLength: Int): LongArray {
496     return LongArray(arrayLength) { readLong() }
497   }
498 
499   fun readLong(): Long {
500     bytesRead += LONG_SIZE
501     return source.readLong()
502   }
503 
504   fun readByte(): Byte {
505     bytesRead += BYTE_SIZE
506     return source.readByte()
507   }
508 
509   fun readBoolean(): Boolean {
510     bytesRead += BOOLEAN_SIZE
511     return source.readByte()
512       .toInt() != 0
513   }
514 
515   fun readByteArray(byteCount: Int): ByteArray {
516     bytesRead += byteCount
517     return source.readByteArray(byteCount.toLong())
518   }
519 
520   fun readChar(): Char {
521     return readString(CHAR_SIZE, Charsets.UTF_16BE)[0]
522   }
523 
524   fun readFloat(): Float {
525     return Float.fromBits(readInt())
526   }
527 
528   fun readDouble(): Double {
529     return Double.fromBits(readLong())
530   }
531 
532   fun readId(): Long {
533     // As long as we don't interpret IDs, reading signed values here is fine.
534     return when (identifierByteSize) {
535       1 -> readByte().toLong()
536       2 -> readShort().toLong()
537       4 -> readInt().toLong()
538       8 -> readLong()
539       else -> throw IllegalArgumentException("ID Length must be 1, 2, 4, or 8")
540     }
541   }
542 
543   fun readUtf8(byteCount: Long): String {
544     bytesRead += byteCount
545     return source.readUtf8(byteCount)
546   }
547 
548   fun readUnsignedShort(): Int {
549     return readShort().toInt() and 0xFFFF
550   }
551 
552   companion object {
553     private val BOOLEAN_SIZE = BOOLEAN.byteSize
554     private val CHAR_SIZE = CHAR.byteSize
555     private val BYTE_SIZE = BYTE.byteSize
556     private val SHORT_SIZE = SHORT.byteSize
557     private val INT_SIZE = INT.byteSize
558     private val LONG_SIZE = LONG.byteSize
559 
560     private val BOOLEAN_TYPE = BOOLEAN.hprofType
561     private val CHAR_TYPE = CHAR.hprofType
562     private val FLOAT_TYPE = FLOAT.hprofType
563     private val DOUBLE_TYPE = DOUBLE.hprofType
564     private val BYTE_TYPE = BYTE.hprofType
565     private val SHORT_TYPE = SHORT.hprofType
566     private val INT_TYPE = INT.hprofType
567     private val LONG_TYPE = LONG.hprofType
568 
569     private const val INT_MASK = 0xffffffffL
570     private const val BYTE_MASK = 0xff
571   }
572 }
573