xref: /aosp_15_r20/external/leakcanary2/shark/src/main/java/shark/ObjectInspectors.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 /*
<lambda>null2  * Copyright (C) 2018 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package shark
17 
18 import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
19 import shark.HeapObject.HeapClass
20 import shark.HeapObject.HeapInstance
21 import java.util.EnumSet
22 
23 /**
24  * A set of default [ObjectInspector]s that knows about common JDK objects.
25  */
26 enum class ObjectInspectors : ObjectInspector {
27 
28   KEYED_WEAK_REFERENCE {
29 
30     override val leakingObjectFilter = { heapObject: HeapObject ->
31       KeyedWeakReferenceFinder.findKeyedWeakReferences(heapObject.graph)
32         .filter { it.hasReferent && it.isRetained }
33         .any { reference ->
34           reference.referent.value == heapObject.objectId
35         }
36     }
37 
38     override fun inspect(
39       reporter: ObjectReporter
40     ) {
41       val graph = reporter.heapObject.graph
42       val references = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph)
43 
44       val objectId = reporter.heapObject.objectId
45       references.forEach { ref ->
46         if (ref.referent.value == objectId) {
47           reporter.leakingReasons += if (ref.description.isNotEmpty()) {
48             "ObjectWatcher was watching this because ${ref.description}"
49           } else {
50             "ObjectWatcher was watching this"
51           }
52           reporter.labels += "key = ${ref.key}"
53           if (ref.watchDurationMillis != null) {
54             reporter.labels += "watchDurationMillis = ${ref.watchDurationMillis}"
55           }
56           if (ref.retainedDurationMillis != null) {
57             reporter.labels += "retainedDurationMillis = ${ref.retainedDurationMillis}"
58           }
59         }
60       }
61     }
62   },
63 
64   CLASSLOADER {
65     override fun inspect(
66       reporter: ObjectReporter
67     ) {
68       reporter.whenInstanceOf(ClassLoader::class) {
69         notLeakingReasons += "A ClassLoader is never leaking"
70       }
71     }
72   },
73 
74   CLASS {
75     override fun inspect(
76       reporter: ObjectReporter
77     ) {
78       if (reporter.heapObject is HeapClass) {
79         reporter.notLeakingReasons += "a class is never leaking"
80       }
81     }
82   },
83 
84   ANONYMOUS_CLASS {
85     override fun inspect(
86       reporter: ObjectReporter
87     ) {
88       val heapObject = reporter.heapObject
89       if (heapObject is HeapInstance) {
90         val instanceClass = heapObject.instanceClass
91         if (instanceClass.name.matches(ANONYMOUS_CLASS_NAME_PATTERN_REGEX)) {
92           val parentClassRecord = instanceClass.superclass!!
93           if (parentClassRecord.name == "java.lang.Object") {
94             try {
95               // This is an anonymous class implementing an interface. The API does not give access
96               // to the interfaces implemented by the class. We check if it's in the class path and
97               // use that instead.
98               val actualClass = Class.forName(instanceClass.name)
99               val interfaces = actualClass.interfaces
100               reporter.labels += if (interfaces.isNotEmpty()) {
101                 val implementedInterface = interfaces[0]
102                 "Anonymous class implementing ${implementedInterface.name}"
103               } else {
104                 "Anonymous subclass of java.lang.Object"
105               }
106             } catch (ignored: ClassNotFoundException) {
107             }
108           } else {
109             // Makes it easier to figure out which anonymous class we're looking at.
110             reporter.labels += "Anonymous subclass of ${parentClassRecord.name}"
111           }
112         }
113       }
114     }
115   },
116 
117   THREAD {
118     override fun inspect(
119       reporter: ObjectReporter
120     ) {
121       reporter.whenInstanceOf(Thread::class) { instance ->
122         val threadName = instance[Thread::class, "name"]!!.value.readAsJavaString()
123         labels += "Thread name: '$threadName'"
124       }
125     }
126   };
127 
128   internal open val leakingObjectFilter: ((heapObject: HeapObject) -> Boolean)? = null
129 
130   companion object {
131     private const val ANONYMOUS_CLASS_NAME_PATTERN = "^.+\\$\\d+$"
132     private val ANONYMOUS_CLASS_NAME_PATTERN_REGEX = ANONYMOUS_CLASS_NAME_PATTERN.toRegex()
133 
134     /** @see ObjectInspectors */
135     val jdkDefaults: List<ObjectInspector>
136       get() {
137         return values().toList()
138       }
139 
140     /**
141      * Returns a list of [LeakingObjectFilter] suitable for common JDK projects.
142      */
143     val jdkLeakingObjectFilters: List<LeakingObjectFilter> =
144       createLeakingObjectFilters(EnumSet.allOf(ObjectInspectors::class.java))
145 
146     /**
147      * Creates a list of [LeakingObjectFilter] based on the passed in [ObjectInspectors].
148      */
149     fun createLeakingObjectFilters(inspectors: Set<ObjectInspectors>): List<LeakingObjectFilter> =
150       inspectors.mapNotNull { it.leakingObjectFilter }
151         .map { filter ->
152           LeakingObjectFilter { heapObject -> filter(heapObject) }
153         }
154   }
155 }
156