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

<lambda>null1 package shark
2 
3 import shark.HprofRecord.HeapDumpRecord.ObjectRecord
4 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
5 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
6 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
7 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
9 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
10 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
11 import shark.ValueHolder.ReferenceHolder
12 import shark.internal.IndexedObject.IndexedClass
13 import shark.internal.IndexedObject.IndexedInstance
14 import shark.internal.IndexedObject.IndexedObjectArray
15 import shark.internal.IndexedObject.IndexedPrimitiveArray
16 import java.nio.charset.Charset
17 import java.util.Locale
18 import kotlin.LazyThreadSafetyMode.NONE
19 import kotlin.reflect.KClass
20 import shark.PrimitiveType.INT
21 
22 /**
23  * An object in the heap dump.
24  */
25 sealed class HeapObject {
26 
27   /**
28    * The graph of objects in the heap, which you can use to navigate the heap.
29    */
30 
31   abstract val graph: HeapGraph
32 
33   /**
34    * The heap identifier of this object.
35    */
36   abstract val objectId: Long
37 
38   /**
39    * [objectId] masked to be a positive unique identifier, as reported in Android Studio.
40    */
41   val positiveObjectId: Long
42     get() = objectId and (-0x1L ushr (8 - graph.identifierByteSize) * 8)
43 
44   /**
45    * An positive object index that's specific to how Shark stores objects in memory.
46    * The index starts at 0 and ends at [HeapGraph.objectCount] - 1. There are no gaps, every index
47    * value corresponds to an object. Classes are first, then instances, then object arrays then
48    * primitive arrays.
49    */
50   abstract val objectIndex: Int
51 
52   /**
53    * Reads and returns the underlying [ObjectRecord].
54    *
55    * This may trigger IO reads.
56    */
57   abstract fun readRecord(): ObjectRecord
58 
59   /**
60    * The total byte size for the record of this object in the heap dump.
61    */
62   abstract val recordSize: Int
63 
64   /**
65    * This [HeapObject] as a [HeapClass] if it is one, or null otherwise
66    */
67   val asClass: HeapClass?
68     get() = if (this is HeapClass) this else null
69 
70   /**
71    * This [HeapObject] as a [HeapInstance] if it is one, or null otherwise
72    */
73   val asInstance: HeapInstance?
74     get() = if (this is HeapInstance) this else null
75 
76   /**
77    * This [HeapObject] as a [HeapObjectArray] if it is one, or null otherwise
78    */
79   val asObjectArray: HeapObjectArray?
80     get() = if (this is HeapObjectArray) this else null
81 
82   /**
83    * This [HeapObject] as a [HeapPrimitiveArray] if it is one, or null otherwise
84    */
85   val asPrimitiveArray: HeapPrimitiveArray?
86     get() = if (this is HeapPrimitiveArray) this else null
87 
88   /**
89    * A class in the heap dump.
90    */
91   class HeapClass internal constructor(
92     private val hprofGraph: HprofHeapGraph,
93     private val indexedObject: IndexedClass,
94     override val objectId: Long,
95     override val objectIndex: Int
96   ) : HeapObject() {
97     override val graph: HeapGraph
98       get() = hprofGraph
99 
100     /**
101      * Whether this is class is a primitive wrapper type
102      */
103     val isPrimitiveWrapperClass: Boolean
104       get() = (name in primitiveWrapperClassNames)
105 
106     /**
107      * The name of this class, identical to [Class.getName].
108      * If this class is an array class, the name has a suffix of brackets for each dimension of
109      * the array, e.g. `com.Foo[][]` is a class for 2 dimensional arrays of `com.Foo`.
110      *
111      * The behavior for primitive types changes depending on the VM that dumped the heap. JVM
112      * heap dumps don't have any [HeapClass] object for primitive types, instead the
113      * `java.land.Class` class has 9 instances (the 8 primitive types and `void`). Android heap
114      * dumps have an [HeapClass] object for primitive type and the `java.land.Class` class has no
115      * instance.
116      *
117      * If this is an array class, you can find the component type by removing the brackets at the
118      * end, e.g. `name.substringBefore('[')`. Be careful when doing this for JVM heap dumps though,
119      * as if the component type is a primitive type there will not be a [HeapClass] object for it.
120      * This is especially tricky with N dimension primitive type arrays, which are instances of
121      * [HeapObjectArray] (vs single dimension primitive type arrays which are instances of
122      * [HeapPrimitiveArray]).
123      */
124     val name: String
125       get() = hprofGraph.className(objectId)
126 
127     /**
128      * Returns [name] stripped of any string content before the last period (included).
129      */
130     val simpleName: String
131       get() = classSimpleName(name)
132 
133     /**
134      * The total byte size of fields for instances of this class, as registered in the class dump.
135      * This includes the size of fields from superclasses.
136      *
137      * @see readFieldsByteSize
138      */
139     val instanceByteSize: Int
140       get() = indexedObject.instanceSize
141 
142     override val recordSize: Int
143       get() = indexedObject.recordSize.toInt()
144 
145     val hasReferenceInstanceFields: Boolean
146       get() = hprofGraph.classDumpHasReferenceFields(indexedObject)
147 
148     /**
149      * Returns true if this class is an array class, and false otherwise.
150      */
151     val isArrayClass: Boolean
152       get() = name.endsWith("[]")
153 
154     val isPrimitiveArrayClass: Boolean
155       get() = name in primitiveTypesByPrimitiveArrayClassName
156 
157     val isObjectArrayClass: Boolean
158       get() = isArrayClass && !isPrimitiveArrayClass
159 
160     /**
161      * The total byte size of fields for instances of this class, computed as the sum of the
162      * individual size of each field of this class. This does not include the size of fields from
163      * superclasses.
164      *
165      * This may trigger IO reads.
166      *
167      * @see instanceByteSize
168      */
169     fun readFieldsByteSize(): Int {
170       return readRecordFields().sumBy {
171         if (it.type == PrimitiveType.REFERENCE_HPROF_TYPE) {
172           hprofGraph.identifierByteSize
173         } else PrimitiveType.byteSizeByHprofType.getValue(it.type)
174       }
175     }
176 
177     /**
178      * The [HeapClass] representing the superclass of this [HeapClass]. If this [HeapClass]
179      * represents either the [Object] class or a primitive type, then
180      * null is returned. If this [HeapClass] represents an array class then the
181      * [HeapClass] object representing the [Object] class is returned.
182      */
183     val superclass: HeapClass?
184       get() {
185         if (indexedObject.superclassId == ValueHolder.NULL_REFERENCE) return null
186         return hprofGraph.findObjectById(indexedObject.superclassId) as HeapClass
187       }
188 
189     /**
190      * The class hierarchy starting at this class (included) and ending at the [Object] class
191      * (included).
192      */
193     val classHierarchy: Sequence<HeapClass>
194       get() = generateSequence(this) { it.superclass }
195 
196     /**
197      * All the subclasses (direct and indirect) of this class,
198      * in the order they were recorded in the heap dump.
199      */
200     val subclasses: Sequence<HeapClass>
201       get() = hprofGraph.classes.filter { it subclassOf this }
202 
203     /**
204      * Returns true if [subclass] is a sub class of this [HeapClass].
205      */
206     infix fun superclassOf(subclass: HeapClass): Boolean {
207       return subclass.classHierarchy.any { it.objectId == objectId }
208     }
209 
210     /**
211      * Returns true if [superclass] is a superclass of this [HeapClass].
212      */
213     infix fun subclassOf(superclass: HeapClass): Boolean {
214       return superclass.objectId != objectId && classHierarchy.any { it.objectId == superclass.objectId }
215     }
216 
217     /**
218      * All instances of this class, including instances of subclasses of this class.
219      */
220     val instances: Sequence<HeapInstance>
221       get() = if (!isArrayClass) {
222         hprofGraph.instances.filter { it instanceOf this }
223       } else {
224         emptySequence()
225       }
226 
227     val objectArrayInstances: Sequence<HeapObjectArray>
228       get() = if (isObjectArrayClass) {
229         hprofGraph.objectArrays.filter { it.indexedObject.arrayClassId == objectId }
230       } else {
231         emptySequence()
232       }
233 
234     /**
235      * Primitive arrays are one dimensional arrays of a primitive type.
236      * N-dimension arrays of primitive types (e.g. int[][]) are object arrays pointing to primitive
237      * arrays.
238      */
239     val primitiveArrayInstances: Sequence<HeapPrimitiveArray>
240       get() {
241         val primitiveType = primitiveTypesByPrimitiveArrayClassName[name]
242         return if (primitiveType != null) {
243           hprofGraph.primitiveArrays.filter { it.primitiveType == primitiveType }
244         } else {
245           emptySequence()
246         }
247       }
248 
249     /**
250      * All direct instances of this class, ie excluding any instance of subclasses of this class.
251      */
252     val directInstances: Sequence<HeapInstance>
253       get() = hprofGraph.instances.filter { it.indexedObject.classId == objectId }
254 
255     /**
256      * Reads and returns the underlying [ClassDumpRecord].
257      *
258      * This may trigger IO reads.
259      */
260     override fun readRecord(): ClassDumpRecord {
261       return hprofGraph.readClassDumpRecord(objectId, indexedObject)
262     }
263 
264     fun readRecordStaticFields() = hprofGraph.classDumpStaticFields(indexedObject)
265 
266     fun readRecordFields() = hprofGraph.classDumpFields(indexedObject)
267 
268     /**
269      * Returns the name of the field declared in this class for the specified [fieldRecord].
270      */
271     fun instanceFieldName(fieldRecord: FieldRecord): String {
272       return hprofGraph.fieldName(objectId, fieldRecord)
273     }
274 
275     /**
276      * The static fields of this class, as a sequence of [HeapField].
277      *
278      * This may trigger IO reads.
279      */
280     fun readStaticFields(): Sequence<HeapField> {
281       return readRecordStaticFields().asSequence()
282         .map { fieldRecord ->
283           HeapField(
284             this, hprofGraph.staticFieldName(objectId, fieldRecord),
285             HeapValue(hprofGraph, fieldRecord.value)
286           )
287         }
288     }
289 
290     /**
291      * Returns a [HeapField] object that reflects the specified declared
292      * field of the class represented by this [HeapClass] object, or null if this field does not
293      * exist. The [name] parameter specifies the simple name of the desired field.
294      *
295      * Also available as a convenience operator: [get]
296      *
297      * This may trigger IO reads.
298      */
299     fun readStaticField(fieldName: String): HeapField? {
300       for (fieldRecord in readRecordStaticFields()) {
301         val recordFieldName = hprofGraph.staticFieldName(objectId, fieldRecord)
302         if (recordFieldName == fieldName) {
303           return HeapField(this, fieldName, HeapValue(hprofGraph, fieldRecord.value))
304         }
305       }
306       return null
307     }
308 
309     /**
310      * @see readStaticField
311      */
312     operator fun get(fieldName: String) = readStaticField(fieldName)
313 
314     override fun toString(): String {
315       return "class $name"
316     }
317   }
318 
319   /**
320    * An instance in the heap dump.
321    */
322   class HeapInstance internal constructor(
323     private val hprofGraph: HprofHeapGraph,
324     internal val indexedObject: IndexedInstance,
325     override val objectId: Long,
326     override val objectIndex: Int
327   ) : HeapObject() {
328 
329     /**
330      * Whether this is an instance of a primitive wrapper type.
331      */
332     val isPrimitiveWrapper: Boolean
333       get() = instanceClassName in primitiveWrapperClassNames
334 
335     override val graph: HeapGraph
336       get() = hprofGraph
337 
338     /**
339      * @see HeapClass.instanceByteSize
340      */
341     val byteSize
342       get() = instanceClass.instanceByteSize
343 
344     /**
345      * The name of the class of this instance, identical to [Class.getName].
346      */
347     val instanceClassName: String
348       get() = hprofGraph.className(indexedObject.classId)
349 
350     /**
351      * Returns [instanceClassName] stripped of any string content before the last period (included).
352      */
353     val instanceClassSimpleName: String
354       get() = classSimpleName(instanceClassName)
355 
356     /**
357      * The class of this instance.
358      */
359     val instanceClass: HeapClass
360       get() = hprofGraph.findObjectById(indexedObject.classId) as HeapClass
361 
362     /**
363      * The heap identifier of the class of this instance.
364      */
365     val instanceClassId: Long
366       get() = indexedObject.classId
367 
368     /**
369      * Reads and returns the underlying [InstanceDumpRecord].
370      *
371      * This may trigger IO reads.
372      */
373     override fun readRecord(): InstanceDumpRecord {
374       return hprofGraph.readInstanceDumpRecord(objectId, indexedObject)
375     }
376 
377     override val recordSize: Int
378       get() = indexedObject.recordSize.toInt()
379 
380     /**
381      * Returns true if this is an instance of the class named [className] or an instance of a
382      * subclass of that class.
383      */
384     infix fun instanceOf(className: String): Boolean =
385       instanceClass.classHierarchy.any { it.name == className }
386 
387     /**
388      * Returns true if this is an instance of [expectedClass] or an instance of a subclass of that
389      * class.
390      */
391     infix fun instanceOf(expectedClass: KClass<*>) =
392       this instanceOf expectedClass.java.name
393 
394     /**
395      * Returns true if this is an instance of [expectedClass] or an instance of a subclass of that
396      * class.
397      */
398     infix fun instanceOf(expectedClass: HeapClass) =
399       instanceClass.classHierarchy.any { it.objectId == expectedClass.objectId }
400 
401     /**
402      * @see readField
403      */
404     fun readField(
405       declaringClass: KClass<out Any>,
406       fieldName: String
407     ): HeapField? {
408       return readField(declaringClass.java.name, fieldName)
409     }
410 
411     /**
412      * Returns a [HeapField] object that reflects the specified declared
413      * field of the instance represented by this [HeapInstance] object, or null if this field does
414      * not exist. The [declaringClassName] specifies the class in which the desired field is
415      * declared, and the [fieldName] parameter specifies the simple name of the desired field.
416      *
417      * Also available as a convenience operator: [get]
418      *
419      * This may trigger IO reads.
420      */
421     fun readField(
422       declaringClassName: String,
423       fieldName: String
424     ): HeapField? {
425       return readFields().firstOrNull { field -> field.declaringClass.name == declaringClassName && field.name == fieldName }
426     }
427 
428     /**
429      * @see readField
430      */
431     operator fun get(
432       declaringClass: KClass<out Any>,
433       fieldName: String
434     ): HeapField? {
435       return readField(declaringClass, fieldName)
436     }
437 
438     /**
439      * @see readField
440      */
441     operator fun get(
442       declaringClassName: String,
443       fieldName: String
444     ) = readField(declaringClassName, fieldName)
445 
446     /**
447      * The fields of this instance, as a sequence of [HeapField].
448      *
449      * This may trigger IO reads.
450      */
451     fun readFields(): Sequence<HeapField> {
452       val fieldReader by lazy(NONE) {
453         hprofGraph.createFieldValuesReader(readRecord())
454       }
455       return instanceClass.classHierarchy
456         .map { heapClass ->
457           heapClass.readRecordFields().asSequence()
458             .map { fieldRecord ->
459               val fieldName = hprofGraph.fieldName(heapClass.objectId, fieldRecord)
460               val fieldValue = fieldReader.readValue(fieldRecord)
461               HeapField(heapClass, fieldName, HeapValue(hprofGraph, fieldValue))
462             }
463         }
464         .flatten()
465     }
466 
467     /**
468      * If this [HeapInstance] is an instance of the [String] class, returns a [String] instance
469      * with content that matches the string in the heap dump. Otherwise returns null.
470      *
471      * This may trigger IO reads.
472      */
473     fun readAsJavaString(): String? {
474       if (instanceClassName != "java.lang.String") {
475         return null
476       }
477 
478       // JVM strings don't have a count field.
479       val count = this["java.lang.String", "count"]?.value?.asInt
480       if (count == 0) {
481         return ""
482       }
483 
484       // Prior to API 26 String.value was a char array.
485       // Since API 26 String.value is backed by native code. The vast majority of strings in a
486       // heap dump are backed by a byte array, but we still find a few backed by a char array.
487       when (val valueRecord =
488         this["java.lang.String", "value"]!!.value.asObject!!.readRecord()) {
489         is CharArrayDump -> {
490           // < API 23
491           // As of Marshmallow, substrings no longer share their parent strings' char arrays
492           // eliminating the need for String.offset
493           // https://android-review.googlesource.com/#/c/83611/
494           val offset = this["java.lang.String", "offset"]?.value?.asInt
495 
496           val chars = if (count != null && offset != null) {
497             // Handle heap dumps where all primitive arrays have been replaced with empty arrays,
498             // e.g. with HprofPrimitiveArrayStripper
499             val toIndex = if (offset + count > valueRecord.array.size) {
500               valueRecord.array.size
501             } else offset + count
502             valueRecord.array.copyOfRange(offset, toIndex)
503           } else {
504             valueRecord.array
505           }
506           return String(chars)
507         }
508         is ByteArrayDump -> {
509           return String(valueRecord.array, Charset.forName("UTF-8"))
510         }
511         else -> throw UnsupportedOperationException(
512           "'value' field ${this["java.lang.String", "value"]!!.value} was expected to be either" +
513             " a char or byte array in string instance with id $objectId"
514         )
515       }
516     }
517 
518     override fun toString(): String {
519       return "instance @$objectId of $instanceClassName"
520     }
521   }
522 
523   /**
524    * An object array in the heap dump.
525    */
526   class HeapObjectArray internal constructor(
527     private val hprofGraph: HprofHeapGraph,
528     internal val indexedObject: IndexedObjectArray,
529     override val objectId: Long,
530     override val objectIndex: Int
531   ) : HeapObject() {
532 
533     override val graph: HeapGraph
534       get() = hprofGraph
535 
536     /**
537      * The name of the class of this array, identical to [Class.getName].
538      */
539     val arrayClassName: String
540       get() = hprofGraph.className(indexedObject.arrayClassId)
541 
542     /**
543      * Returns [arrayClassName] stripped of any string content before the last period (included).
544      */
545     val arrayClassSimpleName: String
546       get() = classSimpleName(arrayClassName)
547 
548     /**
549      * The class of this array.
550      */
551     val arrayClass: HeapClass
552       get() = hprofGraph.findObjectById(indexedObject.arrayClassId) as HeapClass
553 
554     /**
555      * The heap identifier of the class of this array.
556      */
557     val arrayClassId: Long
558       get() = indexedObject.arrayClassId
559 
560 
561     @Deprecated("Use byteSize property instead", ReplaceWith("byteSize"))
562     fun readByteSize() = byteSize
563 
564     /**
565      * The total byte shallow size of elements in this array.
566      */
567     val byteSize: Int
568       get() = recordSize - hprofGraph.objectArrayRecordNonElementSize
569 
570     /**
571      * Reads and returns the underlying [ObjectArrayDumpRecord].
572      *
573      * This may trigger IO reads.
574      */
575     override fun readRecord(): ObjectArrayDumpRecord {
576       return hprofGraph.readObjectArrayDumpRecord(objectId, indexedObject)
577     }
578 
579     override val recordSize: Int
580       get() = indexedObject.recordSize.toInt()
581 
582     /**
583      * The elements in this array, as a sequence of [HeapValue].
584      *
585      * This may trigger IO reads.
586      */
587     fun readElements(): Sequence<HeapValue> {
588       return readRecord().elementIds.asSequence()
589         .map { HeapValue(hprofGraph, ReferenceHolder(it)) }
590     }
591 
592     override fun toString(): String {
593       return "object array @$objectId of $arrayClassName"
594     }
595   }
596 
597   /**
598    * A primitive array in the heap dump.
599    */
600   class HeapPrimitiveArray internal constructor(
601     private val hprofGraph: HprofHeapGraph,
602     private val indexedObject: IndexedPrimitiveArray,
603     override val objectId: Long,
604     override val objectIndex: Int
605   ) : HeapObject() {
606 
607     override val graph: HeapGraph
608       get() = hprofGraph
609 
610     @Deprecated("Use byteSize property instead", ReplaceWith("byteSize"))
611     fun readByteSize() = byteSize
612 
613     /**
614      * The total byte shallow size of elements in this array.
615      */
616     val byteSize: Int
617       get() = recordSize - hprofGraph.primitiveArrayRecordNonElementSize
618 
619 
620     /**
621      * The [PrimitiveType] of elements in this array.
622      */
623     val primitiveType: PrimitiveType
624       get() = indexedObject.primitiveType
625 
626     /**
627      * The name of the class of this array, identical to [Class.getName].
628      */
629     val arrayClassName: String
630       get() = "${primitiveType.name.toLowerCase(Locale.US)}[]"
631 
632     /**
633      * The class of this array.
634      */
635     val arrayClass: HeapClass
636       get() = graph.findClassByName(arrayClassName)!!
637 
638     /**
639      * Reads and returns the underlying [PrimitiveArrayDumpRecord].
640      *
641      * This may trigger IO reads.
642      */
643     override fun readRecord(): PrimitiveArrayDumpRecord {
644       return hprofGraph.readPrimitiveArrayDumpRecord(objectId, indexedObject)
645     }
646 
647     override val recordSize: Int
648       get() = indexedObject.recordSize.toInt()
649 
650     override fun toString(): String {
651       return "primitive array @$objectId of $arrayClassName"
652     }
653   }
654 
655   companion object {
656 
657     internal val primitiveTypesByPrimitiveArrayClassName =
658       PrimitiveType.values().associateBy { "${it.name.toLowerCase(Locale.US)}[]" }
659 
660     private val primitiveWrapperClassNames = setOf<String>(
661       Boolean::class.javaObjectType.name, Char::class.javaObjectType.name,
662       Float::class.javaObjectType.name,
663       Double::class.javaObjectType.name, Byte::class.javaObjectType.name,
664       Short::class.javaObjectType.name,
665       Int::class.javaObjectType.name, Long::class.javaObjectType.name
666     )
667 
668     private fun classSimpleName(className: String): String {
669       val separator = className.lastIndexOf('.')
670       return if (separator == -1) {
671         className
672       } else {
673         className.substring(separator + 1)
674       }
675     }
676   }
677 }
678