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

<lambda>null1 package shark
2 
3 import okio.BufferedSink
4 import okio.Okio
5 import shark.HprofRecord.HeapDumpEndRecord
6 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
7 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
9 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
10 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
11 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
12 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
13 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
14 import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
15 import java.io.File
16 
17 /**
18  * Converts a Hprof file to another file with all primitive arrays replaced with arrays of zeroes,
19  * which can be useful to remove PII. Char arrays are handled slightly differently because 0 would
20  * be the null character so instead these become arrays of '?'.
21  */
22 class HprofPrimitiveArrayStripper {
23 
24   /**
25    * @see HprofPrimitiveArrayStripper
26    */
27   fun stripPrimitiveArrays(
28     inputHprofFile: File,
29     /**
30      * Optional output file. Defaults to a file in the same directory as [inputHprofFile], with
31      * the same name and "-stripped" prepended before the ".hprof" extension. If the file extension
32      * is not ".hprof", then "-stripped" is added at the end of the file.
33      */
34     outputHprofFile: File = File(
35       inputHprofFile.parent, inputHprofFile.name.replace(
36       ".hprof", "-stripped.hprof"
37     ).let { if (it != inputHprofFile.name) it else inputHprofFile.name + "-stripped" })
38   ): File {
39     stripPrimitiveArrays(
40       hprofSourceProvider = FileSourceProvider(inputHprofFile),
41       hprofSink = Okio.buffer(Okio.sink(outputHprofFile.outputStream()))
42     )
43     return outputHprofFile
44   }
45 
46   /**
47    * @see HprofPrimitiveArrayStripper
48    */
49   fun stripPrimitiveArrays(
50     hprofSourceProvider: StreamingSourceProvider,
51     hprofSink: BufferedSink
52   ) {
53     val header = hprofSourceProvider.openStreamingSource().use { HprofHeader.parseHeaderOf(it) }
54     val reader =
55       StreamingHprofReader.readerFor(hprofSourceProvider, header).asStreamingRecordReader()
56     HprofWriter.openWriterFor(
57       hprofSink,
58       hprofHeader = header
59     )
60       .use { writer ->
61         reader.readRecords(setOf(HprofRecord::class),
62           OnHprofRecordListener { _,
63             record ->
64             // HprofWriter automatically emits HeapDumpEndRecord, because it flushes
65             // all continuous heap dump sub records as one heap dump record.
66             if (record is HeapDumpEndRecord) {
67               return@OnHprofRecordListener
68             }
69             writer.write(
70               when (record) {
71                 is BooleanArrayDump -> BooleanArrayDump(
72                   record.id, record.stackTraceSerialNumber,
73                   BooleanArray(record.array.size)
74                 )
75                 is CharArrayDump -> CharArrayDump(
76                   record.id, record.stackTraceSerialNumber,
77                   CharArray(record.array.size) {
78                     '?'
79                   }
80                 )
81                 is FloatArrayDump -> FloatArrayDump(
82                   record.id, record.stackTraceSerialNumber,
83                   FloatArray(record.array.size)
84                 )
85                 is DoubleArrayDump -> DoubleArrayDump(
86                   record.id, record.stackTraceSerialNumber,
87                   DoubleArray(record.array.size)
88                 )
89                 is ByteArrayDump -> ByteArrayDump(
90                   record.id, record.stackTraceSerialNumber,
91                   ByteArray(record.array.size) {
92                     // Converts to '?' in UTF-8 for byte backed strings
93                     63
94                   }
95                 )
96                 is ShortArrayDump -> ShortArrayDump(
97                   record.id, record.stackTraceSerialNumber,
98                   ShortArray(record.array.size)
99                 )
100                 is IntArrayDump -> IntArrayDump(
101                   record.id, record.stackTraceSerialNumber,
102                   IntArray(record.array.size)
103                 )
104                 is LongArrayDump -> LongArrayDump(
105                   record.id, record.stackTraceSerialNumber,
106                   LongArray(record.array.size)
107                 )
108                 else -> {
109                   record
110                 }
111               }
112             )
113           })
114       }
115   }
116 }
117