<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