<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 }