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

<lambda>null1 package shark.internal
2 
3 import shark.GcRoot
4 import shark.GcRoot.JavaFrame
5 import shark.GcRoot.JniGlobal
6 import shark.GcRoot.ThreadObject
7 import shark.HeapGraph
8 import shark.HeapObject
9 import shark.HeapObject.HeapClass
10 import shark.HeapObject.HeapInstance
11 import shark.HeapObject.HeapObjectArray
12 import shark.HeapObject.HeapPrimitiveArray
13 import shark.IgnoredReferenceMatcher
14 import shark.LibraryLeakReferenceMatcher
15 import shark.ReferenceMatcher
16 import shark.ReferencePattern.NativeGlobalVariablePattern
17 import shark.filterFor
18 
19 /**
20  * Extracted from PathFinder, this should eventually be part of public API surface
21  * and we should likely also revisit the gc root type filtering which happens during
22  * heap parsing, as that's not really a concern for the heap parser and more for path
23  * finding. There are probably memory concerns as well there though. We could:
24  * - compress the storing of these roots
25  * - keep only the roots locations and read / deserialize as needed
26  * - Ensure a unique / consistent view of roots by doing the work of GcRootProvider
27  * at parsing time and keeping that list.
28  */
29 internal class GcRootProvider(
30   private val graph: HeapGraph,
31   referenceMatchers: List<ReferenceMatcher>
32 ) {
33 
34   private val jniGlobalReferenceMatchers: Map<String, ReferenceMatcher>
35 
36   init {
37     val jniGlobals = mutableMapOf<String, ReferenceMatcher>()
38     referenceMatchers.filterFor(graph).forEach { referenceMatcher ->
39       when (val pattern = referenceMatcher.pattern) {
40         is NativeGlobalVariablePattern -> {
41           jniGlobals[pattern.className] = referenceMatcher
42         }
43       }
44     }
45     this.jniGlobalReferenceMatchers = jniGlobals
46   }
47 
48   class GcRootReference(
49     val gcRoot: GcRoot,
50     val isLowPriority: Boolean,
51     val matchedLibraryLeak: LibraryLeakReferenceMatcher?,
52   )
53 
54   fun provideGcRoots(): Sequence<GcRootReference> {
55     return sortedGcRoots().asSequence().mapNotNull { (heapObject, gcRoot) ->
56       when (gcRoot) {
57         // Note: in sortedGcRoots we already filter out any java frame that has an associated
58         // thread. These are the remaining ones (shouldn't be any, this is just in case).
59         is JavaFrame -> {
60           GcRootReference(
61             gcRoot,
62             isLowPriority = true,
63             matchedLibraryLeak = null
64           )
65         }
66         is JniGlobal -> {
67           val referenceMatcher = when (heapObject) {
68             is HeapClass -> jniGlobalReferenceMatchers[heapObject.name]
69             is HeapInstance -> jniGlobalReferenceMatchers[heapObject.instanceClassName]
70             is HeapObjectArray -> jniGlobalReferenceMatchers[heapObject.arrayClassName]
71             is HeapPrimitiveArray -> jniGlobalReferenceMatchers[heapObject.arrayClassName]
72           }
73           if (referenceMatcher !is IgnoredReferenceMatcher) {
74             if (referenceMatcher is LibraryLeakReferenceMatcher) {
75               GcRootReference(
76                 gcRoot,
77                 isLowPriority = true,
78                 matchedLibraryLeak = referenceMatcher
79               )
80             } else {
81               GcRootReference(
82                 gcRoot,
83                 isLowPriority = false,
84                 matchedLibraryLeak = null
85               )
86             }
87           } else {
88             null
89           }
90         }
91         else -> {
92           GcRootReference(
93             gcRoot,
94             isLowPriority = false,
95             matchedLibraryLeak = null
96           )
97         }
98       }
99     }
100   }
101 
102   /**
103    * Sorting GC roots to get stable shortest path
104    * Once sorted all ThreadObject Gc Roots are located before JavaLocalPattern Gc Roots.
105    * This ensures ThreadObjects are visited before JavaFrames, and threadsBySerialNumber can be
106    * built before JavaFrames.
107    */
108   private fun sortedGcRoots(): List<Pair<HeapObject, GcRoot>> {
109     val rootClassName: (HeapObject) -> String = { graphObject ->
110       when (graphObject) {
111         is HeapClass -> {
112           graphObject.name
113         }
114         is HeapInstance -> {
115           graphObject.instanceClassName
116         }
117         is HeapObjectArray -> {
118           graphObject.arrayClassName
119         }
120         is HeapPrimitiveArray -> {
121           graphObject.arrayClassName
122         }
123       }
124     }
125 
126     val threadSerialNumbers =
127       ThreadObjects.getThreadObjects(graph).map { it.threadSerialNumber }.toSet()
128 
129     return graph.gcRoots
130       .filter { gcRoot ->
131         // GC roots sometimes reference objects that don't exist in the heap dump
132         // See https://github.com/square/leakcanary/issues/1516
133         graph.objectExists(gcRoot.id) &&
134           // Only include java frames that do not have a corresponding ThreadObject.
135           // JavaLocalReferenceReader will insert the other java frames.
136           !(gcRoot is JavaFrame && gcRoot.threadSerialNumber in threadSerialNumbers)
137       }
138       .map { graph.findObjectById(it.id) to it }
139       .sortedWith { (graphObject1, root1), (graphObject2, root2) ->
140         // Sorting based on pattern name first, but we want ThreadObjects to be first because
141         // they'll later enqueue java frames via JavaLocalReferenceReader in the low priority queue
142         // and we want those java frames at the head of the low priority queue.
143         if (root1 is ThreadObject && root2 !is ThreadObject) {
144           return@sortedWith -1
145         } else if (root1 !is ThreadObject && root2 is ThreadObject) {
146           return@sortedWith 1
147         }
148         val gcRootTypeComparison = root2::class.java.name.compareTo(root1::class.java.name)
149         if (gcRootTypeComparison != 0) {
150           gcRootTypeComparison
151         } else {
152           rootClassName(graphObject1).compareTo(rootClassName(graphObject2))
153         }
154       }
155   }
156 }
157