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