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

<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