xref: /aosp_15_r20/external/leakcanary2/shark-hprof-test/src/main/kotlin/shark/HprofWriterHelper.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package shark
2 
3 import okio.Buffer
4 import shark.GcRoot.StickyClass
5 import shark.HprofRecord.HeapDumpRecord.GcRootRecord
6 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
7 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
9 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
10 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
11 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
12 import shark.HprofRecord.LoadClassRecord
13 import shark.HprofRecord.StringRecord
14 import shark.PrimitiveType.BOOLEAN
15 import shark.PrimitiveType.BYTE
16 import shark.PrimitiveType.CHAR
17 import shark.PrimitiveType.DOUBLE
18 import shark.PrimitiveType.FLOAT
19 import shark.PrimitiveType.INT
20 import shark.PrimitiveType.LONG
21 import shark.PrimitiveType.SHORT
22 import shark.ValueHolder.BooleanHolder
23 import shark.ValueHolder.ByteHolder
24 import shark.ValueHolder.CharHolder
25 import shark.ValueHolder.DoubleHolder
26 import shark.ValueHolder.FloatHolder
27 import shark.ValueHolder.IntHolder
28 import shark.ValueHolder.LongHolder
29 import shark.ValueHolder.ReferenceHolder
30 import shark.ValueHolder.ShortHolder
31 import java.io.Closeable
32 import java.io.File
33 import java.util.UUID
34 import kotlin.random.Random
35 import kotlin.reflect.KClass
36 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
37 
38 class HprofWriterHelper constructor(
39   private val writer: HprofWriter
40 ) : Closeable {
41 
42   private var lastId = 0L
43   private val id: Long
44     get() = ++lastId
45 
46   private val weakRefKeyRandom = Random(42)
47 
48   // Sequence identical for every test run
49   private val weakRefKey: String
50     get() =
51       UUID(weakRefKeyRandom.nextLong(), weakRefKeyRandom.nextLong()).toString()
52 
53   private val typeSizes =
54     PrimitiveType.byteSizeByHprofType + (PrimitiveType.REFERENCE_HPROF_TYPE to writer.hprofHeader.identifierByteSize)
55 
56   private val classDumps = mutableMapOf<Long, ClassDumpRecord>()
57 
58   private val objectClassId = clazz(superclassId = 0, className = "java.lang.Object")
59   private val objectArrayClassId = arrayClass("java.lang.Object")
60   private val stringClassId = clazz(
61     className = "java.lang.String", fields = listOf(
62     "value" to ReferenceHolder::class,
63     "count" to IntHolder::class
64   )
65   )
66 
67   private val referenceClassId = clazz(
68     className = "java.lang.ref.Reference",
69     fields = listOf(
70       "referent" to ReferenceHolder::class
71     )
72   )
73 
74   private val weakReferenceClassId = clazz(
75     className = "java.lang.ref.WeakReference",
76     superclassId = referenceClassId
77   )
78   private val keyedWeakReferenceClassId = clazz(
79     superclassId = weakReferenceClassId,
80     className = "leakcanary.KeyedWeakReference",
81     staticFields = listOf("heapDumpUptimeMillis" to LongHolder(30000)),
82     fields = listOf(
83       "key" to ReferenceHolder::class,
84       "name" to ReferenceHolder::class,
85       "watchUptimeMillis" to LongHolder::class,
86       "retainedUptimeMillis" to LongHolder::class
87     )
88   )
89 
90   fun clazz(
91     className: String,
92     superclassId: Long = -1L, // -1 defaults to java.lang.Object
93     staticFields: List<Pair<String, ValueHolder>> = emptyList(),
94     fields: List<Pair<String, KClass<out ValueHolder>>> = emptyList()
95   ): Long {
96     val classNameRecord = StringRecord(id, className)
97     writer.write(classNameRecord)
98     val loadClass = LoadClassRecord(1, id, 1, classNameRecord.id)
99     writer.write(loadClass)
100 
101     val staticFieldRecords = staticFields.map {
102       val fieldName = StringRecord(id, it.first)
103       writer.write(fieldName)
104       StaticFieldRecord(fieldName.id, typeOf(it.second), it.second)
105     }
106 
107     val fieldRecords = fields.map {
108       val fieldName = StringRecord(id, it.first)
109       writer.write(fieldName)
110       FieldRecord(fieldName.id, typeOf(it.second))
111     }
112 
113     var instanceSize = fieldRecords.sumBy {
114       typeSizes.getValue(it.type)
115     }
116 
117     var nextUpId = if (superclassId == -1L) objectClassId else superclassId
118     while (nextUpId != 0L) {
119       val nextUp = classDumps[nextUpId]!!
120       instanceSize += nextUp.fields.sumBy {
121         typeSizes.getValue(it.type)
122       }
123       nextUpId = nextUp.superclassId
124     }
125     val classDump = ClassDumpRecord(
126       id = loadClass.id,
127       stackTraceSerialNumber = 1,
128       superclassId = if (superclassId == -1L) objectClassId else superclassId,
129       classLoaderId = 0,
130       signersId = 0,
131       protectionDomainId = 0,
132       instanceSize = instanceSize,
133       staticFields = staticFieldRecords,
134       fields = fieldRecords
135     )
136     classDumps[loadClass.id] = classDump
137     writer.write(classDump)
138     val gcRoot = StickyClass(classDump.id)
139     gcRoot(gcRoot)
140     return classDump.id
141   }
142 
143   fun stringRecord(
144     name: String
145   ): StringRecord {
146     val stringRecord = StringRecord(id, name)
147     writer.write(stringRecord)
148     return stringRecord
149   }
150 
151   fun clazz(
152     classNameRecord: StringRecord,
153     superclassId: Long = -1L, // -1 defaults to java.lang.Object
154     staticFields: List<Pair<Long, ValueHolder>> = emptyList(),
155     fields: List<Pair<Long, KClass<out ValueHolder>>> = emptyList()
156   ): Long {
157     val loadClass = LoadClassRecord(1, id, 1, classNameRecord.id)
158     writer.write(loadClass)
159 
160     val staticFieldRecords = staticFields.map {
161       StaticFieldRecord(it.first, typeOf(it.second), it.second)
162     }
163 
164     val fieldRecords = fields.map {
165       FieldRecord(it.first, typeOf(it.second))
166     }
167 
168     var instanceSize = fieldRecords.sumBy {
169       typeSizes.getValue(it.type)
170     }
171 
172     var nextUpId = if (superclassId == -1L) objectClassId else superclassId
173     while (nextUpId != 0L) {
174       val nextUp = classDumps[nextUpId]!!
175       instanceSize += nextUp.fields.sumBy {
176         typeSizes.getValue(it.type)
177       }
178       nextUpId = nextUp.superclassId
179     }
180     val classDump = ClassDumpRecord(
181       id = loadClass.id,
182       stackTraceSerialNumber = 1,
183       superclassId = if (superclassId == -1L) objectClassId else superclassId,
184       classLoaderId = 0,
185       signersId = 0,
186       protectionDomainId = 0,
187       instanceSize = instanceSize,
188       staticFields = staticFieldRecords,
189       fields = fieldRecords
190     )
191     classDumps[loadClass.id] = classDump
192     writer.write(classDump)
193     val gcRoot = StickyClass(classDump.id)
194     gcRoot(gcRoot)
195     return classDump.id
196   }
197 
198   fun gcRoot(gcRoot: GcRoot) {
199     val gcRootRecord = GcRootRecord(gcRoot = gcRoot)
200     writer.write(gcRootRecord)
201   }
202 
203   fun arrayClass(className: String): Long {
204     return clazz(className = "$className[]")
205   }
206 
207   fun string(
208     string: String
209   ): ReferenceHolder {
210     return instance(
211       stringClassId,
212       fields = listOf(string.charArrayDump, IntHolder(string.length))
213     )
214   }
215 
216   fun keyedWeakReference(
217     referentInstanceId: ReferenceHolder
218   ): ReferenceHolder {
219     val referenceKey = string(weakRefKey)
220     return instance(
221       classId = keyedWeakReferenceClassId,
222       fields = listOf(
223         referenceKey,
224         string("its lifecycle has ended"),
225         LongHolder(5000),
226         LongHolder(20000),
227         ReferenceHolder(referentInstanceId.value)
228       )
229     )
230   }
231 
232   fun instance(
233     classId: Long,
234     fields: List<ValueHolder> = emptyList()
235   ): ReferenceHolder {
236     val instanceDump = InstanceDumpRecord(
237       id = id,
238       stackTraceSerialNumber = 1,
239       classId = classId,
240       fieldValues = writer.valuesToBytes(fields)
241     )
242     writer.write(instanceDump)
243     return ReferenceHolder(instanceDump.id)
244   }
245 
246   inner class InstanceAndClassDefinition {
247     val field = LinkedHashMap<String, ValueHolder>()
248     val staticField = LinkedHashMap<String, ValueHolder>()
249   }
250 
251   inner class ClassDefinition {
252     val staticField = LinkedHashMap<String, ValueHolder>()
253   }
254 
255   infix fun String.watchedInstance(block: InstanceAndClassDefinition.() -> Unit): ReferenceHolder {
256     val instance = this.instance(block)
257     keyedWeakReference(instance)
258     return instance
259   }
260 
261   infix fun String.instance(block: InstanceAndClassDefinition.() -> Unit): ReferenceHolder {
262     val definition = InstanceAndClassDefinition()
263     block(definition)
264 
265     val classFields = definition.field.map {
266       it.key to it.value::class
267     }
268 
269     val staticFields = definition.staticField.map { it.key to it.value }
270 
271     val instanceFields = definition.field.map { it.value }
272 
273     return instance(clazz(this, fields = classFields, staticFields = staticFields), instanceFields)
274   }
275 
276   infix fun String.clazz(block: ClassDefinition.() -> Unit): Long {
277     val definition = ClassDefinition()
278     block(definition)
279 
280     val staticFields = definition.staticField.map { it.key to it.value }
281     return clazz(this, staticFields = staticFields)
282   }
283 
284   val String.charArrayDump: ReferenceHolder
285     get() {
286       val arrayDump = CharArrayDump(id, 1, toCharArray())
287       writer.write(arrayDump)
288       return ReferenceHolder(arrayDump.id)
289     }
290 
291   fun objectArray(
292     vararg elements: ReferenceHolder
293   ): ReferenceHolder {
294     return objectArrayOf(objectArrayClassId, *elements)
295   }
296 
297   fun objectArrayOf(
298     classId: Long,
299     vararg elements: ReferenceHolder
300   ): ReferenceHolder {
301     return ReferenceHolder(objectArray(classId, elements.map { it.value }.toLongArray()))
302   }
303 
304   fun objectArray(
305     classId: Long,
306     array: LongArray
307   ): Long {
308     val arrayDump = ObjectArrayDumpRecord(id, 1, classId, array)
309     writer.write(arrayDump)
310     return arrayDump.id
311   }
312 
313   fun primitiveLongArray(array: LongArray): Long {
314     val arrayDump = LongArrayDump(id, 1, array)
315     writer.write(arrayDump)
316     return arrayDump.id
317   }
318 
319   private fun typeOf(wrapper: ValueHolder): Int {
320     return when (wrapper) {
321       is ReferenceHolder -> PrimitiveType.REFERENCE_HPROF_TYPE
322       is BooleanHolder -> BOOLEAN.hprofType
323       is CharHolder -> CHAR.hprofType
324       is FloatHolder -> FLOAT.hprofType
325       is DoubleHolder -> DOUBLE.hprofType
326       is ByteHolder -> BYTE.hprofType
327       is ShortHolder -> SHORT.hprofType
328       is IntHolder -> INT.hprofType
329       is LongHolder -> LONG.hprofType
330     }
331   }
332 
333   private fun typeOf(wrapperClass: KClass<out ValueHolder>): Int {
334     return when (wrapperClass) {
335       ReferenceHolder::class -> PrimitiveType.REFERENCE_HPROF_TYPE
336       BooleanHolder::class -> BOOLEAN.hprofType
337       CharHolder::class -> CHAR.hprofType
338       FloatHolder::class -> FLOAT.hprofType
339       DoubleHolder::class -> DOUBLE.hprofType
340       ByteHolder::class -> BYTE.hprofType
341       ShortHolder::class -> SHORT.hprofType
342       IntHolder::class -> INT.hprofType
343       LongHolder::class -> LONG.hprofType
344       else -> throw IllegalArgumentException("Unexpected class $wrapperClass")
345     }
346   }
347 
348   override fun close() {
349     writer.close()
350   }
351 }
352 
dumpnull353 fun File.dump(block: HprofWriterHelper.() -> Unit) {
354   HprofWriterHelper(HprofWriter.openWriterFor(this))
355     .use(block)
356 }
357 
dumpnull358 fun dump(
359   hprofHeader: HprofHeader = HprofHeader(),
360   block: HprofWriterHelper.() -> Unit
361 ): DualSourceProvider {
362   val buffer = Buffer()
363   HprofWriterHelper(HprofWriter.openWriterFor(buffer, hprofHeader))
364     .use(block)
365   return ByteArraySourceProvider(buffer.readByteArray())
366 }
367 
dumpToBytesnull368 fun dumpToBytes(
369   hprofHeader: HprofHeader = HprofHeader(),
370   block: HprofWriterHelper.() -> Unit
371 ): ByteArray {
372   val buffer = Buffer()
373   HprofWriterHelper(HprofWriter.openWriterFor(buffer, hprofHeader))
374     .use(block)
375   return buffer.readByteArray()
376 }
377 
Listnull378 fun List<HprofRecord>.asHprofBytes(): DualSourceProvider {
379   val buffer = Buffer()
380   HprofWriter.openWriterFor(buffer)
381     .use { writer ->
382       forEach { record ->
383         writer.write(record)
384       }
385     }
386   return ByteArraySourceProvider(buffer.readByteArray())
387 }
388