xref: /aosp_15_r20/external/leakcanary2/shark/src/main/java/shark/internal/OpenJdkInstanceRefReaders.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 package shark.internal
2 
3 import shark.HeapGraph
4 import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory
5 import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader
6 import shark.HeapObject.HeapInstance
7 
8 /**
9  * Defines [VirtualInstanceReferenceReader] factories for common OpenJDK data structures.
10  *
11  * Note: the expanders target the direct classes and don't target subclasses, as these might
12  * include additional out going references that would be missed.
13  */
14 internal enum class OpenJdkInstanceRefReaders : OptionalFactory {
15 
16   // https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/java/util/LinkedList.java
17   LINKED_LIST {
createnull18     override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
19       val linkedListClass = graph.findClassByName("java.util.LinkedList") ?: return null
20       val isOpenJdkImpl = linkedListClass.readRecordFields()
21         .any { linkedListClass.instanceFieldName(it) == "first" }
22 
23       if (!isOpenJdkImpl) {
24         return null
25       }
26       return InternalSharedLinkedListReferenceReader(
27         classObjectId = linkedListClass.objectId,
28         headFieldName = "first",
29         nodeClassName = "java.util.LinkedList\$Node",
30         nodeNextFieldName = "next",
31         nodeElementFieldName = "item",
32       )
33     }
34   },
35 
36   // https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/java/util/ArrayList.java
37   ARRAY_LIST {
createnull38     override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
39       val arrayListClass = graph.findClassByName("java.util.ArrayList") ?: return null
40 
41       val isOpenJdkImpl = arrayListClass.readRecordFields()
42         .any { arrayListClass.instanceFieldName(it) == "elementData" }
43 
44       if (!isOpenJdkImpl) {
45         return null
46       }
47 
48       return InternalSharedArrayListReferenceReader(
49         className = "java.util.ArrayList",
50         classObjectId = arrayListClass.objectId,
51         elementArrayName = "elementData",
52         sizeFieldName = "size",
53       )
54     }
55   },
56 
57   // https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java;bpv=0;bpt=1
58   COPY_ON_WRITE_ARRAY_LIST {
createnull59     override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
60       val arrayListClass = graph.findClassByName("java.util.concurrent.CopyOnWriteArrayList") ?: return null
61 
62       val isOpenJdkImpl = arrayListClass.readRecordFields()
63         .any { arrayListClass.instanceFieldName(it) == "array" }
64 
65       if (!isOpenJdkImpl) {
66         return null
67       }
68 
69       return InternalSharedArrayListReferenceReader(
70         className = "java.util.concurrent.CopyOnWriteArrayList",
71         classObjectId = arrayListClass.objectId,
72         elementArrayName = "array",
73         sizeFieldName = null,
74       )
75     }
76   },
77 
78   // Initial import
79   // https://cs.android.com/android/_/android/platform/libcore/+/51b1b6997fd3f980076b8081f7f1165ccc2a4008:ojluni/src/main/java/java/util/HashMap.java
80   // Latest on master
81   // https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/java/util/HashMap.java
82   /**
83    * Handles HashMap & LinkedHashMap
84    */
85   HASH_MAP {
createnull86     override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
87       val hashMapClass = graph.findClassByName("java.util.HashMap") ?: return null
88 
89       // No loadFactor field in the Apache Harmony impl.
90       val isOpenJdkImpl = hashMapClass.readRecordFields()
91         .any { hashMapClass.instanceFieldName(it) == "loadFactor" }
92 
93       if (!isOpenJdkImpl) {
94         return null
95       }
96 
97       val linkedHashMapClass = graph.findClassByName("java.util.LinkedHashMap")
98       // Initially Entry, changed to Node in JDK 1.8
99       val nodeClassName = if (graph.findClassByName("java.util.HashMap\$Entry") != null) {
100         "java.util.HashMap\$Entry"
101       } else if (graph.findClassByName("java.util.HashMap\$HashMapEntry") != null) {
102         "java.util.HashMap\$HashMapEntry"
103       } else {
104         "java.util.HashMap\$Node"
105       }
106 
107       val hashMapClassId = hashMapClass.objectId
108       val linkedHashMapClassId = linkedHashMapClass?.objectId ?: 0
109 
110       return InternalSharedHashMapReferenceReader(
111         className = "java.util.HashMap",
112         tableFieldName = "table",
113         nodeClassName = nodeClassName,
114         nodeNextFieldName = "next",
115         nodeKeyFieldName = "key",
116         nodeValueFieldName = "value",
117         keyName = "key()",
118         keysOnly = false,
119         matches = {
120           val instanceClassId = it.instanceClassId
121           instanceClassId == hashMapClassId || instanceClassId == linkedHashMapClassId
122         },
123         declaringClassId = { it.instanceClassId }
124       )
125     }
126   },
127 
128   // https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/java/util/concurrent/ConcurrentHashMap.java
129   // Note: structure of impl shared by OpenJDK & Apache Harmony.
130   CONCURRENT_HASH_MAP {
createnull131     override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
132       val hashMapClass =
133         graph.findClassByName("java.util.concurrent.ConcurrentHashMap") ?: return null
134 
135       // No table field in Apache Harmony impl (as seen on Android 4).
136       val isOpenJdkImpl = hashMapClass.readRecordFields()
137         .any { hashMapClass.instanceFieldName(it) == "table" }
138 
139       if (!isOpenJdkImpl) {
140         return null
141       }
142 
143       val hashMapClassId = hashMapClass.objectId
144       return InternalSharedHashMapReferenceReader(
145         className = "java.util.concurrent.ConcurrentHashMap",
146         tableFieldName = "table",
147         nodeClassName = "java.util.concurrent.ConcurrentHashMap\$Node",
148         nodeNextFieldName = "next",
149         nodeKeyFieldName = "key",
150         nodeValueFieldName = "val",
151         keyName = "key()",
152         keysOnly = false,
153         matches = { it.instanceClassId == hashMapClassId },
154         declaringClassId = { it.instanceClassId }
155       )
156     }
157   },
158 
159   /**
160    * Handles HashSet & LinkedHashSet
161    */
162   HASH_SET {
createnull163     override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
164       val hashSetClass = graph.findClassByName("java.util.HashSet") ?: return null
165 
166       val isOpenJdkImpl = hashSetClass.readRecordFields()
167         .any { hashSetClass.instanceFieldName(it) == "map" }
168 
169       if (!isOpenJdkImpl) {
170         return null
171       }
172 
173       val linkedHashSetClass = graph.findClassByName("java.util.LinkedHashSet")
174       // Initially Entry, changed to Node in JDK 1.8
175       val nodeClassName = if (graph.findClassByName("java.util.HashMap\$Entry") != null) {
176         "java.util.HashMap\$Entry"
177       } else if (graph.findClassByName("java.util.HashMap\$HashMapEntry") != null) {
178         "java.util.HashMap\$HashMapEntry"
179       } else {
180         "java.util.HashMap\$Node"
181       }
182       val hashSetClassId = hashSetClass.objectId
183       val linkedHashSetClassId = linkedHashSetClass?.objectId ?: 0
184       return object : VirtualInstanceReferenceReader {
185         override fun matches(instance: HeapInstance): Boolean {
186           val instanceClassId = instance.instanceClassId
187           return instanceClassId == hashSetClassId || instanceClassId == linkedHashSetClassId
188         }
189 
190         override fun read(source: HeapInstance): Sequence<Reference> {
191           // "HashSet.map" is never null when looking at the Android sources history, however
192           // we've had a crash report where it was null on API 24.
193           // https://github.com/square/leakcanary/issues/2342
194           val map = source["java.util.HashSet", "map"]!!.valueAsInstance ?: return emptySequence()
195           return InternalSharedHashMapReferenceReader(
196             className = "java.util.HashMap",
197             tableFieldName = "table",
198             nodeClassName = nodeClassName,
199             nodeNextFieldName = "next",
200             nodeKeyFieldName = "key",
201             nodeValueFieldName = "value",
202             keyName = "element()",
203             keysOnly = true,
204             matches = { true },
205             declaringClassId = { source.instanceClassId }
206           ).read(map)
207         }
208       }
209     }
210   }
211   ;
212 }
213