Skip to content

Commit

Permalink
Merge branch 'py/array_set_expander' into release_2.10
Browse files Browse the repository at this point in the history
* py/array_set_expander:
  Add custom expander for Android ArraySet
  • Loading branch information
pyricau committed Nov 11, 2022
2 parents 2e6488d + 6c61c09 commit ebda255
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 16 deletions.
6 changes: 3 additions & 3 deletions shark-android/src/test/java/shark/HprofIOPerfTest.kt
Expand Up @@ -180,7 +180,7 @@ class HprofIOPerfTest {
)
.isEqualTo(
listOf(
25752, 40.0, 1308325, 25757, 40.0, 1308505
25760, 40.0, 1309045, 25765, 40.0, 1309225
)
)
}
Expand All @@ -198,7 +198,7 @@ class HprofIOPerfTest {
)
.isEqualTo(
listOf(
22489, 40.0, 2203274, 22494, 40.0, 2203454
22493, 40.0, 2203818, 22498, 40.0, 2203998
)
)
}
Expand All @@ -216,7 +216,7 @@ class HprofIOPerfTest {
)
.isEqualTo(
listOf(
16888, 32.0, 768676, 16890, 32.0, 768740
16889, 32.0, 768692, 16891, 32.0, 768756
)
)
}
Expand Down
38 changes: 37 additions & 1 deletion shark/src/main/java/shark/internal/AndroidReferenceReaders.kt
Expand Up @@ -162,9 +162,45 @@ internal enum class AndroidReferenceReaders : OptionalFactory {
}
}
}
};
},

ARRAY_SET {
override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? {
val arraySetClassId = graph.findClassByName(ARRAY_SET_CLASS_NAME)?.objectId ?:return null

return object : VirtualInstanceReferenceReader {
override fun matches(instance: HeapInstance) = instance.instanceClassId == arraySetClassId

override fun read(source: HeapInstance): Sequence<Reference> {
val mArray = source[ARRAY_SET_CLASS_NAME, "mArray"]!!.valueAsObjectArray!!
val locationClassObjectId = source.instanceClassId
return mArray.readElements()
.filter { it.isNonNullReference }
.map { reference ->
Reference(
valueObjectId = reference.asNonNullObjectId!!,
isLowPriority = false,
lazyDetailsResolver = {
LazyDetails(
name = "element()",
locationClassObjectId = locationClassObjectId,
locationType = ARRAY_ENTRY,
isVirtual = true,
matchedLibraryLeak = null,
)
}
)
}
}
}
}
},

;

companion object {
private const val ARRAY_SET_CLASS_NAME = "android.util.ArraySet"

// Note: not supporting the support lib version of these, which is identical but with an
// "android" package prefix instead of "androidx".
private const val SAFE_ITERABLE_MAP_CLASS_NAME = "androidx.arch.core.internal.SafeIterableMap"
Expand Down
4 changes: 2 additions & 2 deletions shark/src/test/java/shark/LeakTraceRenderingTest.kt
Expand Up @@ -103,9 +103,9 @@ class LeakTraceRenderingTest {
leakingReasons += "because reasons"
}
}
}), leakFilters = listOf(LeakingObjectFilter { heapObject ->
}), leakingObjectFinder = FilteringLeakingObjectFinder(listOf(LeakingObjectFilter { heapObject ->
heapObject is HeapInstance && heapObject instanceOf "ClassB"
})
}))
)

