<lambda>null1 package shark.internal
2
3 import shark.HeapGraph
4 import shark.HeapObject.HeapInstance
5 import shark.LibraryLeakReferenceMatcher
6 import shark.ReferencePattern.InstanceFieldPattern
7 import shark.ValueHolder
8 import shark.ValueHolder.ReferenceHolder
9 import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader
10 import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory
11 import shark.internal.Reference.LazyDetails
12 import shark.internal.ReferenceLocationType.ARRAY_ENTRY
13 import shark.internal.ReferenceLocationType.INSTANCE_FIELD
14
15 internal enum class AndroidReferenceReaders : OptionalFactory {
16
17 /**
18 * ActivityThread.mNewActivity is a linked list of ActivityClientRecord that keeps track of
19 * activities after they were resumed, until the main thread is idle. This is used to report
20 * analytics to system_server about how long it took for the main thread to settle after
21 * resuming an activity. Unfortunately, if the main thread never becomes idle, all these
22 * new activities leak in memory.
23 *
24 * We'd normally catch these with a pattern in AndroidReferenceMatchers, and we do have
25 * AndroidReferenceMatchers.ACTIVITY_THREAD__M_NEW_ACTIVITIES to do that, however this matching
26 * only works if none of the activities alive are waiting for idle. If any activity alive is
27 * still waiting for idle (which all the alive activities would be if they main thread is never
28 * idle) then ActivityThread.mActivities will reference an ActivityClientRecord through an
29 * ArrayMap and because ActivityClientRecord are reused that instance will also have its nextIdle
30 * fields set, so we're effectively traversing the ActivityThread.mNewActivity from a completely
31 * different and unexpected entry point.
32 *
33 * To fix that problem of broken pattern matching, we emit the mNewActivities field when
34 * finding an ActivityThread instance, and because custom ref readers have priority over the
35 * default instance field reader, we're guaranteed that mNewActivities is enqueued before
36 * mActivities. Unfortunately, that also means we can't rely on AndroidReferenceMatchers as
37 * those aren't used here, so we recreate our own LibraryLeakReferenceMatcher.
38 *
39 * We want to traverse mNewActivities before mActivities so we can't set isLowPriority to true
40 * like we would for normal path tagged as source of leak. So we will prioritize going through all
41 * activities in mNewActivities, some of which aren't destroyed yet (and therefore not leaking).
42 * Going through those paths of non leaking activities, we might find other leaks though.
43 * This would result in us tagging unrelated leaks as part of the mNewActivities leak. To prevent
44 * this, we traverse ActivityThread.mNewActivities as a linked list through
45 * ActivityClientRecord.nextIdle as a linked list, but we emit only ActivityClientRecord.activity
46 * fields if such activities are destroyed, which means any live activity in
47 * ActivityThread.mNewActivities will be discovered through the normal field navigation process
48 * and should go through ActivityThread.mActivities.
49 */
50 ACTIVITY_THREAD__NEW_ACTIVITIES {
51 override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
52 val activityThreadClass = graph.findClassByName("android.app.ActivityThread") ?: return null
53
54 if (activityThreadClass.readRecordFields().none {
55 activityThreadClass.instanceFieldName(it) == "mNewActivities"
56 }
57 ) {
58 return null
59 }
60
61 val activityClientRecordClass =
62 graph.findClassByName("android.app.ActivityThread\$ActivityClientRecord") ?: return null
63
64 val activityClientRecordFieldNames = activityClientRecordClass.readRecordFields()
65 .map { activityThreadClass.instanceFieldName(it) }
66 .toList()
67
68 if ("nextIdle" !in activityClientRecordFieldNames ||
69 "activity" !in activityClientRecordFieldNames) {
70 return null
71 }
72
73 val activityThreadClassId = activityThreadClass.objectId
74 val activityClientRecordClassId = activityClientRecordClass.objectId
75
76 return object : VirtualInstanceReferenceReader {
77 override fun matches(instance: HeapInstance) =
78 instance.instanceClassId == activityThreadClassId ||
79 instance.instanceClassId == activityClientRecordClassId
80
81 override fun read(source: HeapInstance): Sequence<Reference> {
82 return if (source.instanceClassId == activityThreadClassId) {
83 val mNewActivities =
84 source["android.app.ActivityThread", "mNewActivities"]!!.value.asObjectId!!
85 if (mNewActivities == ValueHolder.NULL_REFERENCE) {
86 emptySequence()
87 } else {
88 source.graph.context[ACTIVITY_THREAD__NEW_ACTIVITIES.name] = mNewActivities
89 sequenceOf(
90 Reference(
91 valueObjectId = mNewActivities,
92 isLowPriority = false,
93 lazyDetailsResolver = {
94 LazyDetails(
95 name = "mNewActivities",
96 locationClassObjectId = activityThreadClassId,
97 locationType = INSTANCE_FIELD,
98 isVirtual = false,
99 matchedLibraryLeak = LibraryLeakReferenceMatcher(
100 pattern = InstanceFieldPattern(
101 "android.app.ActivityThread", "mNewActivities"
102 ),
103 description = """
104 New activities are leaked by ActivityThread until the main thread becomes idle.
105 Tracked here: https://issuetracker.google.com/issues/258390457
106 """.trimIndent()
107 )
108 )
109 })
110 )
111 }
112 } else {
113 val mNewActivities =
114 source.graph.context.get<Long?>(ACTIVITY_THREAD__NEW_ACTIVITIES.name)
115 if (mNewActivities == null || source.objectId != mNewActivities) {
116 emptySequence()
117 } else {
118 generateSequence(source) { node ->
119 node["android.app.ActivityThread\$ActivityClientRecord", "nextIdle"]!!.valueAsInstance
120 }.withIndex().mapNotNull { (index, node) ->
121
122 val activity = node["android.app.ActivityThread\$ActivityClientRecord", "activity"]!!.valueAsInstance
123 if (activity == null ||
124 // Skip non destroyed activities.
125 // (!= true because we also skip if mDestroyed is missing)
126 activity["android.app.Activity", "mDestroyed"]?.value?.asBoolean != true
127 ) {
128 null
129 } else {
130 Reference(
131 valueObjectId = activity.objectId,
132 isLowPriority = false,
133 lazyDetailsResolver = {
134 LazyDetails(
135 name = "$index",
136 locationClassObjectId = activityClientRecordClassId,
137 locationType = ARRAY_ENTRY,
138 isVirtual = true,
139 matchedLibraryLeak = null
140 )
141 })
142 }
143 }
144 }
145 }
146 }
147 }
148 }
149 },
150
151
152 MESSAGE_QUEUE {
153 override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
154 val messageQueueClass =
155 graph.findClassByName("android.os.MessageQueue") ?: return null
156
157 val messageQueueClassId = messageQueueClass.objectId
158
159 return object : VirtualInstanceReferenceReader {
160 override fun matches(instance: HeapInstance) =
161 instance.instanceClassId == messageQueueClassId
162
163 override fun read(source: HeapInstance): Sequence<Reference> {
164 val firstMessage = source["android.os.MessageQueue", "mMessages"]!!.valueAsInstance
165 return generateSequence(firstMessage) { node ->
166 node["android.os.Message", "next"]!!.valueAsInstance
167 }
168 .withIndex()
169 .map { (index, node) ->
170 Reference(
171 valueObjectId = node.objectId,
172 isLowPriority = false,
173 lazyDetailsResolver = {
174 LazyDetails(
175 name = "$index",
176 locationClassObjectId = messageQueueClassId,
177 locationType = ARRAY_ENTRY,
178 isVirtual = true,
179 matchedLibraryLeak = null
180 )
181 }
182 )
183 }
184 }
185 }
186 }
187 },
188
189 ANIMATOR_WEAK_REF_SUCKS {
190 override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
191 val objectAnimatorClass =
192 graph.findClassByName("android.animation.ObjectAnimator") ?: return null
193
194 val weakRefClassId =
195 graph.findClassByName("java.lang.ref.WeakReference")?.objectId ?: return null
196
197 val objectAnimatorClassId = objectAnimatorClass.objectId
198
199 return object : VirtualInstanceReferenceReader {
200 override fun matches(instance: HeapInstance) =
201 instance.instanceClassId == objectAnimatorClassId
202
203 override fun read(source: HeapInstance): Sequence<Reference> {
204 val mTarget = source["android.animation.ObjectAnimator", "mTarget"]?.valueAsInstance
205 ?: return emptySequence()
206
207 if (mTarget.instanceClassId != weakRefClassId) {
208 return emptySequence()
209 }
210
211 val actualRef =
212 mTarget["java.lang.ref.Reference", "referent"]!!.value.holder as ReferenceHolder
213
214 return if (actualRef.isNull) {
215 emptySequence()
216 } else {
217 sequenceOf(Reference(
218 valueObjectId = actualRef.value,
219 isLowPriority = true,
220 lazyDetailsResolver = {
221 LazyDetails(
222 name = "mTarget",
223 locationClassObjectId = objectAnimatorClassId,
224 locationType = INSTANCE_FIELD,
225 matchedLibraryLeak = null,
226 isVirtual = true
227 )
228 }
229 ))
230 }
231 }
232 }
233 }
234 },
235
236 SAFE_ITERABLE_MAP {
237 override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
238 val mapClass =
239 graph.findClassByName(SAFE_ITERABLE_MAP_CLASS_NAME) ?: return null
240 // A subclass of SafeIterableMap with dual storage in a backing HashMap for fast get.
241 // Yes, that's a little weird.
242 val fastMapClass = graph.findClassByName(FAST_SAFE_ITERABLE_MAP_CLASS_NAME)
243
244 val mapClassId = mapClass.objectId
245 val fastMapClassId = fastMapClass?.objectId
246
247 return object : VirtualInstanceReferenceReader {
248 override fun matches(instance: HeapInstance) =
249 instance.instanceClassId.let { classId ->
250 classId == mapClassId || classId == fastMapClassId
251 }
252
253 override fun read(source: HeapInstance): Sequence<Reference> {
254 val start = source[SAFE_ITERABLE_MAP_CLASS_NAME, "mStart"]!!.valueAsInstance
255
256 val entries = generateSequence(start) { node ->
257 node[SAFE_ITERABLE_MAP_ENTRY_CLASS_NAME, "mNext"]!!.valueAsInstance
258 }
259
260 val locationClassObjectId = source.instanceClassId
261 return entries.flatMap { entry ->
262 // mkey is never null
263 val key = entry[SAFE_ITERABLE_MAP_ENTRY_CLASS_NAME, "mKey"]!!.value
264
265 val keyRef = Reference(
266 valueObjectId = key.asObjectId!!,
267 isLowPriority = false,
268 lazyDetailsResolver = {
269 LazyDetails(
270 name = "key()",
271 locationClassObjectId = locationClassObjectId,
272 locationType = ARRAY_ENTRY,
273 isVirtual = true,
274 matchedLibraryLeak = null
275 )
276 }
277 )
278
279 // mValue is never null
280 val value = entry[SAFE_ITERABLE_MAP_ENTRY_CLASS_NAME, "mValue"]!!.value
281
282 val valueRef = Reference(
283 valueObjectId = value.asObjectId!!,
284 isLowPriority = false,
285 lazyDetailsResolver = {
286 val keyAsString = key.asObject?.asInstance?.readAsJavaString()?.let { "\"$it\"" }
287 val keyAsName =
288 keyAsString ?: key.asObject?.toString() ?: "null"
289 LazyDetails(
290 name = keyAsName,
291 locationClassObjectId = locationClassObjectId,
292 locationType = ARRAY_ENTRY,
293 isVirtual = true,
294 matchedLibraryLeak = null
295 )
296 }
297 )
298 sequenceOf(keyRef, valueRef)
299 }
300 }
301 }
302 }
303 },
304
305 ARRAY_SET {
306 override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
307 val arraySetClassId = graph.findClassByName(ARRAY_SET_CLASS_NAME)?.objectId ?:return null
308
309 return object : VirtualInstanceReferenceReader {
310 override fun matches(instance: HeapInstance) = instance.instanceClassId == arraySetClassId
311
312 override fun read(source: HeapInstance): Sequence<Reference> {
313 val mArray = source[ARRAY_SET_CLASS_NAME, "mArray"]!!.valueAsObjectArray!!
314 val locationClassObjectId = source.instanceClassId
315 return mArray.readElements()
316 .filter { it.isNonNullReference }
317 .map { reference ->
318 Reference(
319 valueObjectId = reference.asNonNullObjectId!!,
320 isLowPriority = false,
321 lazyDetailsResolver = {
322 LazyDetails(
323 name = "element()",
324 locationClassObjectId = locationClassObjectId,
325 locationType = ARRAY_ENTRY,
326 isVirtual = true,
327 matchedLibraryLeak = null,
328 )
329 }
330 )
331 }
332 }
333 }
334 }
335 },
336
337 ;
338
339 companion object {
340 private const val ARRAY_SET_CLASS_NAME = "android.util.ArraySet"
341
342 // Note: not supporting the support lib version of these, which is identical but with an
343 // "android" package prefix instead of "androidx".
344 private const val SAFE_ITERABLE_MAP_CLASS_NAME = "androidx.arch.core.internal.SafeIterableMap"
345 private const val FAST_SAFE_ITERABLE_MAP_CLASS_NAME =
346 "androidx.arch.core.internal.FastSafeIterableMap"
347 private const val SAFE_ITERABLE_MAP_ENTRY_CLASS_NAME =
348 "androidx.arch.core.internal.SafeIterableMap\$Entry"
349 }
350 }
351