Skip to content

Instantly share code, notes, and snippets.

@pyricau
Created March 27, 2024 20:57
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyricau/e51e75a334882fc8e52e66c72c41ba47 to your computer and use it in GitHub Desktop.
Save pyricau/e51e75a334882fc8e52e66c72c41ba47 to your computer and use it in GitHub Desktop.
A standalone Kotlin script that shows how to do custom heap analysis work with Shark, the heap parsing library that powers LeakCanary (see https://square.github.io/leakcanary/shark)
#!/usr/bin/env kotlin
// Before running this script, install Kotlin with `brew install kotlin`
// Then run this with `kotlin shark-custom-script.main.kts`
// Edit this script in the latest Android Studio or IntelliJ IDEA releases to get dependency import and auto completion.
@file:Repository("https://repo.maven.apache.org/maven2/")
@file:DependsOn("com.squareup.leakcanary:shark-android:2.13")
import java.io.File
import shark.AndroidMetadataExtractor
import shark.AndroidObjectInspectors
import shark.AndroidReferenceMatchers
import shark.FilteringLeakingObjectFinder
import shark.HeapAnalyzer
import shark.HeapObject.HeapInstance
import shark.HprofHeapGraph.Companion.openHeapGraph
val heapDumpFile: File = TODO("Replace this with hprof file") // = File("PATH_TO_HPROF")
val heapAnalyzer = HeapAnalyzer(listener = { step ->
println("Analysis in progress, working on: ${step.name}")
})
heapDumpFile.openHeapGraph(
proguardMapping = null
).use { graph ->
println("Looking for known leaks $heapDumpFile")
val heapAnalysis = heapAnalyzer.analyze(
heapDumpFile,
graph = graph,
leakingObjectFinder = FilteringLeakingObjectFinder(
AndroidObjectInspectors.appLeakingObjectFilters
),
referenceMatchers = AndroidReferenceMatchers.appDefaults
computeRetainedHeapSize = true,
objectInspectors = AndroidObjectInspectors.appDefaults,
metadataExtractor = AndroidMetadataExtractor,
)
println(heapAnalysis)
println("Let's do some custom heap analysis just for fun.")
println("#####")
println("String instance count: ${graph.findClassByName("java.lang.String")!!.instances.count()}")
println("#####")
val dexPaths =
graph.findClassByName("dalvik.system.BaseDexClassLoader")!!.instances.flatMap { classLoader ->
val pathList = classLoader["dalvik.system.BaseDexClassLoader", "pathList"]!!
.valueAsInstance!!
pathList["dalvik.system.DexPathList", "dexElements"]!!
.valueAsObjectArray!!.readElements()
.mapNotNull {
it.asObject?.asInstance
}.map { dexElement ->
val pathFile = dexElement["dalvik.system.DexPathList\$Element", "path"]!!
.valueAsInstance!!
pathFile["java.io.File", "path"]!!.valueAsInstance!!.readAsJavaString()
}
}
println("All dex paths:\n${dexPaths.joinToString("\n")}")
println("#####")
val generatedComponentClasses = graph.classes.filter { heapClass ->
val classSimpleName = heapClass.simpleName
classSimpleName.startsWith("Dagger") &&
classSimpleName.endsWith("Impl")
}
val componentInstances = generatedComponentClasses.flatMap { it.instances }
val allSingletons = componentInstances.flatMap { it.componentSingletons }
println(
"Found ${componentInstances.count()} Dagger component instances " +
"and ${allSingletons.count()} Dagger singleton instances in heap."
)
}
private val HeapInstance.componentSingletons: Sequence<Long>
get() = readFields().mapNotNull { componentField ->
val componentFieldInstance = componentField.valueAsInstance ?: return@mapNotNull null
val provider = if (componentFieldInstance instanceOf "dagger.internal.DelegateFactory") {
componentFieldInstance["dagger.internal.DelegateFactory", "delegate"]?.valueAsInstance
} else {
componentFieldInstance
}
if (provider == null || !(provider instanceOf "dagger.internal.DoubleCheck")) {
return@mapNotNull null
}
val singletonObjectId =
provider["dagger.internal.DoubleCheck", "instance"]!!.value.asNonNullObjectId!!
val doubleCheckUninitializedObjectId = graph.context.getOrPut("DoubleCheck.UNINITIALIZED") {
checkNotNull(graph.findClassByName("dagger.internal.DoubleCheck")) {
"The Dagger DoubleCheck class should always be in the classpath."
}["UNINITIALIZED"]!!.valueAsInstance!!.objectId
}
if (singletonObjectId != doubleCheckUninitializedObjectId) {
singletonObjectId
} else {
null
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment