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

<lambda>null1 package shark.internal
2 
3 import java.util.EnumSet
4 import kotlin.math.max
5 import shark.GcRoot
6 import shark.GcRoot.StickyClass
7 import shark.HprofHeader
8 import shark.HprofRecordReader
9 import shark.HprofRecordTag
10 import shark.HprofRecordTag.CLASS_DUMP
11 import shark.HprofRecordTag.INSTANCE_DUMP
12 import shark.HprofRecordTag.LOAD_CLASS
13 import shark.HprofRecordTag.OBJECT_ARRAY_DUMP
14 import shark.HprofRecordTag.PRIMITIVE_ARRAY_DUMP
15 import shark.HprofRecordTag.ROOT_DEBUGGER
16 import shark.HprofRecordTag.ROOT_FINALIZING
17 import shark.HprofRecordTag.ROOT_INTERNED_STRING
18 import shark.HprofRecordTag.ROOT_JAVA_FRAME
19 import shark.HprofRecordTag.ROOT_JNI_GLOBAL
20 import shark.HprofRecordTag.ROOT_JNI_LOCAL
21 import shark.HprofRecordTag.ROOT_JNI_MONITOR
22 import shark.HprofRecordTag.ROOT_MONITOR_USED
23 import shark.HprofRecordTag.ROOT_NATIVE_STACK
24 import shark.HprofRecordTag.ROOT_REFERENCE_CLEANUP
25 import shark.HprofRecordTag.ROOT_STICKY_CLASS
26 import shark.HprofRecordTag.ROOT_THREAD_BLOCK
27 import shark.HprofRecordTag.ROOT_THREAD_OBJECT
28 import shark.HprofRecordTag.ROOT_UNKNOWN
29 import shark.HprofRecordTag.ROOT_UNREACHABLE
30 import shark.HprofRecordTag.ROOT_VM_INTERNAL
31 import shark.HprofRecordTag.STRING_IN_UTF8
32 import shark.HprofVersion
33 import shark.HprofVersion.ANDROID
34 import shark.OnHprofRecordTagListener
35 import shark.PrimitiveType
36 import shark.PrimitiveType.INT
37 import shark.ProguardMapping
38 import shark.StreamingHprofReader
39 import shark.ValueHolder
40 import shark.internal.IndexedObject.IndexedClass
41 import shark.internal.IndexedObject.IndexedInstance
42 import shark.internal.IndexedObject.IndexedObjectArray
43 import shark.internal.IndexedObject.IndexedPrimitiveArray
44 import shark.internal.hppc.IntObjectPair
45 import shark.internal.hppc.LongLongScatterMap
46 import shark.internal.hppc.LongObjectPair
47 import shark.internal.hppc.LongObjectScatterMap
48 import shark.internal.hppc.LongScatterSet
49 import shark.internal.hppc.to
50 
51 /**
52  * This class is not thread safe, should be used from a single thread.
53  */
54 internal class HprofInMemoryIndex private constructor(
55   private val positionSize: Int,
56   private val hprofStringCache: LongObjectScatterMap<String>,
57   private val classNames: LongLongScatterMap,
58   private val classIndex: SortedBytesMap,
59   private val instanceIndex: SortedBytesMap,
60   private val objectArrayIndex: SortedBytesMap,
61   private val primitiveArrayIndex: SortedBytesMap,
62   private val gcRoots: List<GcRoot>,
63   private val proguardMapping: ProguardMapping?,
64   private val bytesForClassSize: Int,
65   private val bytesForInstanceSize: Int,
66   private val bytesForObjectArraySize: Int,
67   private val bytesForPrimitiveArraySize: Int,
68   private val useForwardSlashClassPackageSeparator: Boolean,
69   val classFieldsReader: ClassFieldsReader,
70   private val classFieldsIndexSize: Int,
71   private val stickyClassGcRootIds: LongScatterSet,
72 ) {
73 
74   val classCount: Int
75     get() = classIndex.size
76 
77   val instanceCount: Int
78     get() = instanceIndex.size
79 
80   val objectArrayCount: Int
81     get() = objectArrayIndex.size
82 
83   val primitiveArrayCount: Int
84     get() = primitiveArrayIndex.size
85 
86   fun fieldName(
87     classId: Long,
88     id: Long
89   ): String {
90     val fieldNameString = hprofStringById(id)
91     return proguardMapping?.let {
92       val classNameStringId = classNames[classId]
93       val classNameString = hprofStringById(classNameStringId)
94       proguardMapping.deobfuscateFieldName(classNameString, fieldNameString)
95     } ?: fieldNameString
96   }
97 
98   fun className(classId: Long): String {
99     // String, primitive types
100     val classNameStringId = classNames[classId]
101     val classNameString = hprofStringById(classNameStringId)
102     return (proguardMapping?.deobfuscateClassName(classNameString) ?: classNameString).run {
103       if (useForwardSlashClassPackageSeparator) {
104         // JVM heap dumps use "/" for package separators (vs "." for Android heap dumps)
105         replace('/', '.')
106       } else this
107     }
108   }
109 
110   /**
111    * Returns the first class that matches the provided name, prioritizing system classes (as held
112    * by sticky class gc roots).
113    *
114    * On Android, all currently loaded classes are sticky. The heap dump may also contain classes
115    * that were unloaded but not garbage collected yet, leading to classes being present twice
116    * in the heap dump. To work around that we prioritize classes that are held by a sticky class GC
117    * root.
118    */
119   fun classId(className: String): Long? {
120     val internalClassName = if (useForwardSlashClassPackageSeparator) {
121       // JVM heap dumps use "/" for package separators (vs "." for Android heap dumps)
122       className.replace('.', '/')
123     } else className
124 
125     // Note: this performs two linear scans over arrays
126     val hprofStringId = hprofStringCache.entrySequence()
127       .firstOrNull { it.second == internalClassName }
128       ?.first ?: return null
129 
130     val classNamesIterator = classNames.entrySequence().iterator()
131 
132     var firstNonStickyMatchingClass: Long? = null
133     while(classNamesIterator.hasNext()) {
134       val (classId, classNameStringId) = classNamesIterator.next()
135       if (hprofStringId == classNameStringId) {
136         if (classId in stickyClassGcRootIds) {
137           return classId
138         } else {
139           firstNonStickyMatchingClass = classId
140         }
141       }
142     }
143     return firstNonStickyMatchingClass
144   }
145 
146   fun indexedClassSequence(): Sequence<LongObjectPair<IndexedClass>> {
147     return classIndex.entrySequence()
148       .map {
149         val id = it.first
150         val array = it.second
151         id to array.readClass()
152       }
153   }
154 
155   fun indexedInstanceSequence(): Sequence<LongObjectPair<IndexedInstance>> {
156     return instanceIndex.entrySequence()
157       .map {
158         val id = it.first
159         val array = it.second
160         val instance = IndexedInstance(
161           position = array.readTruncatedLong(positionSize),
162           classId = array.readId(),
163           recordSize = array.readTruncatedLong(bytesForInstanceSize)
164         )
165         id to instance
166       }
167   }
168 
169   fun indexedObjectArraySequence(): Sequence<LongObjectPair<IndexedObjectArray>> {
170     return objectArrayIndex.entrySequence()
171       .map {
172         val id = it.first
173         val array = it.second
174         val objectArray = IndexedObjectArray(
175           position = array.readTruncatedLong(positionSize),
176           arrayClassId = array.readId(),
177           recordSize = array.readTruncatedLong(bytesForObjectArraySize)
178         )
179         id to objectArray
180       }
181   }
182 
183   fun indexedPrimitiveArraySequence(): Sequence<LongObjectPair<IndexedPrimitiveArray>> {
184     return primitiveArrayIndex.entrySequence()
185       .map {
186         val id = it.first
187         val array = it.second
188 
189         val primitiveArray = IndexedPrimitiveArray(
190           position = array.readTruncatedLong(positionSize),
191           primitiveType = PrimitiveType.values()[array.readByte()
192             .toInt()],
193           recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
194         )
195         id to primitiveArray
196       }
197   }
198 
199   fun indexedObjectSequence(): Sequence<LongObjectPair<IndexedObject>> {
200     return indexedClassSequence() +
201       indexedInstanceSequence() +
202       indexedObjectArraySequence() +
203       indexedPrimitiveArraySequence()
204   }
205 
206   fun gcRoots(): List<GcRoot> {
207     return gcRoots
208   }
209 
210   fun objectAtIndex(index: Int): LongObjectPair<IndexedObject> {
211     require(index > 0)
212     if (index < classIndex.size) {
213       val objectId = classIndex.keyAt(index)
214       val array = classIndex.getAtIndex(index)
215       return objectId to array.readClass()
216     }
217     var shiftedIndex = index - classIndex.size
218     if (shiftedIndex < instanceIndex.size) {
219       val objectId = instanceIndex.keyAt(shiftedIndex)
220       val array = instanceIndex.getAtIndex(shiftedIndex)
221       return objectId to IndexedInstance(
222         position = array.readTruncatedLong(positionSize),
223         classId = array.readId(),
224         recordSize = array.readTruncatedLong(bytesForInstanceSize)
225       )
226     }
227     shiftedIndex -= instanceIndex.size
228     if (shiftedIndex < objectArrayIndex.size) {
229       val objectId = objectArrayIndex.keyAt(shiftedIndex)
230       val array = objectArrayIndex.getAtIndex(shiftedIndex)
231       return objectId to IndexedObjectArray(
232         position = array.readTruncatedLong(positionSize),
233         arrayClassId = array.readId(),
234         recordSize = array.readTruncatedLong(bytesForObjectArraySize)
235       )
236     }
237     shiftedIndex -= objectArrayIndex.size
238     require(index < primitiveArrayIndex.size)
239     val objectId = primitiveArrayIndex.keyAt(shiftedIndex)
240     val array = primitiveArrayIndex.getAtIndex(shiftedIndex)
241     return objectId to IndexedPrimitiveArray(
242       position = array.readTruncatedLong(positionSize),
243       primitiveType = PrimitiveType.values()[array.readByte()
244         .toInt()],
245       recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
246     )
247   }
248 
249   @Suppress("ReturnCount")
250   fun indexedObjectOrNull(objectId: Long): IntObjectPair<IndexedObject>? {
251     var index = classIndex.indexOf(objectId)
252     if (index >= 0) {
253       val array = classIndex.getAtIndex(index)
254       return index to array.readClass()
255     }
256     index = instanceIndex.indexOf(objectId)
257     if (index >= 0) {
258       val array = instanceIndex.getAtIndex(index)
259       return classIndex.size + index to IndexedInstance(
260         position = array.readTruncatedLong(positionSize),
261         classId = array.readId(),
262         recordSize = array.readTruncatedLong(bytesForInstanceSize)
263       )
264     }
265     index = objectArrayIndex.indexOf(objectId)
266     if (index >= 0) {
267       val array = objectArrayIndex.getAtIndex(index)
268       return classIndex.size + instanceIndex.size + index to IndexedObjectArray(
269         position = array.readTruncatedLong(positionSize),
270         arrayClassId = array.readId(),
271         recordSize = array.readTruncatedLong(bytesForObjectArraySize)
272       )
273     }
274     index = primitiveArrayIndex.indexOf(objectId)
275     if (index >= 0) {
276       val array = primitiveArrayIndex.getAtIndex(index)
277       return classIndex.size + instanceIndex.size + index + primitiveArrayIndex.size to IndexedPrimitiveArray(
278         position = array.readTruncatedLong(positionSize),
279         primitiveType = PrimitiveType.values()[array.readByte()
280           .toInt()],
281         recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
282       )
283     }
284     return null
285   }
286 
287   private fun ByteSubArray.readClass(): IndexedClass {
288     val position = readTruncatedLong(positionSize)
289     val superclassId = readId()
290     val instanceSize = readInt()
291 
292     val recordSize = readTruncatedLong(bytesForClassSize)
293     val fieldsIndex = readTruncatedLong(classFieldsIndexSize).toInt()
294 
295     return IndexedClass(
296       position = position,
297       superclassId = superclassId,
298       instanceSize = instanceSize,
299       recordSize = recordSize,
300       fieldsIndex = fieldsIndex
301     )
302   }
303 
304   @Suppress("ReturnCount")
305   fun objectIdIsIndexed(objectId: Long): Boolean {
306     if (classIndex[objectId] != null) {
307       return true
308     }
309     if (instanceIndex[objectId] != null) {
310       return true
311     }
312     if (objectArrayIndex[objectId] != null) {
313       return true
314     }
315     if (primitiveArrayIndex[objectId] != null) {
316       return true
317     }
318     return false
319   }
320 
321   private fun hprofStringById(id: Long): String {
322     return hprofStringCache[id] ?: throw IllegalArgumentException("Hprof string $id not in cache")
323   }
324 
325   private class Builder(
326     longIdentifiers: Boolean,
327     maxPosition: Long,
328     classCount: Int,
329     instanceCount: Int,
330     objectArrayCount: Int,
331     primitiveArrayCount: Int,
332     val bytesForClassSize: Int,
333     val bytesForInstanceSize: Int,
334     val bytesForObjectArraySize: Int,
335     val bytesForPrimitiveArraySize: Int,
336     val classFieldsTotalBytes: Int,
337     val stickyClassGcRootIds: LongScatterSet,
338   ) : OnHprofRecordTagListener {
339 
340     private val identifierSize = if (longIdentifiers) 8 else 4
341     private val positionSize = byteSizeForUnsigned(maxPosition)
342     private val classFieldsIndexSize = byteSizeForUnsigned(classFieldsTotalBytes.toLong())
343 
344     /**
345      * Map of string id to string
346      * This currently keeps all the hprof strings that we could care about: class names,
347      * static field names and instance fields names
348      */
349     // TODO Replacing with a radix trie reversed into a sparse array of long to trie leaf could save
350     // memory. Can be stored as 3 arrays: array of keys, array of values which are indexes into
351     // a large array of string bytes. Each "entry" consists of a size, the index of the previous
352     // segment and then the segment content.
353 
354     private val hprofStringCache = LongObjectScatterMap<String>()
355 
356     /**
357      * class id to string id
358      */
359     private val classNames = LongLongScatterMap(expectedElements = classCount)
360 
361     private val classFieldBytes = ByteArray(classFieldsTotalBytes)
362 
363     private var classFieldsIndex = 0
364 
365     private val classIndex = UnsortedByteEntries(
366       bytesPerValue = positionSize + identifierSize + 4 + bytesForClassSize + classFieldsIndexSize,
367       longIdentifiers = longIdentifiers,
368       initialCapacity = classCount
369     )
370     private val instanceIndex = UnsortedByteEntries(
371       bytesPerValue = positionSize + identifierSize + bytesForInstanceSize,
372       longIdentifiers = longIdentifiers,
373       initialCapacity = instanceCount
374     )
375     private val objectArrayIndex = UnsortedByteEntries(
376       bytesPerValue = positionSize + identifierSize + bytesForObjectArraySize,
377       longIdentifiers = longIdentifiers,
378       initialCapacity = objectArrayCount
379     )
380     private val primitiveArrayIndex = UnsortedByteEntries(
381       bytesPerValue = positionSize + 1 + bytesForPrimitiveArraySize,
382       longIdentifiers = longIdentifiers,
383       initialCapacity = primitiveArrayCount
384     )
385 
386     // Pre seeding gc roots with the sticky class gc roots we've already parsed.
387     private val gcRoots: MutableList<GcRoot> = ArrayList<GcRoot>(stickyClassGcRootIds.size()).apply {
388       stickyClassGcRootIds.elementSequence().forEach {classId ->
389         add(StickyClass(classId))
390       }
391     }
392 
393     private fun HprofRecordReader.copyToClassFields(byteCount: Int) {
394       for (i in 1..byteCount) {
395         classFieldBytes[classFieldsIndex++] = readByte()
396       }
397     }
398 
399     private fun lastClassFieldsShort() =
400       ((classFieldBytes[classFieldsIndex - 2].toInt() and 0xff shl 8) or
401         (classFieldBytes[classFieldsIndex - 1].toInt() and 0xff)).toShort()
402 
403     @Suppress("LongMethod")
404     override fun onHprofRecord(
405       tag: HprofRecordTag,
406       length: Long,
407       reader: HprofRecordReader
408     ) {
409       when (tag) {
410         STRING_IN_UTF8 -> {
411           hprofStringCache[reader.readId()] = reader.readUtf8(length - identifierSize)
412         }
413         LOAD_CLASS -> {
414           // classSerialNumber
415           reader.skip(INT.byteSize)
416           val id = reader.readId()
417           // stackTraceSerialNumber
418           reader.skip(INT.byteSize)
419           val classNameStringId = reader.readId()
420           classNames[id] = classNameStringId
421         }
422         ROOT_UNKNOWN -> {
423           reader.readUnknownGcRootRecord().apply {
424             if (id != ValueHolder.NULL_REFERENCE) {
425               gcRoots += this
426             }
427           }
428         }
429         ROOT_JNI_GLOBAL -> {
430           reader.readJniGlobalGcRootRecord().apply {
431             if (id != ValueHolder.NULL_REFERENCE) {
432               gcRoots += this
433             }
434           }
435         }
436         ROOT_JNI_LOCAL -> {
437           reader.readJniLocalGcRootRecord().apply {
438             if (id != ValueHolder.NULL_REFERENCE) {
439               gcRoots += this
440             }
441           }
442         }
443         ROOT_JAVA_FRAME -> {
444           reader.readJavaFrameGcRootRecord().apply {
445             if (id != ValueHolder.NULL_REFERENCE) {
446               gcRoots += this
447             }
448           }
449         }
450         ROOT_NATIVE_STACK -> {
451           reader.readNativeStackGcRootRecord().apply {
452             if (id != ValueHolder.NULL_REFERENCE) {
453               gcRoots += this
454             }
455           }
456         }
457         ROOT_STICKY_CLASS -> {
458           // We already parse these gc roots in the initial scan.
459           reader.skipId()
460         }
461         ROOT_THREAD_BLOCK -> {
462           reader.readThreadBlockGcRootRecord().apply {
463             if (id != ValueHolder.NULL_REFERENCE) {
464               gcRoots += this
465             }
466           }
467         }
468         ROOT_MONITOR_USED -> {
469           reader.readMonitorUsedGcRootRecord().apply {
470             if (id != ValueHolder.NULL_REFERENCE) {
471               gcRoots += this
472             }
473           }
474         }
475         ROOT_THREAD_OBJECT -> {
476           reader.readThreadObjectGcRootRecord().apply {
477             if (id != ValueHolder.NULL_REFERENCE) {
478               gcRoots += this
479             }
480           }
481         }
482         ROOT_INTERNED_STRING -> {
483           reader.readInternedStringGcRootRecord().apply {
484             if (id != ValueHolder.NULL_REFERENCE) {
485               gcRoots += this
486             }
487           }
488         }
489         ROOT_FINALIZING -> {
490           reader.readFinalizingGcRootRecord().apply {
491             if (id != ValueHolder.NULL_REFERENCE) {
492               gcRoots += this
493             }
494           }
495         }
496         ROOT_DEBUGGER -> {
497           reader.readDebuggerGcRootRecord().apply {
498             if (id != ValueHolder.NULL_REFERENCE) {
499               gcRoots += this
500             }
501           }
502         }
503         ROOT_REFERENCE_CLEANUP -> {
504           reader.readReferenceCleanupGcRootRecord().apply {
505             if (id != ValueHolder.NULL_REFERENCE) {
506               gcRoots += this
507             }
508           }
509         }
510         ROOT_VM_INTERNAL -> {
511           reader.readVmInternalGcRootRecord().apply {
512             if (id != ValueHolder.NULL_REFERENCE) {
513               gcRoots += this
514             }
515           }
516         }
517         ROOT_JNI_MONITOR -> {
518           reader.readJniMonitorGcRootRecord().apply {
519             if (id != ValueHolder.NULL_REFERENCE) {
520               gcRoots += this
521             }
522           }
523         }
524         ROOT_UNREACHABLE -> {
525           reader.readUnreachableGcRootRecord().apply {
526             if (id != ValueHolder.NULL_REFERENCE) {
527               gcRoots += this
528             }
529           }
530         }
531         CLASS_DUMP -> {
532           val bytesReadStart = reader.bytesRead
533           val id = reader.readId()
534           // stack trace serial number
535           reader.skip(INT.byteSize)
536           val superclassId = reader.readId()
537           reader.skip(5 * identifierSize)
538 
539           // instance size (in bytes)
540           // Useful to compute retained size
541           val instanceSize = reader.readInt()
542 
543           reader.skipClassDumpConstantPool()
544 
545           val startPosition = classFieldsIndex
546 
547           val bytesReadFieldStart = reader.bytesRead
548 
549           reader.copyToClassFields(2)
550           val staticFieldCount = lastClassFieldsShort().toInt() and 0xFFFF
551           for (i in 0 until staticFieldCount) {
552             reader.copyToClassFields(identifierSize)
553             reader.copyToClassFields(1)
554             val type = classFieldBytes[classFieldsIndex - 1].toInt() and 0xff
555             if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
556               reader.copyToClassFields(identifierSize)
557             } else {
558               reader.copyToClassFields(PrimitiveType.byteSizeByHprofType.getValue(type))
559             }
560           }
561 
562           reader.copyToClassFields(2)
563           val fieldCount = lastClassFieldsShort().toInt() and 0xFFFF
564           for (i in 0 until fieldCount) {
565             reader.copyToClassFields(identifierSize)
566             reader.copyToClassFields(1)
567           }
568 
569           val fieldsSize = (reader.bytesRead - bytesReadFieldStart).toInt()
570           val recordSize = reader.bytesRead - bytesReadStart
571 
572           classIndex.append(id)
573             .apply {
574               writeTruncatedLong(bytesReadStart, positionSize)
575               writeId(superclassId)
576               writeInt(instanceSize)
577               writeTruncatedLong(recordSize, bytesForClassSize)
578               writeTruncatedLong(startPosition.toLong(), classFieldsIndexSize)
579             }
580           require(startPosition + fieldsSize == classFieldsIndex) {
581             "Expected $classFieldsIndex to have moved by $fieldsSize and be equal to ${startPosition + fieldsSize}"
582           }
583         }
584         INSTANCE_DUMP -> {
585           val bytesReadStart = reader.bytesRead
586           val id = reader.readId()
587           reader.skip(INT.byteSize)
588           val classId = reader.readId()
589           val remainingBytesInInstance = reader.readInt()
590           reader.skip(remainingBytesInInstance)
591           val recordSize = reader.bytesRead - bytesReadStart
592           instanceIndex.append(id)
593             .apply {
594               writeTruncatedLong(bytesReadStart, positionSize)
595               writeId(classId)
596               writeTruncatedLong(recordSize, bytesForInstanceSize)
597             }
598         }
599         OBJECT_ARRAY_DUMP -> {
600           val bytesReadStart = reader.bytesRead
601           val id = reader.readId()
602           // stack trace serial number
603           reader.skip(INT.byteSize)
604           val size = reader.readInt()
605           val arrayClassId = reader.readId()
606           reader.skip(identifierSize * size)
607           // record size - (ID+INT + INT + ID)
608           val recordSize = reader.bytesRead - bytesReadStart
609           objectArrayIndex.append(id)
610             .apply {
611               writeTruncatedLong(bytesReadStart, positionSize)
612               writeId(arrayClassId)
613               writeTruncatedLong(recordSize, bytesForObjectArraySize)
614             }
615         }
616         PRIMITIVE_ARRAY_DUMP -> {
617           val bytesReadStart = reader.bytesRead
618           val id = reader.readId()
619           reader.skip(INT.byteSize)
620           val size = reader.readInt()
621           val type = PrimitiveType.primitiveTypeByHprofType.getValue(reader.readUnsignedByte())
622           reader.skip(size * type.byteSize)
623           val recordSize = reader.bytesRead - bytesReadStart
624           primitiveArrayIndex.append(id)
625             .apply {
626               writeTruncatedLong(bytesReadStart, positionSize)
627               writeByte(type.ordinal.toByte())
628               writeTruncatedLong(recordSize, bytesForPrimitiveArraySize)
629             }
630         }
631       }
632     }
633 
634     fun buildIndex(
635       proguardMapping: ProguardMapping?,
636       hprofHeader: HprofHeader
637     ): HprofInMemoryIndex {
638       require(classFieldsIndex == classFieldBytes.size) {
639         "Read $classFieldsIndex into fields bytes instead of expected ${classFieldBytes.size}"
640       }
641 
642       val sortedInstanceIndex = instanceIndex.moveToSortedMap()
643       val sortedObjectArrayIndex = objectArrayIndex.moveToSortedMap()
644       val sortedPrimitiveArrayIndex = primitiveArrayIndex.moveToSortedMap()
645       val sortedClassIndex = classIndex.moveToSortedMap()
646       // Passing references to avoid copying the underlying data structures.
647       return HprofInMemoryIndex(
648         positionSize = positionSize,
649         hprofStringCache = hprofStringCache,
650         classNames = classNames,
651         classIndex = sortedClassIndex,
652         instanceIndex = sortedInstanceIndex,
653         objectArrayIndex = sortedObjectArrayIndex,
654         primitiveArrayIndex = sortedPrimitiveArrayIndex,
655         gcRoots = gcRoots,
656         proguardMapping = proguardMapping,
657         bytesForClassSize = bytesForClassSize,
658         bytesForInstanceSize = bytesForInstanceSize,
659         bytesForObjectArraySize = bytesForObjectArraySize,
660         bytesForPrimitiveArraySize = bytesForPrimitiveArraySize,
661         useForwardSlashClassPackageSeparator = hprofHeader.version != ANDROID,
662         classFieldsReader = ClassFieldsReader(identifierSize, classFieldBytes),
663         classFieldsIndexSize = classFieldsIndexSize,
664         stickyClassGcRootIds = stickyClassGcRootIds,
665       )
666     }
667   }
668 
669   companion object {
670 
671     private fun byteSizeForUnsigned(maxValue: Long): Int {
672       var value = maxValue
673       var byteCount = 0
674       while (value != 0L) {
675         value = value shr 8
676         byteCount++
677       }
678       return byteCount
679     }
680 
681     fun indexHprof(
682       reader: StreamingHprofReader,
683       hprofHeader: HprofHeader,
684       proguardMapping: ProguardMapping?,
685       indexedGcRootTags: Set<HprofRecordTag>
686     ): HprofInMemoryIndex {
687 
688       // First pass to count and correctly size arrays once and for all.
689       var maxClassSize = 0L
690       var maxInstanceSize = 0L
691       var maxObjectArraySize = 0L
692       var maxPrimitiveArraySize = 0L
693       var classCount = 0
694       var instanceCount = 0
695       var objectArrayCount = 0
696       var primitiveArrayCount = 0
697       var classFieldsTotalBytes = 0
698       val stickyClassGcRootIds = LongScatterSet()
699 
700       val bytesRead = reader.readRecords(
701         EnumSet.of(
702           CLASS_DUMP,
703           INSTANCE_DUMP,
704           OBJECT_ARRAY_DUMP,
705           PRIMITIVE_ARRAY_DUMP,
706           ROOT_STICKY_CLASS
707         )
708       ) { tag, _, reader ->
709         val bytesReadStart = reader.bytesRead
710         when (tag) {
711           CLASS_DUMP -> {
712             classCount++
713             reader.skipClassDumpHeader()
714             val bytesReadStaticFieldStart = reader.bytesRead
715             reader.skipClassDumpStaticFields()
716             reader.skipClassDumpFields()
717             maxClassSize = max(maxClassSize, reader.bytesRead - bytesReadStart)
718             classFieldsTotalBytes += (reader.bytesRead - bytesReadStaticFieldStart).toInt()
719           }
720           INSTANCE_DUMP -> {
721             instanceCount++
722             reader.skipInstanceDumpRecord()
723             maxInstanceSize = max(maxInstanceSize, reader.bytesRead - bytesReadStart)
724           }
725           OBJECT_ARRAY_DUMP -> {
726             objectArrayCount++
727             reader.skipObjectArrayDumpRecord()
728             maxObjectArraySize = max(maxObjectArraySize, reader.bytesRead - bytesReadStart)
729           }
730           PRIMITIVE_ARRAY_DUMP -> {
731             primitiveArrayCount++
732             reader.skipPrimitiveArrayDumpRecord()
733             maxPrimitiveArraySize = max(maxPrimitiveArraySize, reader.bytesRead - bytesReadStart)
734           }
735           ROOT_STICKY_CLASS -> {
736             // StickyClass has only 1 field: id. Our API 23 emulators in CI are creating heap
737             // dumps with duplicated sticky class roots, up to 30K times for some objects.
738             // There's no point in keeping all these in our list of roots, 1 per each is enough
739             // so we deduplicate with stickyClassGcRootIds.
740             val id = reader.readStickyClassGcRootRecord().id
741             if (id != ValueHolder.NULL_REFERENCE) {
742               stickyClassGcRootIds += id
743             }
744           }
745         }
746       }
747 
748       val bytesForClassSize = byteSizeForUnsigned(maxClassSize)
749       val bytesForInstanceSize = byteSizeForUnsigned(maxInstanceSize)
750       val bytesForObjectArraySize = byteSizeForUnsigned(maxObjectArraySize)
751       val bytesForPrimitiveArraySize = byteSizeForUnsigned(maxPrimitiveArraySize)
752 
753       val indexBuilderListener = Builder(
754         longIdentifiers = hprofHeader.identifierByteSize == 8,
755         maxPosition = bytesRead,
756         classCount = classCount,
757         instanceCount = instanceCount,
758         objectArrayCount = objectArrayCount,
759         primitiveArrayCount = primitiveArrayCount,
760         bytesForClassSize = bytesForClassSize,
761         bytesForInstanceSize = bytesForInstanceSize,
762         bytesForObjectArraySize = bytesForObjectArraySize,
763         bytesForPrimitiveArraySize = bytesForPrimitiveArraySize,
764         classFieldsTotalBytes = classFieldsTotalBytes,
765         stickyClassGcRootIds
766       )
767 
768       val recordTypes = EnumSet.of(
769         STRING_IN_UTF8,
770         LOAD_CLASS,
771         CLASS_DUMP,
772         INSTANCE_DUMP,
773         OBJECT_ARRAY_DUMP,
774         PRIMITIVE_ARRAY_DUMP
775       ) + HprofRecordTag.rootTags.intersect(indexedGcRootTags)
776 
777       reader.readRecords(recordTypes, indexBuilderListener)
778       return indexBuilderListener.buildIndex(proguardMapping, hprofHeader)
779     }
780   }
781 }
782