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

<lambda>null1 package shark
2 
3 import shark.internal.createSHA1Hash
4 import java.io.File
5 import java.io.Serializable
6 
7 /**
8  * The result of an analysis performed by [HeapAnalyzer], either a [HeapAnalysisSuccess] or a
9  * [HeapAnalysisFailure]. This class is serializable however there are no guarantees of forward
10  * compatibility.
11  */
12 sealed class HeapAnalysis : Serializable {
13   /**
14    * The hprof file that was analyzed.
15    */
16   abstract val heapDumpFile: File
17 
18   /**
19    * The [System.currentTimeMillis] when this [HeapAnalysis] instance was created.
20    */
21   abstract val createdAtTimeMillis: Long
22 
23   /**
24    * Total time spent dumping the heap.
25    */
26   abstract val dumpDurationMillis: Long
27 
28   /**
29    * Total time spent analyzing the heap.
30    */
31   abstract val analysisDurationMillis: Long
32 
33   companion object {
34     private const val serialVersionUID: Long = -8657286725869987172
35     const val DUMP_DURATION_UNKNOWN: Long = -1
36   }
37 }
38 
39 /**
40  * The analysis performed by [HeapAnalyzer] did not complete successfully.
41  */
42 data class HeapAnalysisFailure(
43   override val heapDumpFile: File,
44   override val createdAtTimeMillis: Long,
45   override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
46   override val analysisDurationMillis: Long,
47   /**
48    * An exception wrapping the actual exception that was thrown.
49    */
50   val exception: HeapAnalysisException
51 ) : HeapAnalysis() {
52 
toStringnull53   override fun toString(): String {
54     return """====================================
55 HEAP ANALYSIS FAILED
56 
57 You can report this failure at https://github.com/square/leakcanary/issues
58 Please provide the stacktrace, metadata and the heap dump file.
59 ====================================
60 STACKTRACE
61 
62 $exception====================================
63 METADATA
64 
65 Build.VERSION.SDK_INT: ${androidSdkInt()}
66 Build.MANUFACTURER: ${androidManufacturer()}
67 LeakCanary version: ${leakCanaryVersion()}
68 Analysis duration: $analysisDurationMillis ms
69 Heap dump file path: ${heapDumpFile.absolutePath}
70 Heap dump timestamp: $createdAtTimeMillis
71 ===================================="""
72   }
73 
74   companion object {
75     private const val serialVersionUID: Long = 8483254400637792414
76   }
77 }
78 
79 /**
80  * The result of a successful heap analysis performed by [HeapAnalyzer].
81  */
82 data class HeapAnalysisSuccess(
83   override val heapDumpFile: File,
84   override val createdAtTimeMillis: Long,
85   override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
86   override val analysisDurationMillis: Long,
87   val metadata: Map<String, String>,
88   /**
89    * The list of [ApplicationLeak] found in the heap dump by [HeapAnalyzer].
90    */
91   val applicationLeaks: List<ApplicationLeak>,
92   /**
93    * The list of [LibraryLeak] found in the heap dump by [HeapAnalyzer].
94    */
95   val libraryLeaks: List<LibraryLeak>,
96   val unreachableObjects: List<LeakTraceObject>
97 ) : HeapAnalysis() {
98   /**
99    * The list of [Leak] found in the heap dump by [HeapAnalyzer], ie all [applicationLeaks] and
100    * all [libraryLeaks] in one list.
101    */
102   val allLeaks: Sequence<Leak>
103     get() = applicationLeaks.asSequence() + libraryLeaks.asSequence()
104 
toStringnull105   override fun toString(): String {
106     return """====================================
107 HEAP ANALYSIS RESULT
108 ====================================
109 ${applicationLeaks.size} APPLICATION LEAKS
110 
111 References underlined with "~~~" are likely causes.
112 Learn more at https://squ.re/leaks.
113 ${
114       if (applicationLeaks.isNotEmpty()) "\n" + applicationLeaks.joinToString(
115         "\n\n"
116       ) + "\n" else ""
117     }====================================
118 ${libraryLeaks.size} LIBRARY LEAKS
119 
120 A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
121 See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
122 ${
123       if (libraryLeaks.isNotEmpty()) "\n" + libraryLeaks.joinToString(
124         "\n\n"
125       ) + "\n" else ""
126     }====================================
127 ${unreachableObjects.size} UNREACHABLE OBJECTS
128 
129 An unreachable object is still in memory but LeakCanary could not find a strong reference path
130 from GC roots.
131 ${
132       if (unreachableObjects.isNotEmpty()) "\n" + unreachableObjects.joinToString(
133         "\n\n"
134       ) + "\n" else ""
135     }====================================
136 METADATA
137 
138 Please include this in bug reports and Stack Overflow questions.
139 ${
140       if (metadata.isNotEmpty()) "\n" + metadata.map { "${it.key}: ${it.value}" }.joinToString(
141         "\n"
142       ) else ""
143     }
144 Analysis duration: $analysisDurationMillis ms
145 Heap dump file path: ${heapDumpFile.absolutePath}
146 Heap dump timestamp: $createdAtTimeMillis
147 Heap dump duration: ${if (dumpDurationMillis != DUMP_DURATION_UNKNOWN) "$dumpDurationMillis ms" else "Unknown"}
148 ===================================="""
149   }
150 
151   companion object {
152     private const val serialVersionUID: Long = 130453013437459642
153   }
154 }
155 
156 /**
157  * A leak found by [HeapAnalyzer], either an [ApplicationLeak] or a [LibraryLeak].
158  */
159 sealed class Leak : Serializable {
160 
161   /**
162    * Group of leak traces which share the same leak signature.
163    */
164   abstract val leakTraces: List<LeakTrace>
165 
166   /**
167    * Sum of [LeakTrace.retainedHeapByteSize] for all elements in [leakTraces].
168    * Null if the retained heap size was not computed.
169    */
170   val totalRetainedHeapByteSize: Int?
171     get() = if (leakTraces.first().retainedHeapByteSize == null) {
172       null
173     } else {
<lambda>null174       leakTraces.sumBy { it.retainedHeapByteSize!! }
175     }
176 
177   /**
178    * Sum of [LeakTrace.retainedObjectCount] for all elements in [leakTraces].
179    * Null if the retained heap size was not computed.
180    */
181   val totalRetainedObjectCount: Int?
182     get() = if (leakTraces.first().retainedObjectCount == null) {
183       null
184     } else {
<lambda>null185       leakTraces.sumBy { it.retainedObjectCount!! }
186     }
187 
188   /**
189    * A unique SHA1 hash that represents this group of leak traces.
190    *
191    * For [ApplicationLeak] this is based on [LeakTrace.signature] and for [LibraryLeak] this is
192    * based on [LibraryLeak.pattern].
193    */
194   abstract val signature: String
195 
196   abstract val shortDescription: String
197 
toStringnull198   override fun toString(): String {
199     return (if (totalRetainedHeapByteSize != null) "$totalRetainedHeapByteSize bytes retained by leaking objects\n" else "") +
200       (if (leakTraces.size > 1) "Displaying only 1 leak trace out of ${leakTraces.size} with the same signature\n" else "") +
201       "Signature: $signature\n" +
202       leakTraces.first()
203   }
204 
205   companion object {
206     private const val serialVersionUID: Long = -2287572510360910916
207   }
208 }
209 
210 /**
211  * A leak found by [HeapAnalyzer], where the only path to the leaking object required going
212  * through a reference matched by [pattern], as provided to a [LibraryLeakReferenceMatcher]
213  * instance. This is a known leak in library code that is beyond your control.
214  */
215 data class LibraryLeak(
216   override val leakTraces: List<LeakTrace>,
217   /**
218    * The pattern that matched one of the references in each of [leakTraces], as provided to a
219    * [LibraryLeakReferenceMatcher] instance.
220    */
221   val pattern: ReferencePattern,
222   /**
223    * A description that conveys what we know about this library leak.
224    */
225   val description: String
226 ) : Leak() {
227   override val signature: String
228     get() = pattern.toString().createSHA1Hash()
229 
230   override val shortDescription: String
231     get() = pattern.toString()
232 
toStringnull233   override fun toString(): String {
234     return """Leak pattern: $pattern
235 Description: $description
236 ${super.toString()}
237 """
238   }
239 
240   companion object {
241     private const val serialVersionUID: Long = 3943636164568681903
242   }
243 }
244 
245 /**
246  * A leak found by [HeapAnalyzer] in your application.
247  */
248 data class ApplicationLeak(
249   override val leakTraces: List<LeakTrace>
250 ) : Leak() {
251   override val signature: String
252     get() = leakTraces.first().signature
253 
254   override val shortDescription: String
255     get() {
256       val leakTrace = leakTraces.first()
firstSuspectReferencePathnull257       return leakTrace.suspectReferenceSubpath.firstOrNull()?.let { firstSuspectReferencePath ->
258         val referenceName = firstSuspectReferencePath.referenceGenericName
259         firstSuspectReferencePath.originObject.classSimpleName + "." + referenceName
260       } ?: leakTrace.leakingObject.className
261     }
262 
263   // Override required to avoid the default toString() from data classes
toStringnull264   override fun toString(): String {
265     return super.toString()
266   }
267 
268   companion object {
269     private const val serialVersionUID: Long = 524928276700576863
270   }
271 }
272 
androidSdkIntnull273 private fun androidSdkInt(): Int {
274   return try {
275     val versionClass = Class.forName("android.os.Build\$VERSION")
276     val sdkIntField = versionClass.getDeclaredField("SDK_INT")
277     sdkIntField.get(null) as Int
278   } catch (e: Exception) {
279     -1
280   }
281 }
282 
androidManufacturernull283 private fun androidManufacturer(): String {
284   return try {
285     val buildClass = Class.forName("android.os.Build")
286     val manufacturerField = buildClass.getDeclaredField("MANUFACTURER")
287     manufacturerField.get(null) as String
288   } catch (e: Exception) {
289     "Unknown"
290   }
291 }
292 
leakCanaryVersionnull293 private fun leakCanaryVersion(): String {
294   return try {
295     val versionHolderClass = Class.forName("leakcanary.internal.InternalLeakCanary")
296     val versionField = versionHolderClass.getDeclaredField("version")
297     versionField.isAccessible = true
298     versionField.get(null) as String
299   } catch (e: Exception) {
300     "Unknown"
301   }
302 }