<lambda>null1 package shark.internal
2
3 import java.util.LinkedHashMap
4 import kotlin.LazyThreadSafetyMode.NONE
5 import shark.HeapGraph
6 import shark.HeapObject.HeapClass
7 import shark.HeapObject.HeapInstance
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
9 import shark.IgnoredReferenceMatcher
10 import shark.LibraryLeakReferenceMatcher
11 import shark.PrimitiveType
12 import shark.PrimitiveType.BOOLEAN
13 import shark.PrimitiveType.BYTE
14 import shark.PrimitiveType.CHAR
15 import shark.PrimitiveType.DOUBLE
16 import shark.PrimitiveType.FLOAT
17 import shark.PrimitiveType.INT
18 import shark.PrimitiveType.LONG
19 import shark.PrimitiveType.SHORT
20 import shark.internal.Reference.LazyDetails
21 import shark.internal.ReferenceLocationType.INSTANCE_FIELD
22 import shark.ReferenceMatcher
23 import shark.ReferencePattern.InstanceFieldPattern
24 import shark.filterFor
25
26 /**
27 * Expands instance fields that hold non null references.
28 */
29 internal class FieldInstanceReferenceReader(
30 graph: HeapGraph,
31 referenceMatchers: List<ReferenceMatcher>
32 ) : ReferenceReader<HeapInstance> {
33
34 private val fieldNameByClassName: Map<String, Map<String, ReferenceMatcher>>
35 private val javaLangObjectId: Long
36
37 private val sizeOfObjectInstances: Int
38
39 init {
40 val objectClass = graph.findClassByName("java.lang.Object")
41 javaLangObjectId = objectClass?.objectId ?: -1
42 sizeOfObjectInstances = determineSizeOfObjectInstances(objectClass, graph)
43
44 val fieldNameByClassName = mutableMapOf<String, MutableMap<String, ReferenceMatcher>>()
45 referenceMatchers.filterFor(graph).forEach { referenceMatcher ->
46 val pattern = referenceMatcher.pattern
47 if (pattern is InstanceFieldPattern) {
48 val mapOrNull = fieldNameByClassName[pattern.className]
49 val map = if (mapOrNull != null) mapOrNull else {
50 val newMap = mutableMapOf<String, ReferenceMatcher>()
51 fieldNameByClassName[pattern.className] = newMap
52 newMap
53 }
54 map[pattern.fieldName] = referenceMatcher
55 }
56 }
57 this.fieldNameByClassName = fieldNameByClassName
58 }
59
60 override fun read(source: HeapInstance): Sequence<Reference> {
61 if (source.isPrimitiveWrapper ||
62 // We ignore the fact that String references a value array to avoid having
63 // to read the string record and find the object id for that array, since we know
64 // it won't be interesting anyway.
65 // That also means the value array isn't added to the dominator tree, so we need to
66 // add that back when computing shallow size in ShallowSizeCalculator.
67 // Another side effect is that if the array is referenced elsewhere, we might
68 // double count its side.
69 source.instanceClassName == "java.lang.String" ||
70 source.instanceClass.instanceByteSize <= sizeOfObjectInstances
71 ) {
72 return emptySequence()
73 }
74
75 val fieldReferenceMatchers = LinkedHashMap<String, ReferenceMatcher>()
76
77 val classHierarchy = source.instanceClass.classHierarchyWithoutJavaLangObject(javaLangObjectId)
78
79 classHierarchy.forEach {
80 val referenceMatcherByField = fieldNameByClassName[it.name]
81 if (referenceMatcherByField != null) {
82 for ((fieldName, referenceMatcher) in referenceMatcherByField) {
83 if (!fieldReferenceMatchers.containsKey(fieldName)) {
84 fieldReferenceMatchers[fieldName] = referenceMatcher
85 }
86 }
87 }
88 }
89
90 return with(source) {
91 // Assigning to local variable to avoid repeated lookup and cast:
92 // HeapInstance.graph casts HeapInstance.hprofGraph to HeapGraph in its getter
93 val hprofGraph = graph
94 val fieldReader by lazy(NONE) {
95 FieldIdReader(readRecord(), hprofGraph.identifierByteSize)
96 }
97 val result = mutableListOf<Pair<String, Reference>>()
98 var skipBytesCount = 0
99
100 for (heapClass in classHierarchy) {
101 for (fieldRecord in heapClass.readRecordFields()) {
102 if (fieldRecord.type != PrimitiveType.REFERENCE_HPROF_TYPE) {
103 // Skip all fields that are not references. Track how many bytes to skip
104 skipBytesCount += hprofGraph.getRecordSize(fieldRecord)
105 } else {
106 // Skip the accumulated bytes offset
107 fieldReader.skipBytes(skipBytesCount)
108 skipBytesCount = 0
109 val valueObjectId = fieldReader.readId()
110 if (valueObjectId != 0L) {
111 val name = heapClass.instanceFieldName(fieldRecord)
112 val referenceMatcher = fieldReferenceMatchers[name]
113 if (referenceMatcher !is IgnoredReferenceMatcher) {
114 val locationClassObjectId = heapClass.objectId
115 result.add(
116 name to Reference(
117 valueObjectId = valueObjectId,
118 isLowPriority = referenceMatcher != null,
119 lazyDetailsResolver = {
120 LazyDetails(
121 name = name,
122 locationClassObjectId = locationClassObjectId,
123 locationType = INSTANCE_FIELD,
124 matchedLibraryLeak = referenceMatcher as LibraryLeakReferenceMatcher?,
125 isVirtual = false
126 )
127 }
128 )
129 )
130 }
131 }
132 }
133 }
134 }
135 result.sortBy { it.first }
136 result.asSequence().map { it.second }
137 }
138 }
139
140 /**
141 * Returns class hierarchy for an instance, but without it's root element, which is always
142 * java.lang.Object.
143 * Why do we want class hierarchy without java.lang.Object?
144 * In pre-M there were no ref fields in java.lang.Object; and FieldIdReader wouldn't be created
145 * Android M added shadow$_klass_ reference to class, so now it triggers extra record read.
146 * Solution: skip heap class for java.lang.Object completely when reading the records
147 * @param javaLangObjectId ID of the java.lang.Object to run comparison against
148 */
149 private fun HeapClass.classHierarchyWithoutJavaLangObject(
150 javaLangObjectId: Long
151 ): List<HeapClass> {
152 val result = mutableListOf<HeapClass>()
153 var parent: HeapClass? = this
154 while (parent != null && parent.objectId != javaLangObjectId) {
155 result += parent
156 parent = parent.superclass
157 }
158 return result
159 }
160
161 private fun HeapGraph.getRecordSize(field: FieldRecord) =
162 when (field.type) {
163 PrimitiveType.REFERENCE_HPROF_TYPE -> identifierByteSize
164 BOOLEAN.hprofType -> 1
165 CHAR.hprofType -> 2
166 FLOAT.hprofType -> 4
167 DOUBLE.hprofType -> 8
168 BYTE.hprofType -> 1
169 SHORT.hprofType -> 2
170 INT.hprofType -> 4
171 LONG.hprofType -> 8
172 else -> throw IllegalStateException("Unknown type ${field.type}")
173 }
174
175 private fun determineSizeOfObjectInstances(
176 objectClass: HeapClass?,
177 graph: HeapGraph
178 ): Int {
179 return if (objectClass != null) {
180 // In Android 16 ClassDumpRecord.instanceSize for java.lang.Object can be 8 yet there are 0
181 // fields. This is likely because there is extra per instance data that isn't coming from
182 // fields in the Object class. See #1374
183 val objectClassFieldSize = objectClass.readFieldsByteSize()
184
185 // shadow$_klass_ (object id) + shadow$_monitor_ (Int)
186 val sizeOfObjectOnArt = graph.identifierByteSize + INT.byteSize
187 if (objectClassFieldSize == sizeOfObjectOnArt) {
188 sizeOfObjectOnArt
189 } else {
190 0
191 }
192 } else {
193 0
194 }
195 }
196 }
197