analysis renders """
Expand Down
4 changes: 2 additions & 2 deletions shark/src/test/java/shark/RetainedSizeTest.kt
Expand Up @@ -330,10 +330,10 @@ class RetainedSizeTest {

val analysis = hprofFile.checkForLeaks<HeapAnalysis>(
computeRetainedHeapSize = true,
leakFilters = listOf(FilteringLeakingObjectFinder.LeakingObjectFilter { heapObject ->
leakingObjectFinder = FilteringLeakingObjectFinder(listOf(FilteringLeakingObjectFinder.LeakingObjectFilter { heapObject ->
heapObject is HeapInstance &&
heapObject.instanceClassName == Thread::class.java.name
})
}))
)
println(analysis.toString())
analysis as HeapAnalysisSuccess
Expand Down
15 changes: 7 additions & 8 deletions shark/src/test/java/shark/TestUtil.kt
@@ -1,21 +1,20 @@
package shark

import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
import shark.HprofHeapGraph.Companion.openHeapGraph
import shark.ReferencePattern.InstanceFieldPattern
import shark.ReferencePattern.JavaLocalPattern
import java.io.File
import java.lang.ref.PhantomReference
import java.lang.ref.SoftReference
import java.lang.ref.WeakReference
import shark.HprofHeapGraph.Companion.openHeapGraph
import shark.ReferencePattern.InstanceFieldPattern
import shark.ReferencePattern.JavaLocalPattern

fun <T : HeapAnalysis> DualSourceProvider.checkForLeaks(
objectInspectors: List<ObjectInspector> = emptyList(),
computeRetainedHeapSize: Boolean = false,
referenceMatchers: List<ReferenceMatcher> = defaultReferenceMatchers,
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null,
leakFilters: List<LeakingObjectFilter> = ObjectInspectors.jdkLeakingObjectFilters,
leakingObjectFinder: LeakingObjectFinder = FilteringLeakingObjectFinder(ObjectInspectors.jdkLeakingObjectFilters),
file: File = File("/no/file")
): T {
val inspectors = if (ObjectInspectors.KEYED_WEAK_REFERENCE !in objectInspectors) {
Expand All @@ -29,7 +28,7 @@ fun <T : HeapAnalysis> DualSourceProvider.checkForLeaks(
heapAnalyzer.analyze(
heapDumpFile = file,
graph = graph,
leakingObjectFinder = FilteringLeakingObjectFinder(leakFilters),
leakingObjectFinder = leakingObjectFinder,
referenceMatchers = referenceMatchers,
computeRetainedHeapSize = computeRetainedHeapSize,
objectInspectors = inspectors,
Expand All @@ -49,11 +48,11 @@ fun <T : HeapAnalysis> File.checkForLeaks(
referenceMatchers: List<ReferenceMatcher> = defaultReferenceMatchers,
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null,
leakFilters: List<LeakingObjectFilter> = ObjectInspectors.jdkLeakingObjectFilters,
leakingObjectFinder: LeakingObjectFinder = FilteringLeakingObjectFinder(ObjectInspectors.jdkLeakingObjectFilters),
): T {
return FileSourceProvider(this).checkForLeaks(
objectInspectors, computeRetainedHeapSize, referenceMatchers, metadataExtractor,
proguardMapping, leakFilters, this
proguardMapping, leakingObjectFinder, this
)
}

Expand Down
Expand Up @@ -3,9 +3,20 @@ package shark.internal
import java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import shark.FilteringLeakingObjectFinder
import shark.HeapAnalysisSuccess
import shark.HeapGraph
import shark.HeapObject
import shark.HeapObject.HeapInstance
import shark.HprofHeapGraph.Companion.openHeapGraph
import shark.IgnoredReferenceMatcher
import shark.LeakTraceReference.ReferenceType.ARRAY_ENTRY
import shark.LeakingObjectFinder
import shark.ReferenceMatcher
import shark.ReferencePattern.StaticFieldPattern
import shark.checkForLeaks
import shark.defaultReferenceMatchers
import shark.internal.AndroidReferenceReaders.Companion

class AndroidReferenceReadersHprofTest {

Expand All @@ -30,6 +41,57 @@ class AndroidReferenceReadersHprofTest {
assertThat(mapReference.referenceName).isEqualTo("\"leaking\"")
assertThat(mapReference.referenceType).isEqualTo(ARRAY_ENTRY)
}

@Test fun `ArraySet traversed as set`() {
// This hprof happens to have an ArraySet in it.
val hprofFile = "safe_iterable_map.hprof".classpathFile()

val analysis = hprofFile.checkForFakeArraySetLeak()

val leakTrace = analysis.applicationLeaks.single().leakTraces.single()

println(leakTrace)

val mapReference =
leakTrace.referencePath.single { it.owningClassSimpleName == "ArraySet" }
assertThat(mapReference.referenceName).isEqualTo("element()")
assertThat(mapReference.referenceType).isEqualTo(ARRAY_ENTRY)
}
}

fun File.checkForFakeArraySetLeak(): HeapAnalysisSuccess {
val instanceHeldByArraySet =
"android.view.accessibility.AccessibilityNodeInfo\$AccessibilityAction"

class ArraySetFakeLeakingObjectFinder : LeakingObjectFinder {
override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
val arraySetInstances = graph.findClassByName("android.util.ArraySet")!!
.instances
.map { arraySetInstance ->
arraySetInstance to arraySetInstance["android.util.ArraySet", "mArray"]!!
.valueAsObjectArray!!
.readElements()
.filter {
it.asObject?.asInstance?.instanceClass?.name == instanceHeldByArraySet
}
.toList()
}
val firstElementReferencedByArraySet = arraySetInstances.first { (_, elements) ->
elements.isNotEmpty()
}.second.first()

return setOf(firstElementReferencedByArraySet.asObjectId!!)
}
}
return checkForLeaks(
referenceMatchers = defaultReferenceMatchers + IgnoredReferenceMatcher(
StaticFieldPattern(
instanceHeldByArraySet,
"ACTION_FOCUS"
)
),
leakingObjectFinder = ArraySetFakeLeakingObjectFinder()
)
}

fun String.classpathFile(): File {
Expand Down

0 comments on commit ebda255

Please sign in to comment.