From 1eb05da87723acb31c758afad9a8f24898fc4ae6 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 17:12:44 -0700 Subject: [PATCH 01/13] Remove deprecated useExperimentalLeakFinders --- .../api/leakcanary-android-core.api | 11 ++++------- .../src/main/java/leakcanary/LeakCanary.kt | 13 ------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api b/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api index f205c2421e..6a47889658 100644 --- a/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api +++ b/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api @@ -94,15 +94,14 @@ public final class leakcanary/LeakCanary { public final class leakcanary/LeakCanary$Config { public fun ()V - public fun (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZ)V - public synthetic fun (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)V + public synthetic fun (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z public final fun component10 ()Z public final fun component11 ()Lshark/LeakingObjectFinder; public final fun component12 ()Lleakcanary/HeapDumper; public final fun component13 ()Ljava/util/List; public final fun component14 ()Z - public final fun component15 ()Z public final fun component2 ()Z public final fun component3 ()I public final fun component4 ()Ljava/util/List; @@ -111,8 +110,8 @@ public final class leakcanary/LeakCanary$Config { public final fun component7 ()Lshark/MetadataExtractor; public final fun component8 ()Z public final fun component9 ()I - public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZ)Lleakcanary/LeakCanary$Config; - public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZILjava/lang/Object;)Lleakcanary/LeakCanary$Config; + public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)Lleakcanary/LeakCanary$Config; + public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILjava/lang/Object;)Lleakcanary/LeakCanary$Config; public fun equals (Ljava/lang/Object;)Z public final fun getComputeRetainedHeapSize ()Z public final fun getDumpHeap ()Z @@ -128,7 +127,6 @@ public final class leakcanary/LeakCanary$Config { public final fun getRequestWriteExternalStoragePermission ()Z public final fun getRetainedVisibleThreshold ()I public final fun getShowNotifications ()Z - public final fun getUseExperimentalLeakFinders ()Z public fun hashCode ()I public final fun newBuilder ()Lleakcanary/LeakCanary$Config$Builder; public fun toString ()Ljava/lang/String; @@ -150,7 +148,6 @@ public final class leakcanary/LeakCanary$Config$Builder { public final fun requestWriteExternalStoragePermission (Z)Lleakcanary/LeakCanary$Config$Builder; public final fun retainedVisibleThreshold (I)Lleakcanary/LeakCanary$Config$Builder; public final fun showNotifications (Z)Lleakcanary/LeakCanary$Config$Builder; - public final fun useExperimentalLeakFinders (Z)Lleakcanary/LeakCanary$Config$Builder; } public final class leakcanary/LogcatEventListener : leakcanary/EventListener { diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt index 96a27ef6bc..b6d3369502 100644 --- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt +++ b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt @@ -216,12 +216,6 @@ object LeakCanary { * Defaults to true. */ val showNotifications: Boolean = true, - - /** - * Deprecated: This is a no-op, set a custom [leakingObjectFinder] instead. - */ - @Deprecated("This is a no-op, set a custom leakingObjectFinder instead") - val useExperimentalLeakFinders: Boolean = false ) { /** @@ -264,7 +258,6 @@ object LeakCanary { private var leakingObjectFinder = config.leakingObjectFinder private var heapDumper = config.heapDumper private var eventListeners = config.eventListeners - private var useExperimentalLeakFinders = config.useExperimentalLeakFinders private var showNotifications = config.showNotifications /** @see [LeakCanary.Config.dumpHeap] */ @@ -320,11 +313,6 @@ object LeakCanary { fun eventListeners(eventListeners: List) = apply { this.eventListeners = eventListeners } - /** @see [LeakCanary.Config.useExperimentalLeakFinders] */ - @Deprecated("Set a custom leakingObjectFinder instead") - fun useExperimentalLeakFinders(useExperimentalLeakFinders: Boolean) = - apply { this.useExperimentalLeakFinders = useExperimentalLeakFinders } - /** @see [LeakCanary.Config.showNotifications] */ fun showNotifications(showNotifications: Boolean) = apply { this.showNotifications = showNotifications } @@ -344,7 +332,6 @@ object LeakCanary { leakingObjectFinder = leakingObjectFinder, heapDumper = heapDumper, eventListeners = eventListeners, - useExperimentalLeakFinders = useExperimentalLeakFinders, showNotifications = showNotifications, ) } From 53c1553634784c305af008360e337be4fa739692 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 17:17:53 -0700 Subject: [PATCH 02/13] Delete deprecated InstrumentationLeakDetector --- ...est.kt => AndroidDetectLeaksAssertTest.kt} | 4 +- .../leakcanary/InstrumentationLeakDetector.kt | 158 ------------------ .../src/main/java/shark/HprofHeapGraph.kt | 2 +- 3 files changed, 3 insertions(+), 161 deletions(-) rename leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/{InstrumentationLeakDetectorTest.kt => AndroidDetectLeaksAssertTest.kt} (86%) delete mode 100644 leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/InstrumentationLeakDetector.kt diff --git a/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/InstrumentationLeakDetectorTest.kt b/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/AndroidDetectLeaksAssertTest.kt similarity index 86% rename from leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/InstrumentationLeakDetectorTest.kt rename to leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/AndroidDetectLeaksAssertTest.kt index 6e0e93cf6c..44cb378459 100644 --- a/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/InstrumentationLeakDetectorTest.kt +++ b/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/AndroidDetectLeaksAssertTest.kt @@ -7,10 +7,10 @@ import org.junit.Test import java.util.Date /** - * Tests that the [InstrumentationLeakDetector] can detect leaks + * Tests that the [AndroidDetectLeaksAssert] can detect leaks * in instrumentation tests */ -class InstrumentationLeakDetectorTest { +class AndroidDetectLeaksAssertTest { @Before fun setUp() { AppWatcher.objectWatcher diff --git a/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/InstrumentationLeakDetector.kt b/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/InstrumentationLeakDetector.kt deleted file mode 100644 index 695baaa9be..0000000000 --- a/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/InstrumentationLeakDetector.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2018 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package leakcanary - -import android.os.SystemClock -import leakcanary.InstrumentationLeakDetector.Result.AnalysisPerformed -import leakcanary.InstrumentationLeakDetector.Result.NoAnalysis -import leakcanary.HeapAnalysisDecision.NoHeapAnalysis -import leakcanary.internal.InstrumentationHeapAnalyzer -import leakcanary.internal.InstrumentationHeapDumpFileProvider -import leakcanary.internal.RetryingHeapAnalyzer -import leakcanary.internal.friendly.measureDurationMillis -import org.junit.runner.notification.RunListener -import shark.HeapAnalysis -import shark.HeapAnalysisException -import shark.HeapAnalysisFailure -import shark.HeapAnalysisSuccess -import shark.SharkLog - -/** - * Deprecated: Use LeakAssertions instead - * - * [InstrumentationLeakDetector] can be used to detect memory leaks in instrumentation tests. - * - * To use it, you need to add an instrumentation test listener (e.g. [FailTestOnLeakRunListener]) - * that will invoke [detectLeaks]. - * - * ### Add an instrumentation test listener - * - * LeakCanary provides [FailTestOnLeakRunListener], but you can also implement - * your own [RunListener] and call [detectLeaks] directly if you need a more custom - * behavior (for instance running it only once per test suite). - * - * All you need to do is add the following to the defaultConfig of your build.gradle: - * - * `testInstrumentationRunnerArgument "listener", "leakcanary.FailTestOnLeakRunListener"` - * - * Then you can run your instrumentation tests via Gradle as usually, and they will fail when - * a memory leak is detected: - * - * `./gradlew leakcanary-sample:connectedCheck` - * - * If instead you want to run UI tests via adb, add a *listener* execution argument to - * your command line for running the UI tests: - * `-e listener leakcanary.FailTestOnLeakRunListener`. The full command line - * should look something like this: - * ```shell - * adb shell am instrument \\ - * -w com.android.foo/android.support.test.runner.AndroidJUnitRunner \\ - * -e listener leakcanary.FailTestOnLeakRunListener - * ``` - * - * ### Rationale - * Instead of using the [InstrumentationLeakDetector], one could simply enable LeakCanary in - * instrumentation tests. - * - * This approach would have two disadvantages: - * - * - Heap dumps freeze the VM, and the leak analysis is IO and CPU heavy. This can slow down - * the test and introduce flakiness - * - The leak analysis is asynchronous by default. This means the tests could finish and the - * process dies before the analysis is finished. - * - * The approach taken here is to collect all objects to watch as you run the test, but not - * do any heap dump during the test. Then, at the end, if any of the watched objects is still in - * memory we dump the heap and perform a blocking analysis. There is only one heap dump performed, - * no matter the number of objects retained. - */ -@Deprecated("Use LeakAssertions instead") -class InstrumentationLeakDetector { - - /** - * The result of calling [detectLeaks], which is either [NoAnalysis] or [AnalysisPerformed]. - */ - sealed class Result { - class NoAnalysis(val reason: String) : Result() - class AnalysisPerformed(val heapAnalysis: HeapAnalysis) : Result() - } - - /** - * Looks for retained objects, triggers a heap dump if needed and performs an analysis. - */ - @Suppress("ReturnCount") - fun detectLeaks(): Result { - val retainedObjectsChecker = AndroidDetectLeaksInterceptor() - val yesNo = retainedObjectsChecker.waitUntilReadyForHeapAnalysis() - if (yesNo is NoHeapAnalysis) { - return NoAnalysis(yesNo.reason) - } - - val heapDumpFile = InstrumentationHeapDumpFileProvider().newHeapDumpFile() - - val config = LeakCanary.config - - KeyedWeakReference.heapDumpUptimeMillis = SystemClock.uptimeMillis() - val heapDumpDurationMillis = try { - measureDurationMillis { - config.heapDumper.dumpHeap(heapDumpFile) - } - } catch (exception: Exception) { - SharkLog.d(exception) { "Could not dump heap" } - return AnalysisPerformed( - HeapAnalysisFailure( - heapDumpFile = heapDumpFile, - createdAtTimeMillis = System.currentTimeMillis(), - dumpDurationMillis = 0, - analysisDurationMillis = 0, - exception = HeapAnalysisException(exception) - ) - ) - } finally { - val heapDumpUptimeMillis = KeyedWeakReference.heapDumpUptimeMillis - AppWatcher.objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) - } - - val heapAnalyzer = RetryingHeapAnalyzer( - InstrumentationHeapAnalyzer( - leakingObjectFinder = config.leakingObjectFinder, - referenceMatchers = config.referenceMatchers, - computeRetainedHeapSize = config.computeRetainedHeapSize, - metadataExtractor = config.metadataExtractor, - objectInspectors = config.objectInspectors, - proguardMapping = null - ) - ) - - val heapAnalysis = heapAnalyzer.analyze(heapDumpFile).let { - when (it) { - is HeapAnalysisSuccess -> it.copy(dumpDurationMillis = heapDumpDurationMillis) - is HeapAnalysisFailure -> it.copy(dumpDurationMillis = heapDumpDurationMillis) - } - } - - return AnalysisPerformed(heapAnalysis) - } - - companion object { - - @Deprecated( - "This is a no-op as LeakCanary automatically detects tests", - replaceWith = ReplaceWith("") - ) - fun updateConfig() = Unit - } -} diff --git a/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt b/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt index eb4d18b0b3..808d73df40 100644 --- a/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt +++ b/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt @@ -402,7 +402,7 @@ class HprofHeapGraph internal constructor( * different cache size in tests in a different module. * * LRU cache size of 3000 is a sweet spot to balance hits vs memory usage. - * This is based on running InstrumentationLeakDetectorTest a bunch of time on a + * This is based on running an instrumented test a bunch of time on a * Pixel 2 XL API 28. Hit count was ~120K, miss count ~290K */ var INTERNAL_LRU_CACHE_SIZE = 3000 From 6d88c2497aa6581c4aa8ae2261632e6215dfb3c8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 17:19:38 -0700 Subject: [PATCH 03/13] Remove deprecated AppWatcher.Config --- .../src/main/java/leakcanary/AppWatcher.kt | 79 ------------------- .../test/java/leakcanary/AppWatcherTest.kt | 25 ------ 2 files changed, 104 deletions(-) diff --git a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt index cfb606ae76..9f86cc503a 100644 --- a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt +++ b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt @@ -136,83 +136,4 @@ object AppWatcher { ServiceWatcher(reachabilityWatcher) ) } - - @Deprecated("Call AppWatcher.manualInstall() ") - data class Config( - @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") - val watchActivities: Boolean = true, - - @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") - val watchFragments: Boolean = true, - - @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") - val watchFragmentViews: Boolean = true, - - @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") - val watchViewModels: Boolean = true, - - @Deprecated("Call AppWatcher.manualInstall() with a custom retainedDelayMillis value") - val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5), - - @Deprecated("Call AppWatcher.appDefaultWatchers() with a custom ReachabilityWatcher") - val enabled: Boolean = true - ) { - - @Deprecated("Configuration moved to AppWatcher.manualInstall()", replaceWith = ReplaceWith("")) - @Suppress("NEWER_VERSION_IN_SINCE_KOTLIN") - @SinceKotlin("999.9") // Hide from Kotlin code, this method is only for Java code - fun newBuilder(): Builder = Builder(this) - - @Deprecated("Configuration moved to XML resources") - class Builder internal constructor(config: Config) { - private var watchActivities = config.watchActivities - private var watchFragments = config.watchFragments - private var watchFragmentViews = config.watchFragmentViews - private var watchViewModels = config.watchViewModels - private var watchDurationMillis = config.watchDurationMillis - - /** Deprecated. @see [Config.enabled] */ - @Deprecated("see [Config.enabled]", replaceWith = ReplaceWith("")) - fun enabled(enabled: Boolean) = this - - /** @see [Config.watchActivities] */ - @Deprecated("see [Config.watchActivities]", replaceWith = ReplaceWith("")) - fun watchActivities(watchActivities: Boolean) = - apply { this.watchActivities = watchActivities } - - @Deprecated("see [Config.watchFragments]", replaceWith = ReplaceWith("")) - /** @see [Config.watchFragments] */ - fun watchFragments(watchFragments: Boolean) = - apply { this.watchFragments = watchFragments } - - @Deprecated("see [Config.watchFragmentViews]", replaceWith = ReplaceWith("")) - /** @see [Config.watchFragmentViews] */ - fun watchFragmentViews(watchFragmentViews: Boolean) = - apply { this.watchFragmentViews = watchFragmentViews } - - @Deprecated("see [Config.watchViewModels]", replaceWith = ReplaceWith("")) - /** @see [Config.watchViewModels] */ - fun watchViewModels(watchViewModels: Boolean) = - apply { this.watchViewModels = watchViewModels } - - @Deprecated("see [Config.watchDurationMillis]", replaceWith = ReplaceWith("")) - /** @see [Config.watchDurationMillis] */ - fun watchDurationMillis(watchDurationMillis: Long) = - apply { this.watchDurationMillis = watchDurationMillis } - - @Deprecated("Configuration moved to AppWatcher.manualInstall()") - fun build() = config.copy( - watchActivities = watchActivities, - watchFragments = watchFragments, - watchFragmentViews = watchFragmentViews, - watchViewModels = watchViewModels, - watchDurationMillis = watchDurationMillis - ) - } - } - - @Deprecated("Configuration moved to AppWatcher.manualInstall()") - @JvmStatic @Volatile - var config: Config = Config() - } diff --git a/object-watcher/object-watcher-android-core/src/test/java/leakcanary/AppWatcherTest.kt b/object-watcher/object-watcher-android-core/src/test/java/leakcanary/AppWatcherTest.kt index 16914c769c..1457005927 100644 --- a/object-watcher/object-watcher-android-core/src/test/java/leakcanary/AppWatcherTest.kt +++ b/object-watcher/object-watcher-android-core/src/test/java/leakcanary/AppWatcherTest.kt @@ -2,8 +2,6 @@ package leakcanary import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import kotlin.reflect.full.memberFunctions -import kotlin.reflect.full.memberProperties class AppWatcherTest { @@ -12,27 +10,4 @@ class AppWatcherTest { .describedAs("Ensure AppWatcher doesn't crash in JUnit tests") .isFalse() } - - /** - * Validates that each field in [AppWatcher.Config] has a matching builder function - * in [AppWatcher.Config.Builder] - */ - @Test fun `AppWatcher Config Builder matches AppWatcher Config`() { - assertThat(configProperties()) - .containsExactlyInAnyOrderElementsOf(configBuilderFunctions()) - } - - private fun configBuilderFunctions() = AppWatcher.Config.Builder::class.memberFunctions - .filter { member -> - member.annotations.none { it is Deprecated } - } - .map { it.name } - .subtract(setOf("build", "equals", "hashCode", "toString")) - - private fun configProperties() = AppWatcher.Config::class.memberProperties - .filter { member -> - // Ignore deprecated fields, we don't need builders for those - member.annotations.none { it is Deprecated } - } - .map { it.name } } From 4209b0f23e0bc79625d2c8b36c82c1393fa81afa Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 17:20:31 -0700 Subject: [PATCH 04/13] Remove deprecated ObjectWatcher.watch method --- .../src/main/java/leakcanary/ObjectWatcher.kt | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/ObjectWatcher.kt b/object-watcher/object-watcher/src/main/java/leakcanary/ObjectWatcher.kt index 2154a8b6ef..e33a4eb300 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/ObjectWatcher.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/ObjectWatcher.kt @@ -15,10 +15,10 @@ */ package leakcanary -import shark.SharkLog import java.lang.ref.ReferenceQueue import java.util.UUID import java.util.concurrent.Executor +import shark.SharkLog /** * [ObjectWatcher] can be passed objects to [watch]. It will create [KeyedWeakReference] instances @@ -109,33 +109,6 @@ class ObjectWatcher constructor( onObjectRetainedListeners.remove(listener) } - /** - * Identical to [watch] with an empty string reference name. - */ - @Deprecated( - "Add description parameter explaining why an object is watched to help understand leak traces.", - replaceWith = ReplaceWith( - "expectWeaklyReachable(watchedObject, \"Explain why this object should be garbage collected soon\")" - ) - ) - fun watch(watchedObject: Any) { - expectWeaklyReachable(watchedObject, "") - } - - @Deprecated( - "Method renamed expectWeaklyReachable() to clarify usage.", - replaceWith = ReplaceWith( - "expectWeaklyReachable(watchedObject, description)" - ) - ) - fun watch( - watchedObject: Any, - description: String - ) { - expectWeaklyReachable(watchedObject, description) - } - - @Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String From 9cb615e1c6036c29cc9fa080476d7bc7c4258712 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 22:12:28 -0700 Subject: [PATCH 05/13] Remove deprecated Hprof API methods --- .../leakcanary-android-instrumentation.api | 23 ------ .../java/leakcanary/IndexingTest.kt | 27 ++++--- .../api/object-watcher-android-core.api | 36 --------- .../object-watcher/api/object-watcher.api | 2 - .../java/shark/AndroidResourceIdNamesTest.kt | 15 ++-- .../src/test/java/shark/LegacyHprofTest.kt | 33 ++++---- shark/shark-graph/api/shark-graph.api | 2 - .../src/main/java/shark/HprofHeapGraph.kt | 77 +------------------ .../test/java/shark/HprofDeobfuscatorTest.kt | 8 +- shark/shark-hprof/api/shark-hprof.api | 37 --------- .../shark-hprof/src/main/java/shark/Hprof.kt | 59 -------------- .../src/main/java/shark/HprofReader.kt | 23 ------ .../src/main/java/shark/HprofWriter.kt | 32 -------- .../shark/HprofReaderPrimitiveArrayTest.kt | 22 +++--- 14 files changed, 56 insertions(+), 340 deletions(-) delete mode 100644 shark/shark-hprof/src/main/java/shark/Hprof.kt delete mode 100644 shark/shark-hprof/src/main/java/shark/HprofReader.kt diff --git a/leakcanary/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api b/leakcanary/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api index 83886cdf32..22280758ec 100644 --- a/leakcanary/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api +++ b/leakcanary/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api @@ -60,29 +60,6 @@ public abstract interface class leakcanary/HeapAnalysisReporter { public abstract fun reportHeapAnalysis (Lshark/HeapAnalysis;)V } -public final class leakcanary/InstrumentationLeakDetector { - public static final field Companion Lleakcanary/InstrumentationLeakDetector$Companion; - public fun ()V - public final fun detectLeaks ()Lleakcanary/InstrumentationLeakDetector$Result; -} - -public final class leakcanary/InstrumentationLeakDetector$Companion { - public final fun updateConfig ()V -} - -public abstract class leakcanary/InstrumentationLeakDetector$Result { -} - -public final class leakcanary/InstrumentationLeakDetector$Result$AnalysisPerformed : leakcanary/InstrumentationLeakDetector$Result { - public fun (Lshark/HeapAnalysis;)V - public final fun getHeapAnalysis ()Lshark/HeapAnalysis; -} - -public final class leakcanary/InstrumentationLeakDetector$Result$NoAnalysis : leakcanary/InstrumentationLeakDetector$Result { - public fun (Ljava/lang/String;)V - public final fun getReason ()Ljava/lang/String; -} - public final class leakcanary/LeakAssertions { public static final field INSTANCE Lleakcanary/LeakAssertions; public static final field NO_TAG Ljava/lang/String; diff --git a/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/IndexingTest.kt b/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/IndexingTest.kt index 7b03ad3c30..2504265297 100644 --- a/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/IndexingTest.kt +++ b/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/IndexingTest.kt @@ -1,13 +1,14 @@ package leakcanary -import android.os.SystemClock import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Test -import shark.Hprof -import shark.HprofHeapGraph -import shark.SharkLog import java.io.File import java.io.FileOutputStream +import leakcanary.internal.friendly.measureDurationMillis +import org.junit.Test +import shark.FileSourceProvider +import shark.HprofHeader +import shark.HprofIndex +import shark.SharkLog class IndexingTest { @@ -18,13 +19,17 @@ class IndexingTest { val heapDumpFile = File(context.filesDir, "AnalysisDurationTest.hprof") context.assets.open("large-dump.hprof").copyTo(FileOutputStream(heapDumpFile)) - Hprof.open(heapDumpFile).use { hprof -> - SharkLog.d { "Start indexing" } - val before = SystemClock.uptimeMillis() - HprofHeapGraph.indexHprof(hprof) - val durationMs = (SystemClock.uptimeMillis() - before) - SharkLog.d { "Indexing took $durationMs ms" } + val sourceProvider = FileSourceProvider(heapDumpFile) + val parsedHeaders = HprofHeader.parseHeaderOf(heapDumpFile) + + SharkLog.d { "Start indexing" } + val durationMs = measureDurationMillis { + HprofIndex.indexRecordsOf( + hprofSourceProvider = sourceProvider, + hprofHeader = parsedHeaders + ) } + SharkLog.d { "Indexing took $durationMs ms" } } } diff --git a/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api b/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api index 694db90607..d1bc338d3f 100644 --- a/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api +++ b/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api @@ -8,7 +8,6 @@ public final class leakcanary/AppWatcher { public static final field INSTANCE Lleakcanary/AppWatcher; public final fun appDefaultWatchers (Landroid/app/Application;Lleakcanary/ReachabilityWatcher;)Ljava/util/List; public static synthetic fun appDefaultWatchers$default (Lleakcanary/AppWatcher;Landroid/app/Application;Lleakcanary/ReachabilityWatcher;ILjava/lang/Object;)Ljava/util/List; - public static final fun getConfig ()Lleakcanary/AppWatcher$Config; public final fun getObjectWatcher ()Lleakcanary/ObjectWatcher; public final fun getRetainedDelayMillis ()J public final fun isInstalled ()Z @@ -16,41 +15,6 @@ public final class leakcanary/AppWatcher { public final fun manualInstall (Landroid/app/Application;J)V public final fun manualInstall (Landroid/app/Application;JLjava/util/List;)V public static synthetic fun manualInstall$default (Lleakcanary/AppWatcher;Landroid/app/Application;JLjava/util/List;ILjava/lang/Object;)V - public static final fun setConfig (Lleakcanary/AppWatcher$Config;)V -} - -public final class leakcanary/AppWatcher$Config { - public fun ()V - public fun (ZZZZJZ)V - public synthetic fun (ZZZZJZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()Z - public final fun component3 ()Z - public final fun component4 ()Z - public final fun component5 ()J - public final fun component6 ()Z - public final fun copy (ZZZZJZ)Lleakcanary/AppWatcher$Config; - public static synthetic fun copy$default (Lleakcanary/AppWatcher$Config;ZZZZJZILjava/lang/Object;)Lleakcanary/AppWatcher$Config; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnabled ()Z - public final fun getWatchActivities ()Z - public final fun getWatchDurationMillis ()J - public final fun getWatchFragmentViews ()Z - public final fun getWatchFragments ()Z - public final fun getWatchViewModels ()Z - public fun hashCode ()I - public final fun newBuilder ()Lleakcanary/AppWatcher$Config$Builder; - public fun toString ()Ljava/lang/String; -} - -public final class leakcanary/AppWatcher$Config$Builder { - public final fun build ()Lleakcanary/AppWatcher$Config; - public final fun enabled (Z)Lleakcanary/AppWatcher$Config$Builder; - public final fun watchActivities (Z)Lleakcanary/AppWatcher$Config$Builder; - public final fun watchDurationMillis (J)Lleakcanary/AppWatcher$Config$Builder; - public final fun watchFragmentViews (Z)Lleakcanary/AppWatcher$Config$Builder; - public final fun watchFragments (Z)Lleakcanary/AppWatcher$Config$Builder; - public final fun watchViewModels (Z)Lleakcanary/AppWatcher$Config$Builder; } public final class leakcanary/FragmentAndViewModelWatcher : leakcanary/InstallableWatcher { diff --git a/object-watcher/object-watcher/api/object-watcher.api b/object-watcher/object-watcher/api/object-watcher.api index b8a304bf2a..5406fbd86b 100644 --- a/object-watcher/object-watcher/api/object-watcher.api +++ b/object-watcher/object-watcher/api/object-watcher.api @@ -46,8 +46,6 @@ public final class leakcanary/ObjectWatcher : leakcanary/ReachabilityWatcher { public final fun getRetainedObjectCount ()I public final fun getRetainedObjects ()Ljava/util/List; public final fun removeOnObjectRetainedListener (Lleakcanary/OnObjectRetainedListener;)V - public final fun watch (Ljava/lang/Object;)V - public final fun watch (Ljava/lang/Object;Ljava/lang/String;)V } public abstract interface class leakcanary/OnObjectRetainedListener { diff --git a/shark/shark-android/src/test/java/shark/AndroidResourceIdNamesTest.kt b/shark/shark-android/src/test/java/shark/AndroidResourceIdNamesTest.kt index 60f9962a35..6443305341 100644 --- a/shark/shark-android/src/test/java/shark/AndroidResourceIdNamesTest.kt +++ b/shark/shark-android/src/test/java/shark/AndroidResourceIdNamesTest.kt @@ -4,6 +4,7 @@ import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever +import java.io.File import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -12,7 +13,7 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import shark.AndroidResourceIdNames.Companion.FIRST_APP_RESOURCE_ID import shark.AndroidResourceIdNames.Companion.RESOURCE_ID_TYPE_ITERATOR -import java.io.File +import shark.HprofHeapGraph.Companion.openHeapGraph class AndroidResourceIdNamesTest { @@ -136,11 +137,9 @@ class AndroidResourceIdNamesTest { val hprofFolder = testFolder.newFolder() val hprofFile = File(hprofFolder, "heapdump.hprof") JvmTestHeapDumper.dumpHeap(hprofFile.absolutePath) - Hprof.open(hprofFile) - .use { hprof -> - val graph = HprofHeapGraph.indexHprof(hprof) - val idNames = AndroidResourceIdNames.readFromHeap(graph) - block(idNames) - } + hprofFile.openHeapGraph().use {graph -> + val idNames = AndroidResourceIdNames.readFromHeap(graph) + block(idNames) + } } -} \ No newline at end of file +} diff --git a/shark/shark-android/src/test/java/shark/LegacyHprofTest.kt b/shark/shark-android/src/test/java/shark/LegacyHprofTest.kt index 867cd9228a..0a148b1725 100644 --- a/shark/shark-android/src/test/java/shark/LegacyHprofTest.kt +++ b/shark/shark-android/src/test/java/shark/LegacyHprofTest.kt @@ -101,9 +101,8 @@ class LegacyHprofTest { } @Test fun androidOCountActivityWrappingContexts() { - val contextWrapperStatuses = Hprof.open("leak_asynctask_o.hprof".classpathFile()) - .use { hprof -> - val graph = HprofHeapGraph.indexHprof(hprof) + val contextWrapperStatuses = "leak_asynctask_o.hprof".classpathFile() + .openHeapGraph().use { graph -> graph.instances.filter { it instanceOf "android.content.ContextWrapper" && !(it instanceOf "android.app.Activity") @@ -193,20 +192,24 @@ class LegacyHprofTest { val stickyClasses = mutableListOf() val classesAndNameStringId = mutableMapOf() val stringRecordById = mutableMapOf() - StreamingHprofReader.readerFor(file, header).readRecords(setOf(ROOT_STICKY_CLASS, STRING_IN_UTF8, LOAD_CLASS)) { tag, length, reader -> - when(tag) { - ROOT_STICKY_CLASS -> reader.readStickyClassGcRootRecord().apply { - stickyClasses += id - } - STRING_IN_UTF8 -> reader.readStringRecord(length).apply { - stringRecordById[id] = string - } - LOAD_CLASS -> reader.readLoadClassRecord().apply { - classesAndNameStringId[id] = classNameStringId + StreamingHprofReader.readerFor(file, header) + .readRecords(setOf(ROOT_STICKY_CLASS, STRING_IN_UTF8, LOAD_CLASS)) { tag, length, reader -> + when (tag) { + ROOT_STICKY_CLASS -> reader.readStickyClassGcRootRecord().apply { + stickyClasses += id + } + + STRING_IN_UTF8 -> reader.readStringRecord(length).apply { + stringRecordById[id] = string + } + + LOAD_CLASS -> reader.readLoadClassRecord().apply { + classesAndNameStringId[id] = classNameStringId + } + + else -> {} } - else -> {} } - } val duplicatedClassObjectIdsByNameStringId = classesAndNameStringId.entries .groupBy { (_, className) -> className } diff --git a/shark/shark-graph/api/shark-graph.api b/shark/shark-graph/api/shark-graph.api index 7325222d83..09b29ead70 100644 --- a/shark/shark-graph/api/shark-graph.api +++ b/shark/shark-graph/api/shark-graph.api @@ -200,8 +200,6 @@ public final class shark/HprofHeapGraph : shark/CloseableHeapGraph { public final class shark/HprofHeapGraph$Companion { public final fun getINTERNAL_LRU_CACHE_SIZE ()I - public final fun indexHprof (Lshark/Hprof;Lshark/ProguardMapping;Ljava/util/Set;)Lshark/HeapGraph; - public static synthetic fun indexHprof$default (Lshark/HprofHeapGraph$Companion;Lshark/Hprof;Lshark/ProguardMapping;Ljava/util/Set;ILjava/lang/Object;)Lshark/HeapGraph; public final fun openHeapGraph (Ljava/io/File;Lshark/ProguardMapping;Ljava/util/Set;)Lshark/CloseableHeapGraph; public final fun openHeapGraph (Lshark/DualSourceProvider;Lshark/ProguardMapping;Ljava/util/Set;)Lshark/CloseableHeapGraph; public static synthetic fun openHeapGraph$default (Lshark/HprofHeapGraph$Companion;Ljava/io/File;Lshark/ProguardMapping;Ljava/util/Set;ILjava/lang/Object;)Lshark/CloseableHeapGraph; diff --git a/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt b/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt index 808d73df40..a5127839c9 100644 --- a/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt +++ b/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt @@ -1,21 +1,6 @@ package shark -import shark.GcRoot.Debugger -import shark.GcRoot.Finalizing -import shark.GcRoot.InternedString -import shark.GcRoot.JavaFrame -import shark.GcRoot.JniGlobal -import shark.GcRoot.JniLocal -import shark.GcRoot.JniMonitor -import shark.GcRoot.MonitorUsed -import shark.GcRoot.NativeStack -import shark.GcRoot.ReferenceCleanup -import shark.GcRoot.StickyClass -import shark.GcRoot.ThreadBlock -import shark.GcRoot.ThreadObject -import shark.GcRoot.Unknown -import shark.GcRoot.Unreachable -import shark.GcRoot.VmInternal +import java.io.File import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray @@ -36,6 +21,8 @@ import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.In import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump import shark.HprofVersion.ANDROID +import shark.PrimitiveType.BYTE +import shark.PrimitiveType.INT import shark.internal.FieldValuesReader import shark.internal.HprofInMemoryIndex import shark.internal.IndexedObject @@ -44,10 +31,6 @@ import shark.internal.IndexedObject.IndexedInstance import shark.internal.IndexedObject.IndexedObjectArray import shark.internal.IndexedObject.IndexedPrimitiveArray import shark.internal.LruCache -import java.io.File -import kotlin.reflect.KClass -import shark.PrimitiveType.BYTE -import shark.PrimitiveType.INT /** * A [HeapGraph] that reads from an Hprof file indexed by [HprofIndex]. @@ -428,59 +411,5 @@ class HprofHeapGraph internal constructor( val index = HprofIndex.indexRecordsOf(this, header, proguardMapping, indexedGcRootTypes) return index.openHeapGraph() } - - @Deprecated( - "Replaced by HprofIndex.indexRecordsOf().openHeapGraph() or File.openHeapGraph()", - replaceWith = ReplaceWith( - "HprofIndex.indexRecordsOf(hprof, proguardMapping, indexedGcRootTypes)" + - ".openHeapGraph()" - ) - ) - fun indexHprof( - hprof: Hprof, - proguardMapping: ProguardMapping? = null, - indexedGcRootTypes: Set> = deprecatedDefaultIndexedGcRootTypes() - ): HeapGraph { - val indexedRootTags = indexedGcRootTypes.map { - when (it) { - Unknown::class -> HprofRecordTag.ROOT_UNKNOWN - JniGlobal::class -> HprofRecordTag.ROOT_JNI_GLOBAL - JniLocal::class -> HprofRecordTag.ROOT_JNI_LOCAL - JavaFrame::class -> HprofRecordTag.ROOT_JAVA_FRAME - NativeStack::class -> HprofRecordTag.ROOT_NATIVE_STACK - StickyClass::class -> HprofRecordTag.ROOT_STICKY_CLASS - ThreadBlock::class -> HprofRecordTag.ROOT_THREAD_BLOCK - MonitorUsed::class -> HprofRecordTag.ROOT_MONITOR_USED - ThreadObject::class -> HprofRecordTag.ROOT_THREAD_OBJECT - InternedString::class -> HprofRecordTag.ROOT_INTERNED_STRING - Finalizing::class -> HprofRecordTag.ROOT_FINALIZING - Debugger::class -> HprofRecordTag.ROOT_DEBUGGER - ReferenceCleanup::class -> HprofRecordTag.ROOT_REFERENCE_CLEANUP - VmInternal::class -> HprofRecordTag.ROOT_VM_INTERNAL - JniMonitor::class -> HprofRecordTag.ROOT_JNI_MONITOR - Unreachable::class -> HprofRecordTag.ROOT_UNREACHABLE - else -> error("Unknown root $it") - } - }.toSet() - val index = - HprofIndex.indexRecordsOf( - FileSourceProvider(hprof.file), hprof.header, proguardMapping, indexedRootTags - ) - val graph = index.openHeapGraph() - hprof.attachClosable(graph) - return graph - } - - private fun deprecatedDefaultIndexedGcRootTypes() = setOf( - JniGlobal::class, - JavaFrame::class, - JniLocal::class, - MonitorUsed::class, - NativeStack::class, - StickyClass::class, - ThreadBlock::class, - ThreadObject::class, - JniMonitor::class - ) } } diff --git a/shark/shark-graph/src/test/java/shark/HprofDeobfuscatorTest.kt b/shark/shark-graph/src/test/java/shark/HprofDeobfuscatorTest.kt index ea0e22c19d..5ebe98861b 100644 --- a/shark/shark-graph/src/test/java/shark/HprofDeobfuscatorTest.kt +++ b/shark/shark-graph/src/test/java/shark/HprofDeobfuscatorTest.kt @@ -7,6 +7,7 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import shark.ValueHolder.IntHolder import java.io.File +import shark.HprofHeapGraph.Companion.openHeapGraph class HprofDeobfuscatorTest { @@ -219,9 +220,6 @@ class HprofDeobfuscatorTest { } private fun File.readHprof(block: (HeapGraph) -> Unit) { - Hprof.open(this) - .use { hprof -> - block(HprofHeapGraph.indexHprof(hprof)) - } + openHeapGraph().use { block(it) } } -} \ No newline at end of file +} diff --git a/shark/shark-hprof/api/shark-hprof.api b/shark/shark-hprof/api/shark-hprof.api index 5acd199406..75f5720ff9 100644 --- a/shark/shark-hprof/api/shark-hprof.api +++ b/shark/shark-hprof/api/shark-hprof.api @@ -118,33 +118,6 @@ public final class shark/GcRoot$VmInternal : shark/GcRoot { public fun getId ()J } -public final class shark/Hprof : java/io/Closeable { - public static final field Companion Lshark/Hprof$Companion; - public synthetic fun (Ljava/io/File;Lshark/HprofHeader;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun attachClosable (Ljava/io/Closeable;)V - public fun close ()V - public final fun getFile ()Ljava/io/File; - public final fun getFileLength ()J - public final fun getHeader ()Lshark/HprofHeader; - public final fun getHeapDumpTimestamp ()J - public final fun getHprofVersion ()Lshark/Hprof$HprofVersion; - public final fun getReader ()Lshark/HprofReader; -} - -public final class shark/Hprof$Companion { - public final fun open (Ljava/io/File;)Lshark/Hprof; -} - -public final class shark/Hprof$HprofVersion : java/lang/Enum { - public static final field ANDROID Lshark/Hprof$HprofVersion; - public static final field JDK1_2_BETA3 Lshark/Hprof$HprofVersion; - public static final field JDK1_2_BETA4 Lshark/Hprof$HprofVersion; - public static final field JDK_6 Lshark/Hprof$HprofVersion; - public final fun getVersionString ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lshark/Hprof$HprofVersion; - public static fun values ()[Lshark/Hprof$HprofVersion; -} - public final class shark/HprofDeobfuscator { public fun ()V public final fun deobfuscate (Lshark/ProguardMapping;Ljava/io/File;Ljava/io/File;)Ljava/io/File; @@ -182,12 +155,6 @@ public final class shark/HprofPrimitiveArrayStripper { public static synthetic fun stripPrimitiveArrays$default (Lshark/HprofPrimitiveArrayStripper;Ljava/io/File;Ljava/io/File;ILjava/lang/Object;)Ljava/io/File; } -public final class shark/HprofReader { - public final fun getIdentifierByteSize ()I - public final fun getStartPosition ()J - public final fun readHprofRecords (Ljava/util/Set;Lshark/OnHprofRecordListener;)V -} - public abstract class shark/HprofRecord { } @@ -502,15 +469,11 @@ public final class shark/HprofWriter : java/io/Closeable { public synthetic fun (Lokio/BufferedSink;Lshark/HprofHeader;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close ()V public final fun getHprofHeader ()Lshark/HprofHeader; - public final fun getHprofVersion ()Lshark/Hprof$HprofVersion; - public final fun getIdentifierByteSize ()I public final fun valuesToBytes (Ljava/util/List;)[B public final fun write (Lshark/HprofRecord;)V } public final class shark/HprofWriter$Companion { - public final fun open (Ljava/io/File;ILshark/Hprof$HprofVersion;)Lshark/HprofWriter; - public static synthetic fun open$default (Lshark/HprofWriter$Companion;Ljava/io/File;ILshark/Hprof$HprofVersion;ILjava/lang/Object;)Lshark/HprofWriter; public final fun openWriterFor (Ljava/io/File;Lshark/HprofHeader;)Lshark/HprofWriter; public final fun openWriterFor (Lokio/BufferedSink;Lshark/HprofHeader;)Lshark/HprofWriter; public static synthetic fun openWriterFor$default (Lshark/HprofWriter$Companion;Ljava/io/File;Lshark/HprofHeader;ILjava/lang/Object;)Lshark/HprofWriter; diff --git a/shark/shark-hprof/src/main/java/shark/Hprof.kt b/shark/shark-hprof/src/main/java/shark/Hprof.kt deleted file mode 100644 index 8263b7306e..0000000000 --- a/shark/shark-hprof/src/main/java/shark/Hprof.kt +++ /dev/null @@ -1,59 +0,0 @@ -package shark - -import java.io.Closeable -import java.io.File - -/** - * Hprof is deprecated, and we offer partial backward compatibility. Any code that was - * previously using HprofReader directly now has to call [StreamingHprofReader.readerFor] or - * [HprofRandomAcccessReader.readerFor] - */ -@Deprecated("Replaced by HprofStreamingReader.readerFor or HprofRandomAccessReader.openReaderFor") -class Hprof private constructor( - val file: File, - val header: HprofHeader -) : Closeable { - - val reader: HprofReader = HprofReader(this) - - val heapDumpTimestamp: Long - get() = header.heapDumpTimestamp - - val hprofVersion: HprofVersion - get() = HprofVersion.valueOf(header.version.name) - - val fileLength: Long - get() = file.length() - - private val closeables = mutableListOf() - - /** - * Maintains backward compatibility because [Hprof.open] returns a closeable. This allows - * consuming libraries to attach a closeable that will be closed whe [Hprof] is closed. - */ - fun attachClosable(closeable: Closeable) { - closeables += closeable - } - - override fun close() { - closeables.forEach { it.close() } - } - - @Deprecated(message = "Moved to top level class", replaceWith = ReplaceWith("shark.HprofVersion")) - enum class HprofVersion { - JDK1_2_BETA3, - JDK1_2_BETA4, - JDK_6, - ANDROID; - - val versionString: String - get() = shark.HprofVersion.valueOf(name).versionString - } - - companion object { - @Deprecated( - message = "Replaced by HprofStreamingReader.readerFor or HprofRandomAccessReader.openReaderFor" - ) - fun open(hprofFile: File): Hprof = Hprof(hprofFile, HprofHeader.parseHeaderOf(hprofFile)) - } -} \ No newline at end of file diff --git a/shark/shark-hprof/src/main/java/shark/HprofReader.kt b/shark/shark-hprof/src/main/java/shark/HprofReader.kt deleted file mode 100644 index f32a0c1fa8..0000000000 --- a/shark/shark-hprof/src/main/java/shark/HprofReader.kt +++ /dev/null @@ -1,23 +0,0 @@ -package shark - -import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader -import kotlin.reflect.KClass - -@Deprecated("Replaced by HprofStreamingReader.readerFor or HprofRandomAccessReader.openReaderFor") -class HprofReader internal constructor( - private val hprof: Hprof -) { - val identifierByteSize: Int - get() = hprof.header.identifierByteSize - - val startPosition: Long - get() = hprof.header.recordsPosition.toLong() - - fun readHprofRecords( - recordTypes: Set>, - listener: OnHprofRecordListener - ) { - val reader = StreamingHprofReader.readerFor(hprof.file, hprof.header).asStreamingRecordReader() - reader.readRecords(recordTypes, listener) - } -} \ No newline at end of file diff --git a/shark/shark-hprof/src/main/java/shark/HprofWriter.kt b/shark/shark-hprof/src/main/java/shark/HprofWriter.kt index 6e13dc4bab..11187af45c 100644 --- a/shark/shark-hprof/src/main/java/shark/HprofWriter.kt +++ b/shark/shark-hprof/src/main/java/shark/HprofWriter.kt @@ -94,20 +94,6 @@ class HprofWriter private constructor( val hprofHeader: HprofHeader ) : Closeable { - @Deprecated( - "Replaced by HprofWriter.hprofHeader.identifierByteSize", - ReplaceWith("hprofHeader.identifierByteSize") - ) - val identifierByteSize: Int - get() = hprofHeader.identifierByteSize - - @Deprecated( - "Replaced by HprofWriter.hprofHeader.version", - ReplaceWith("hprofHeader.version") - ) - val hprofVersion: Hprof.HprofVersion - get() = Hprof.HprofVersion.valueOf(hprofHeader.version.name) - private val workBuffer = Buffer() /** @@ -480,23 +466,5 @@ class HprofWriter private constructor( hprofSink.writeLong(hprofHeader.heapDumpTimestamp) return HprofWriter(hprofSink, hprofHeader) } - - @Deprecated( - "Replaced by HprofWriter.openWriterFor()", - ReplaceWith( - "shark.HprofWriter.openWriterFor(hprofFile)" - ) - ) - fun open( - hprofFile: File, - identifierByteSize: Int = 4, - hprofVersion: Hprof.HprofVersion = Hprof.HprofVersion.ANDROID - ): HprofWriter = openWriterFor( - hprofFile, - HprofHeader( - version = HprofVersion.valueOf(hprofVersion.name), - identifierByteSize = identifierByteSize - ) - ) } } diff --git a/shark/shark-hprof/src/test/java/shark/HprofReaderPrimitiveArrayTest.kt b/shark/shark-hprof/src/test/java/shark/HprofReaderPrimitiveArrayTest.kt index 93743583cc..c2a9e4d5a7 100644 --- a/shark/shark-hprof/src/test/java/shark/HprofReaderPrimitiveArrayTest.kt +++ b/shark/shark-hprof/src/test/java/shark/HprofReaderPrimitiveArrayTest.kt @@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test import kotlin.text.Charsets.UTF_8 +import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader class HprofReaderPrimitiveArrayTest { @@ -14,11 +15,8 @@ class HprofReaderPrimitiveArrayTest { fun skips_primitive_arrays_correctly() { val heapDump = heapDumpRule.dumpHeap() - Hprof.open(heapDump).use { hprof -> - hprof.reader.readHprofRecords( - emptySet() - ) // skip everything including primitive arrays - { _, _ -> } + StreamingHprofReader.readerFor(heapDump).readRecords(emptySet()) { _, _, _ -> + error("Should skip all records, including primitive arrays") } } @@ -31,14 +29,12 @@ class HprofReaderPrimitiveArrayTest { val heapDump = heapDumpRule.dumpHeap() var myByteArrayIsInHeapDump = false - Hprof.open(heapDump).use { hprof -> - hprof.reader.readHprofRecords( - setOf(HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord::class) - ) { _, record -> - if (record is HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump) { - if (byteArray.contentEquals(record.array)) { - myByteArrayIsInHeapDump = true - } + + val reader = StreamingHprofReader.readerFor(heapDump).asStreamingRecordReader() + reader.readRecords(setOf(HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord::class)) { _, record -> + if (record is HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump) { + if (byteArray.contentEquals(record.array)) { + myByteArrayIsInHeapDump = true } } } From e6ad419f89b10180378799b8b31c2f72990899c0 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 22:16:39 -0700 Subject: [PATCH 06/13] Remove deprecated DefaultOnHeapAnalyzedListener --- .../api/leakcanary-android-core.api | 37 ++++++------------- .../DefaultOnHeapAnalyzedListener.kt | 26 ------------- .../src/main/java/leakcanary/LeakCanary.kt | 18 --------- .../internal/AndroidDebugHeapAnalyzer.kt | 1 - 4 files changed, 12 insertions(+), 70 deletions(-) delete mode 100644 leakcanary/leakcanary-android-core/src/main/java/leakcanary/DefaultOnHeapAnalyzedListener.kt diff --git a/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api b/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api index 6a47889658..8ca58c12d0 100644 --- a/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api +++ b/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api @@ -17,16 +17,6 @@ public final class leakcanary/BackgroundThreadHeapAnalyzer : leakcanary/EventLis public fun onEvent (Lleakcanary/EventListener$Event;)V } -public final class leakcanary/DefaultOnHeapAnalyzedListener : leakcanary/OnHeapAnalyzedListener { - public static final field Companion Lleakcanary/DefaultOnHeapAnalyzedListener$Companion; - public fun (Landroid/app/Application;)V - public fun onHeapAnalyzed (Lshark/HeapAnalysis;)V -} - -public final class leakcanary/DefaultOnHeapAnalyzedListener$Companion { - public final fun create ()Lleakcanary/OnHeapAnalyzedListener; -} - public abstract interface class leakcanary/EventListener { public abstract fun onEvent (Lleakcanary/EventListener$Event;)V } @@ -94,24 +84,23 @@ public final class leakcanary/LeakCanary { public final class leakcanary/LeakCanary$Config { public fun ()V - public fun (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)V - public synthetic fun (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZZILjava/util/List;Ljava/util/List;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)V + public synthetic fun (ZZILjava/util/List;Ljava/util/List;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z - public final fun component10 ()Z - public final fun component11 ()Lshark/LeakingObjectFinder; - public final fun component12 ()Lleakcanary/HeapDumper; - public final fun component13 ()Ljava/util/List; - public final fun component14 ()Z + public final fun component10 ()Lshark/LeakingObjectFinder; + public final fun component11 ()Lleakcanary/HeapDumper; + public final fun component12 ()Ljava/util/List; + public final fun component13 ()Z public final fun component2 ()Z public final fun component3 ()I public final fun component4 ()Ljava/util/List; public final fun component5 ()Ljava/util/List; - public final fun component6 ()Lleakcanary/OnHeapAnalyzedListener; - public final fun component7 ()Lshark/MetadataExtractor; - public final fun component8 ()Z - public final fun component9 ()I - public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)Lleakcanary/LeakCanary$Config; - public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILjava/lang/Object;)Lleakcanary/LeakCanary$Config; + public final fun component6 ()Lshark/MetadataExtractor; + public final fun component7 ()Z + public final fun component8 ()I + public final fun component9 ()Z + public final fun copy (ZZILjava/util/List;Ljava/util/List;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)Lleakcanary/LeakCanary$Config; + public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILjava/lang/Object;)Lleakcanary/LeakCanary$Config; public fun equals (Ljava/lang/Object;)Z public final fun getComputeRetainedHeapSize ()Z public final fun getDumpHeap ()Z @@ -122,7 +111,6 @@ public final class leakcanary/LeakCanary$Config { public final fun getMaxStoredHeapDumps ()I public final fun getMetadataExtractor ()Lshark/MetadataExtractor; public final fun getObjectInspectors ()Ljava/util/List; - public final fun getOnHeapAnalyzedListener ()Lleakcanary/OnHeapAnalyzedListener; public final fun getReferenceMatchers ()Ljava/util/List; public final fun getRequestWriteExternalStoragePermission ()Z public final fun getRetainedVisibleThreshold ()I @@ -143,7 +131,6 @@ public final class leakcanary/LeakCanary$Config$Builder { public final fun maxStoredHeapDumps (I)Lleakcanary/LeakCanary$Config$Builder; public final fun metadataExtractor (Lshark/MetadataExtractor;)Lleakcanary/LeakCanary$Config$Builder; public final fun objectInspectors (Ljava/util/List;)Lleakcanary/LeakCanary$Config$Builder; - public final fun onHeapAnalyzedListener (Lleakcanary/OnHeapAnalyzedListener;)Lleakcanary/LeakCanary$Config$Builder; public final fun referenceMatchers (Ljava/util/List;)Lleakcanary/LeakCanary$Config$Builder; public final fun requestWriteExternalStoragePermission (Z)Lleakcanary/LeakCanary$Config$Builder; public final fun retainedVisibleThreshold (I)Lleakcanary/LeakCanary$Config$Builder; diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/DefaultOnHeapAnalyzedListener.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/DefaultOnHeapAnalyzedListener.kt deleted file mode 100644 index 57b32c500c..0000000000 --- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/DefaultOnHeapAnalyzedListener.kt +++ /dev/null @@ -1,26 +0,0 @@ -package leakcanary - -import android.app.Application -import shark.HeapAnalysis - -/** - * Deprecated, this is now a no-op. Add to LeakCanary.config.eventListeners instead. - * - * Default [OnHeapAnalyzedListener] implementation, which will store the analysis to disk and - * show a notification summarizing the result. - */ -@Deprecated(message = "Add to LeakCanary.config.eventListeners instead") -class DefaultOnHeapAnalyzedListener private constructor() : - OnHeapAnalyzedListener { - - // Kept this constructor for backward compatibility of public API. - @Deprecated(message = "Add to LeakCanary.config.eventListeners instead") - constructor(application: Application) : this() - - override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) { - } - - companion object { - fun create(): OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener() - } -} diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt index b6d3369502..f5fc7eb3a3 100644 --- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt +++ b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt @@ -92,17 +92,6 @@ object LeakCanary { */ val objectInspectors: List = AndroidObjectInspectors.appDefaults, - /** - * Deprecated, add to LeakCanary.config.eventListeners instead. - * Called on a background thread when the heap analysis is complete. - * If you want leaks to be added to the activity that lists leaks, make sure to delegate - * calls to a [DefaultOnHeapAnalyzedListener]. - * - * Defaults to [DefaultOnHeapAnalyzedListener] - */ - @Deprecated(message = "Add to LeakCanary.config.eventListeners instead") - val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(), - /** * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata]. * Called on a background thread during heap analysis. @@ -249,7 +238,6 @@ object LeakCanary { private var retainedVisibleThreshold = config.retainedVisibleThreshold private var referenceMatchers = config.referenceMatchers private var objectInspectors = config.objectInspectors - private var onHeapAnalyzedListener = config.onHeapAnalyzedListener private var metadataExtractor = config.metadataExtractor private var computeRetainedHeapSize = config.computeRetainedHeapSize private var maxStoredHeapDumps = config.maxStoredHeapDumps @@ -280,11 +268,6 @@ object LeakCanary { fun objectInspectors(objectInspectors: List) = apply { this.objectInspectors = objectInspectors } - /** @see [LeakCanary.Config.onHeapAnalyzedListener] */ - @Deprecated(message = "Add to LeakCanary.config.eventListeners instead") - fun onHeapAnalyzedListener(onHeapAnalyzedListener: OnHeapAnalyzedListener) = - apply { this.onHeapAnalyzedListener = onHeapAnalyzedListener } - /** @see [LeakCanary.Config.metadataExtractor] */ fun metadataExtractor(metadataExtractor: MetadataExtractor) = apply { this.metadataExtractor = metadataExtractor } @@ -324,7 +307,6 @@ object LeakCanary { retainedVisibleThreshold = retainedVisibleThreshold, referenceMatchers = referenceMatchers, objectInspectors = objectInspectors, - onHeapAnalyzedListener = onHeapAnalyzedListener, metadataExtractor = metadataExtractor, computeRetainedHeapSize = computeRetainedHeapSize, maxStoredHeapDumps = maxStoredHeapDumps, diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/AndroidDebugHeapAnalyzer.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/AndroidDebugHeapAnalyzer.kt index 4c9769c8d7..9446064915 100644 --- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/AndroidDebugHeapAnalyzer.kt +++ b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/AndroidDebugHeapAnalyzer.kt @@ -115,7 +115,6 @@ internal object AndroidDebugHeapAnalyzer { } } } - LeakCanary.config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis) return analysisDoneEvent } From 95f6f9dd0b0063e5f90d7dddc33d80d54faadab3 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 22:17:19 -0700 Subject: [PATCH 07/13] Removed deprecated ObjectWatcher.likelyLeakingReasons --- shark/shark/api/shark.api | 1 - shark/shark/src/main/java/shark/ObjectReporter.kt | 14 +------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/shark/shark/api/shark.api b/shark/shark/api/shark.api index fc1127131f..d00f0c6265 100644 --- a/shark/shark/api/shark.api +++ b/shark/shark/api/shark.api @@ -349,7 +349,6 @@ public final class shark/ObjectReporter { public final fun getHeapObject ()Lshark/HeapObject; public final fun getLabels ()Ljava/util/LinkedHashSet; public final fun getLeakingReasons ()Ljava/util/Set; - public final fun getLikelyLeakingReasons ()Ljava/util/Set; public final fun getNotLeakingReasons ()Ljava/util/Set; public final fun whenInstanceOf (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V public final fun whenInstanceOf (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)V diff --git a/shark/shark/src/main/java/shark/ObjectReporter.kt b/shark/shark/src/main/java/shark/ObjectReporter.kt index f562a5df54..9068e7decc 100644 --- a/shark/shark/src/main/java/shark/ObjectReporter.kt +++ b/shark/shark/src/main/java/shark/ObjectReporter.kt @@ -22,18 +22,6 @@ class ObjectReporter constructor(val heapObject: HeapObject) { */ val leakingReasons = mutableSetOf() - /** - * Deprecated, use leakingReasons instead. - */ - @Deprecated( - "Replace likelyLeakingReasons with leakingReasons", - replaceWith = ReplaceWith( - "leakingReasons" - ) - ) - val likelyLeakingReasons - get() = leakingReasons - /** * Reasons for which this object is expected to be reachable (ie it's not leaking). */ @@ -61,4 +49,4 @@ class ObjectReporter constructor(val heapObject: HeapObject) { block(heapObject) } } -} \ No newline at end of file +} From 5645e2fac9e036538409143e5fe2821457ac487e Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 22:18:49 -0700 Subject: [PATCH 08/13] remove deprecated HeapObject.readByteSize --- shark/shark-graph/api/shark-graph.api | 1 - shark/shark-graph/src/main/java/shark/HeapObject.kt | 12 ++++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/shark/shark-graph/api/shark-graph.api b/shark/shark-graph/api/shark-graph.api index 09b29ead70..bc8adc362c 100644 --- a/shark/shark-graph/api/shark-graph.api +++ b/shark/shark-graph/api/shark-graph.api @@ -130,7 +130,6 @@ public final class shark/HeapObject$HeapObjectArray : shark/HeapObject { public fun getObjectId ()J public fun getObjectIndex ()I public fun getRecordSize ()I - public final fun readByteSize ()I public final fun readElements ()Lkotlin/sequences/Sequence; public fun readRecord ()Lshark/HprofRecord$HeapDumpRecord$ObjectRecord$ObjectArrayDumpRecord; public synthetic fun readRecord ()Lshark/HprofRecord$HeapDumpRecord$ObjectRecord; diff --git a/shark/shark-graph/src/main/java/shark/HeapObject.kt b/shark/shark-graph/src/main/java/shark/HeapObject.kt index 4e1f779303..8551c988ab 100644 --- a/shark/shark-graph/src/main/java/shark/HeapObject.kt +++ b/shark/shark-graph/src/main/java/shark/HeapObject.kt @@ -1,5 +1,9 @@ package shark +import java.nio.charset.Charset +import java.util.Locale +import kotlin.LazyThreadSafetyMode.NONE +import kotlin.reflect.KClass import shark.HprofRecord.HeapDumpRecord.ObjectRecord import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord @@ -13,11 +17,6 @@ import shark.internal.IndexedObject.IndexedClass import shark.internal.IndexedObject.IndexedInstance import shark.internal.IndexedObject.IndexedObjectArray import shark.internal.IndexedObject.IndexedPrimitiveArray -import java.nio.charset.Charset -import java.util.Locale -import kotlin.LazyThreadSafetyMode.NONE -import kotlin.reflect.KClass -import shark.PrimitiveType.INT /** * An object in the heap dump. @@ -558,9 +557,6 @@ sealed class HeapObject { get() = indexedObject.arrayClassId - @Deprecated("Use byteSize property instead", ReplaceWith("byteSize")) - fun readByteSize() = byteSize - /** * The total byte shallow size of elements in this array. */ From f5361fe8a73f4f3d732b81f2e10a5f5e062da625 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 22:19:48 -0700 Subject: [PATCH 09/13] Remove deprecated OnHeapAnalyzedListener --- .../api/leakcanary-android-core.api | 9 ----- .../java/leakcanary/OnHeapAnalyzedListener.kt | 33 ------------------- .../src/main/java/shark/ObjectInspector.kt | 2 +- 3 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 leakcanary/leakcanary-android-core/src/main/java/leakcanary/OnHeapAnalyzedListener.kt diff --git a/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api b/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api index 8ca58c12d0..c160056f4a 100644 --- a/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api +++ b/leakcanary/leakcanary-android-core/api/leakcanary-android-core.api @@ -147,15 +147,6 @@ public final class leakcanary/NotificationEventListener : leakcanary/EventListen public fun onEvent (Lleakcanary/EventListener$Event;)V } -public abstract interface class leakcanary/OnHeapAnalyzedListener { - public static final field Companion Lleakcanary/OnHeapAnalyzedListener$Companion; - public abstract fun onHeapAnalyzed (Lshark/HeapAnalysis;)V -} - -public final class leakcanary/OnHeapAnalyzedListener$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lleakcanary/OnHeapAnalyzedListener; -} - public final class leakcanary/RemoteWorkManagerHeapAnalyzer : leakcanary/EventListener { public static final field INSTANCE Lleakcanary/RemoteWorkManagerHeapAnalyzer; public fun onEvent (Lleakcanary/EventListener$Event;)V diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/OnHeapAnalyzedListener.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/OnHeapAnalyzedListener.kt deleted file mode 100644 index 3cfa095502..0000000000 --- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/OnHeapAnalyzedListener.kt +++ /dev/null @@ -1,33 +0,0 @@ -package leakcanary - -import shark.HeapAnalysis - -/** - * Deprecated, add to LeakCanary.config.eventListeners instead. - * Called after [leakcanary.EventListener.Event.HeapAnalysisDone]. - */ -@Deprecated(message = "Add to LeakCanary.config.eventListeners instead") -fun interface OnHeapAnalyzedListener { - - /** - * @see OnHeapAnalyzedListener - */ - fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) - - companion object { - /** - * Utility function to create a [OnHeapAnalyzedListener] from the passed in [block] lambda - * instead of using the anonymous `object : OnHeapAnalyzedListener` syntax. - * - * Usage: - * - * ```kotlin - * val listener = OnHeapAnalyzedListener { - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (HeapAnalysis) -> Unit): OnHeapAnalyzedListener = - OnHeapAnalyzedListener { heapAnalysis -> block(heapAnalysis) } - } -} diff --git a/shark/shark/src/main/java/shark/ObjectInspector.kt b/shark/shark/src/main/java/shark/ObjectInspector.kt index 5bb30e3e68..c04916f75b 100644 --- a/shark/shark/src/main/java/shark/ObjectInspector.kt +++ b/shark/shark/src/main/java/shark/ObjectInspector.kt @@ -18,7 +18,7 @@ fun interface ObjectInspector { companion object { /** * Utility function to create a [ObjectInspector] from the passed in [block] lambda instead of - * using the anonymous `object : OnHeapAnalyzedListener` syntax. + * using the anonymous `object : ObjectInspector` syntax. * * Usage: * From b6a8398590bcb0e81f3feb8956917da0a737ea7a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Fri, 19 May 2023 22:25:26 -0700 Subject: [PATCH 10/13] Remove companion invokes that existed prior to fun interfaces --- .../object-watcher/api/object-watcher.api | 10 ---------- .../src/main/java/leakcanary/Clock.kt | 17 ----------------- .../leakcanary/OnObjectRetainedListener.kt | 17 ----------------- shark/shark-hprof/api/shark-hprof.api | 10 ---------- .../main/java/shark/OnHprofRecordListener.kt | 17 ----------------- .../java/shark/OnHprofRecordTagListener.kt | 17 ----------------- shark/shark/api/shark.api | 12 ------------ .../main/java/shark/LeakingObjectFinder.kt | 17 ----------------- .../src/main/java/shark/MetadataExtractor.kt | 15 --------------- .../src/main/java/shark/ObjectInspector.kt | 17 ----------------- .../java/shark/OnAnalysisProgressListener.kt | 19 ++----------------- 11 files changed, 2 insertions(+), 166 deletions(-) diff --git a/object-watcher/object-watcher/api/object-watcher.api b/object-watcher/object-watcher/api/object-watcher.api index 5406fbd86b..79780e537f 100644 --- a/object-watcher/object-watcher/api/object-watcher.api +++ b/object-watcher/object-watcher/api/object-watcher.api @@ -1,12 +1,7 @@ public abstract interface class leakcanary/Clock { - public static final field Companion Lleakcanary/Clock$Companion; public abstract fun uptimeMillis ()J } -public final class leakcanary/Clock$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function0;)Lleakcanary/Clock; -} - public abstract interface class leakcanary/GcTrigger { public abstract fun runGc ()V } @@ -49,14 +44,9 @@ public final class leakcanary/ObjectWatcher : leakcanary/ReachabilityWatcher { } public abstract interface class leakcanary/OnObjectRetainedListener { - public static final field Companion Lleakcanary/OnObjectRetainedListener$Companion; public abstract fun onObjectRetained ()V } -public final class leakcanary/OnObjectRetainedListener$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function0;)Lleakcanary/OnObjectRetainedListener; -} - public abstract interface class leakcanary/ReachabilityWatcher { public abstract fun expectWeaklyReachable (Ljava/lang/Object;Ljava/lang/String;)V } diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt b/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt index 8d071f984e..6c73eb6dc8 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt @@ -10,21 +10,4 @@ fun interface Clock { * On Android VMs, this should return android.os.SystemClock.uptimeMillis(). */ fun uptimeMillis(): Long - - companion object { - /** - * Utility function to create a [Clock] from the passed in [block] lambda - * instead of using the anonymous `object : Clock` syntax. - * - * Usage: - * - * ```kotlin - * val clock = Clock { - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: () -> Long): Clock = - Clock { block() } - } } diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt b/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt index 0e5b548b70..bb39e61b49 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt @@ -11,21 +11,4 @@ fun interface OnObjectRetainedListener { * A watched object became retained. */ fun onObjectRetained() - - companion object { - /** - * Utility function to create a [OnObjectRetainedListener] from the passed in [block] lambda - * instead of using the anonymous `object : OnObjectRetainedListener` syntax. - * - * Usage: - * - * ```kotlin - * val listener = OnObjectRetainedListener { - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: () -> Unit): OnObjectRetainedListener = - OnObjectRetainedListener { block() } - } } diff --git a/shark/shark-hprof/api/shark-hprof.api b/shark/shark-hprof/api/shark-hprof.api index 75f5720ff9..cd119ccbf5 100644 --- a/shark/shark-hprof/api/shark-hprof.api +++ b/shark/shark-hprof/api/shark-hprof.api @@ -481,23 +481,13 @@ public final class shark/HprofWriter$Companion { } public abstract interface class shark/OnHprofRecordListener { - public static final field Companion Lshark/OnHprofRecordListener$Companion; public abstract fun onHprofRecord (JLshark/HprofRecord;)V } -public final class shark/OnHprofRecordListener$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function2;)Lshark/OnHprofRecordListener; -} - public abstract interface class shark/OnHprofRecordTagListener { - public static final field Companion Lshark/OnHprofRecordTagListener$Companion; public abstract fun onHprofRecord (Lshark/HprofRecordTag;JLshark/HprofRecordReader;)V } -public final class shark/OnHprofRecordTagListener$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function3;)Lshark/OnHprofRecordTagListener; -} - public final class shark/PrimitiveType : java/lang/Enum { public static final field BOOLEAN Lshark/PrimitiveType; public static final field BYTE Lshark/PrimitiveType; diff --git a/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt b/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt index 24b6e1e128..58dab023e8 100644 --- a/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt +++ b/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt @@ -15,21 +15,4 @@ fun interface OnHprofRecordListener { position: Long, record: HprofRecord ) - - companion object { - /** - * Utility function to create a [OnHprofRecordListener] from the passed in [block] lambda - * instead of using the anonymous `object : OnHprofRecordListener` syntax. - * - * Usage: - * - * ```kotlin - * val listener = OnHprofRecordListener { position, record -> - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (Long, HprofRecord) -> Unit): OnHprofRecordListener = - OnHprofRecordListener { position, record -> block(position, record) } - } } diff --git a/shark/shark-hprof/src/main/java/shark/OnHprofRecordTagListener.kt b/shark/shark-hprof/src/main/java/shark/OnHprofRecordTagListener.kt index 8994b13a36..c2cb5db2fa 100644 --- a/shark/shark-hprof/src/main/java/shark/OnHprofRecordTagListener.kt +++ b/shark/shark-hprof/src/main/java/shark/OnHprofRecordTagListener.kt @@ -16,21 +16,4 @@ fun interface OnHprofRecordTagListener { length: Long, reader: HprofRecordReader ) - - companion object { - /** - * Utility function to create a [OnHprofRecordTagListener] from the passed in [block] lambda - * instead of using the anonymous `object : OnHprofRecordTagListener` syntax. - * - * Usage: - * - * ```kotlin - * val listener = OnHprofRecordTagListener { tag, length, reader -> - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (HprofRecordTag, Long, HprofRecordReader) -> Unit): OnHprofRecordTagListener = - OnHprofRecordTagListener { tag, length, reader -> block(tag, length, reader) } - } } diff --git a/shark/shark/api/shark.api b/shark/shark/api/shark.api index d00f0c6265..305d5510cd 100644 --- a/shark/shark/api/shark.api +++ b/shark/shark/api/shark.api @@ -262,14 +262,9 @@ public final class shark/LeakTraceReference$ReferenceType : java/lang/Enum { } public abstract interface class shark/LeakingObjectFinder { - public static final field Companion Lshark/LeakingObjectFinder$Companion; public abstract fun findLeakingObjectIds (Lshark/HeapGraph;)Ljava/util/Set; } -public final class shark/LeakingObjectFinder$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lshark/LeakingObjectFinder; -} - public final class shark/LibraryLeak : shark/Leak { public static final field Companion Lshark/LibraryLeak$Companion; public fun (Ljava/util/List;Lshark/ReferencePattern;Ljava/lang/String;)V @@ -314,18 +309,12 @@ public abstract interface class shark/MetadataExtractor { public final class shark/MetadataExtractor$Companion { public final fun getNO_OP ()Lshark/MetadataExtractor; - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lshark/MetadataExtractor; } public abstract interface class shark/ObjectInspector { - public static final field Companion Lshark/ObjectInspector$Companion; public abstract fun inspect (Lshark/ObjectReporter;)V } -public final class shark/ObjectInspector$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lshark/ObjectInspector; -} - public abstract class shark/ObjectInspectors : java/lang/Enum, shark/ObjectInspector { public static final field ANONYMOUS_CLASS Lshark/ObjectInspectors; public static final field CLASS Lshark/ObjectInspectors; @@ -361,7 +350,6 @@ public abstract interface class shark/OnAnalysisProgressListener { public final class shark/OnAnalysisProgressListener$Companion { public final fun getNO_OP ()Lshark/OnAnalysisProgressListener; - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lshark/OnAnalysisProgressListener; } public final class shark/OnAnalysisProgressListener$Step : java/lang/Enum { diff --git a/shark/shark/src/main/java/shark/LeakingObjectFinder.kt b/shark/shark/src/main/java/shark/LeakingObjectFinder.kt index 80ca2af1f5..8ce1a38ec0 100644 --- a/shark/shark/src/main/java/shark/LeakingObjectFinder.kt +++ b/shark/shark/src/main/java/shark/LeakingObjectFinder.kt @@ -12,21 +12,4 @@ fun interface LeakingObjectFinder { * For a given heap graph, returns a set of object ids for the objects that are leaking. */ fun findLeakingObjectIds(graph: HeapGraph): Set - - companion object { - /** - * Utility function to create a [LeakingObjectFinder] from the passed in [block] lambda - * instead of using the anonymous `object : LeakingObjectFinder` syntax. - * - * Usage: - * - * ```kotlin - * val listener = LeakingObjectFinder { - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (HeapGraph) -> Set): LeakingObjectFinder = - LeakingObjectFinder { graph -> block(graph) } - } } diff --git a/shark/shark/src/main/java/shark/MetadataExtractor.kt b/shark/shark/src/main/java/shark/MetadataExtractor.kt index b3d56eb584..7434082257 100644 --- a/shark/shark/src/main/java/shark/MetadataExtractor.kt +++ b/shark/shark/src/main/java/shark/MetadataExtractor.kt @@ -14,20 +14,5 @@ fun interface MetadataExtractor { * A no-op [MetadataExtractor] */ val NO_OP = MetadataExtractor { emptyMap() } - - /** - * Utility function to create a [MetadataExtractor] from the passed in [block] lambda instead of - * using the anonymous `object : MetadataExtractor` syntax. - * - * Usage: - * - * ```kotlin - * val inspector = MetadataExtractor { graph -> - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (HeapGraph) -> Map): MetadataExtractor = - MetadataExtractor { graph -> block(graph) } } } diff --git a/shark/shark/src/main/java/shark/ObjectInspector.kt b/shark/shark/src/main/java/shark/ObjectInspector.kt index c04916f75b..5073e37f2c 100644 --- a/shark/shark/src/main/java/shark/ObjectInspector.kt +++ b/shark/shark/src/main/java/shark/ObjectInspector.kt @@ -14,21 +14,4 @@ fun interface ObjectInspector { * @see [ObjectInspector] */ fun inspect(reporter: ObjectReporter) - - companion object { - /** - * Utility function to create a [ObjectInspector] from the passed in [block] lambda instead of - * using the anonymous `object : ObjectInspector` syntax. - * - * Usage: - * - * ```kotlin - * val inspector = ObjectInspector { reporter -> - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (ObjectReporter) -> Unit): ObjectInspector = - ObjectInspector { reporter -> block(reporter) } - } } diff --git a/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt b/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt index 92cdbff92c..0db4e13f77 100644 --- a/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt +++ b/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt @@ -26,9 +26,9 @@ fun interface OnAnalysisProgressListener { init { val lowercaseName = name.replace("_", " ") - .toLowerCase(Locale.US) + .lowercase(Locale.US) humanReadableName = - lowercaseName.substring(0, 1).toUpperCase(Locale.US) + lowercaseName.substring(1) + lowercaseName.substring(0, 1).uppercase(Locale.US) + lowercaseName.substring(1) } } @@ -40,20 +40,5 @@ fun interface OnAnalysisProgressListener { * A no-op [OnAnalysisProgressListener] */ val NO_OP = OnAnalysisProgressListener {} - - /** - * Utility function to create a [OnAnalysisProgressListener] from the passed in [block] lambda - * instead of using the anonymous `object : OnAnalysisProgressListener` syntax. - * - * Usage: - * - * ```kotlin - * val listener = OnAnalysisProgressListener { - * - * } - * ``` - */ - inline operator fun invoke(crossinline block: (Step) -> Unit): OnAnalysisProgressListener = - OnAnalysisProgressListener { step -> block(step) } } } From b31f6e0c15e0caeec4a141ba9cf036894b8ad6a8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Sat, 27 May 2023 14:05:44 -0700 Subject: [PATCH 11/13] moving things around --- gradle/libs.versions.toml | 3 + .../src/main/java/leakcanary/Clock.kt | 2 - .../leakcanary/OnObjectRetainedListener.kt | 2 - .../java/shark/internal/friendly/Friendly.kt | 3 +- .../src/test/java/shark/HprofIOPerfTest.kt | 27 +- .../java/shark/HprofRetainedHeapPerfTest.kt | 1 - .../src/main/java/shark/HprofHeapGraph.kt | 12 + .../main/java/shark/OnHprofRecordListener.kt | 2 - shark/shark/build.gradle | 1 + .../ActualMatchingReferenceReaderFactory.kt | 19 + .../{internal => }/AndroidNativeSizeMapper.kt | 5 +- .../shark/AndroidReferenceReaderFactory.kt | 29 + .../{internal => }/AndroidReferenceReaders.kt | 15 +- .../ApacheHarmonyInstanceRefReaders.kt | 18 +- .../ChainingInstanceReferenceReader.kt | 17 +- .../{internal => }/ClassReferenceReader.kt | 13 +- .../DelegatingObjectReferenceReader.kt | 3 +- .../shark/{internal => }/DominatorTree.kt | 7 +- .../FieldInstanceReferenceReader.kt | 15 +- .../src/main/java/shark/GcRootProvider.kt | 8 + .../src/main/java/shark/GcRootReference.kt | 8 + .../shark/src/main/java/shark/HeapAnalyzer.kt | 670 ++---------------- .../JavaLocalReferenceReader.kt | 15 +- shark/shark/src/main/java/shark/LeakTracer.kt | 11 + .../main/java/shark/LeakingObjectFinder.kt | 2 - .../java/shark/LeaksAndUnreachableObjects.kt | 8 + ...tProvider.kt => MatchingGcRootProvider.kt} | 56 +- .../src/main/java/shark/MetadataExtractor.kt | 2 - .../ObjectArrayReferenceReader.kt | 7 +- .../shark/{internal => }/ObjectDominators.kt | 30 +- .../src/main/java/shark/ObjectInspector.kt | 2 - .../java/shark/OnAnalysisProgressListener.kt | 2 - .../OpenJdkInstanceRefReaders.kt | 10 +- .../src/main/java/shark/PathFindingResults.kt | 9 + ...r.kt => PrioritizingShortestPathFinder.kt} | 75 +- .../main/java/shark/RealLeakTracerFactory.kt | 540 ++++++++++++++ .../java/shark/{internal => }/Reference.kt | 10 +- .../main/java/shark/ReferenceLocationType.kt | 11 + .../shark/{internal => }/ReferenceReader.kt | 10 +- .../src/main/java/shark/ShortestPathFinder.kt | 18 + ...tualizingMatchingReferenceReaderFactory.kt | 26 + .../internal/InternalSharedExpanderHelpers.kt | 7 +- .../InternalSharkCollectionsHelper.kt | 2 + .../shark/internal/ReferenceLocationType.kt | 8 - .../java/shark/internal/ReferencePathNode.kt | 4 +- .../shark/internal/ShallowSizeCalculator.kt | 2 +- .../shark/OpenJdkInstanceRefReadersTest.kt | 50 +- .../AndroidReferenceReadersHprofTest.kt | 6 - .../java/shark/internal/DominatorTreeTest.kt | 3 +- 49 files changed, 976 insertions(+), 830 deletions(-) create mode 100644 shark/shark/src/main/java/shark/ActualMatchingReferenceReaderFactory.kt rename shark/shark/src/main/java/shark/{internal => }/AndroidNativeSizeMapper.kt (95%) create mode 100644 shark/shark/src/main/java/shark/AndroidReferenceReaderFactory.kt rename shark/shark/src/main/java/shark/{internal => }/AndroidReferenceReaders.kt (94%) rename shark/shark/src/main/java/shark/{internal => }/ApacheHarmonyInstanceRefReaders.kt (89%) rename shark/shark/src/main/java/shark/{internal => }/ChainingInstanceReferenceReader.kt (78%) rename shark/shark/src/main/java/shark/{internal => }/ClassReferenceReader.kt (88%) rename shark/shark/src/main/java/shark/{internal => }/DelegatingObjectReferenceReader.kt (94%) rename shark/shark/src/main/java/shark/{internal => }/DominatorTree.kt (98%) rename shark/shark/src/main/java/shark/{internal => }/FieldInstanceReferenceReader.kt (95%) create mode 100644 shark/shark/src/main/java/shark/GcRootProvider.kt create mode 100644 shark/shark/src/main/java/shark/GcRootReference.kt rename shark/shark/src/main/java/shark/{internal => }/JavaLocalReferenceReader.kt (87%) create mode 100644 shark/shark/src/main/java/shark/LeakTracer.kt create mode 100644 shark/shark/src/main/java/shark/LeaksAndUnreachableObjects.kt rename shark/shark/src/main/java/shark/{internal/GcRootProvider.kt => MatchingGcRootProvider.kt} (73%) rename shark/shark/src/main/java/shark/{internal => }/ObjectArrayReferenceReader.kt (92%) rename shark/shark/src/main/java/shark/{internal => }/ObjectDominators.kt (85%) rename shark/shark/src/main/java/shark/{internal => }/OpenJdkInstanceRefReaders.kt (95%) create mode 100644 shark/shark/src/main/java/shark/PathFindingResults.kt rename shark/shark/src/main/java/shark/{internal/PathFinder.kt => PrioritizingShortestPathFinder.kt} (78%) create mode 100644 shark/shark/src/main/java/shark/RealLeakTracerFactory.kt rename shark/shark/src/main/java/shark/{internal => }/Reference.kt (91%) create mode 100644 shark/shark/src/main/java/shark/ReferenceLocationType.kt rename shark/shark/src/main/java/shark/{internal => }/ReferenceReader.kt (74%) create mode 100644 shark/shark/src/main/java/shark/ShortestPathFinder.kt create mode 100644 shark/shark/src/main/java/shark/VirtualizingMatchingReferenceReaderFactory.kt delete mode 100644 shark/shark/src/main/java/shark/internal/ReferenceLocationType.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 979b772b0c..860946dc73 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ # We would like to use Kotlin recent language features but keep Kotlin 1.3 library APIs # The benefit is that depending clients do not have to upgrade to Kotlin 1.4 kotlin = "1.8.21" +coroutines = "1.7.0" androidXTest = "1.1.0" androidXJunit = "1.1.3" workManager = "2.7.0" @@ -30,6 +31,8 @@ gradlePlugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plug gradlePlugin-keeper = { module = "com.slack.keeper:keeper", version = "0.7.0" } gradlePlugin-sqldelight = { module = "app.cash.sqldelight:gradle-plugin", version = "2.0.0-alpha05" } +coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } + kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt b/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt index 6c73eb6dc8..d974aac958 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt @@ -2,8 +2,6 @@ package leakcanary /** * An interface to abstract the SystemClock.uptimeMillis() Android API in non Android artifacts. - * - * This is a functional interface with which you can create a [Clock] from a lambda. */ fun interface Clock { /** diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt b/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt index bb39e61b49..f92ef057b9 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt @@ -2,8 +2,6 @@ package leakcanary /** * Listener used by [ObjectWatcher] to report retained objects. - * - * This is a functional interface with which you can create a [OnObjectRetainedListener] from a lambda. */ fun interface OnObjectRetainedListener { diff --git a/shark/shark-android/src/main/java/shark/internal/friendly/Friendly.kt b/shark/shark-android/src/main/java/shark/internal/friendly/Friendly.kt index 09f1fcdb5f..180f5a3fdd 100644 --- a/shark/shark-android/src/main/java/shark/internal/friendly/Friendly.kt +++ b/shark/shark-android/src/main/java/shark/internal/friendly/Friendly.kt @@ -3,7 +3,8 @@ package shark.internal.friendly +import shark.AndroidNativeSizeMapper import shark.HeapGraph internal inline fun HeapGraph.mapNativeSizes() = - shark.internal.AndroidNativeSizeMapper.mapNativeSizes(this) + AndroidNativeSizeMapper.mapNativeSizes(this) diff --git a/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt b/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt index 30a299a5b1..83d35657ee 100644 --- a/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt +++ b/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt @@ -6,7 +6,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.nield.kotlinstatistics.median import shark.HprofHeapGraph.Companion.openHeapGraph -import shark.PrimitiveType.INT /** * IO reads is the largest factor on Shark's performance so this helps prevents @@ -245,22 +244,24 @@ class HprofIOPerfTest { printResult: Boolean = false ): List> { val source = MetricsDualSourceProvider(hprofFile) - val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener.NO_OP) val analysis = source.openHeapGraph().use { graph -> - heapAnalyzer.analyze( - heapDumpFile = hprofFile, - graph = graph, - leakingObjectFinder = FilteringLeakingObjectFinder( - AndroidObjectInspectors.appLeakingObjectFilters - ), + + val leakingObjectFinder = FilteringLeakingObjectFinder( + AndroidObjectInspectors.appLeakingObjectFilters + ) + + val objectIds = leakingObjectFinder.findLeakingObjectIds(graph) + val tracer = RealLeakTracerFactory( referenceMatchers = AndroidReferenceMatchers.appDefaults, computeRetainedHeapSize = computeRetainedHeapSize, objectInspectors = AndroidObjectInspectors.appDefaults, - metadataExtractor = AndroidMetadataExtractor - ) - } - check(analysis is HeapAnalysisSuccess) { - "Expected success not $analysis" + referenceReaderFactory = AndroidReferenceReaderFactory( + AndroidReferenceMatchers.appDefaults + ), + listener = {} + ).createFor(graph) + + tracer.traceObjects(objectIds) } if (printResult) { println(analysis) diff --git a/shark/shark-android/src/test/java/shark/HprofRetainedHeapPerfTest.kt b/shark/shark-android/src/test/java/shark/HprofRetainedHeapPerfTest.kt index f97e62faa8..280894cd76 100644 --- a/shark/shark-android/src/test/java/shark/HprofRetainedHeapPerfTest.kt +++ b/shark/shark-android/src/test/java/shark/HprofRetainedHeapPerfTest.kt @@ -25,7 +25,6 @@ import shark.OnAnalysisProgressListener.Step.FINDING_PATHS_TO_RETAINED_OBJECTS import shark.OnAnalysisProgressListener.Step.FINDING_RETAINED_OBJECTS import shark.OnAnalysisProgressListener.Step.INSPECTING_OBJECTS import shark.OnAnalysisProgressListener.Step.PARSING_HEAP_DUMP -import shark.internal.ObjectDominators private const val ANALYSIS_THREAD = "analysis" diff --git a/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt b/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt index a5127839c9..92b175c7d2 100644 --- a/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt +++ b/shark/shark-graph/src/main/java/shark/HprofHeapGraph.kt @@ -407,6 +407,18 @@ class HprofHeapGraph internal constructor( proguardMapping: ProguardMapping? = null, indexedGcRootTypes: Set = HprofIndex.defaultIndexedGcRootTags() ): CloseableHeapGraph { + // TODO We can probably remove the concept of DualSourceProvider. Opening a heap graph requires + // a random access reader which is built from a random access source + headers. + // Also require headers, and the index. + // So really we're: + // 1) Reader the headers from an okio source + // 2) Reading the whole source streaming to create the index. Wondering if we really need to parse + // the headers, close the file then parse / skip the header part. Can't the parsing + indexing give + // us headers + index? + // 3) Using the index + headers + a random access source on the content to create a closeable + // abstraction. + // Note: should see if Okio has a better abstraction for random access now. + // Also Use FileSystem + Path instead of File as the core way to open a file based heap dump. val header = openStreamingSource().use { HprofHeader.parseHeaderOf(it) } val index = HprofIndex.indexRecordsOf(this, header, proguardMapping, indexedGcRootTypes) return index.openHeapGraph() diff --git a/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt b/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt index 58dab023e8..b9f205e7b5 100644 --- a/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt +++ b/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt @@ -4,8 +4,6 @@ package shark * Listener passed in to [StreamingHprofReader.readRecords], gets notified for each [HprofRecord] * found in the heap dump which types is in the set of the recordTypes parameter passed to * [StreamingHprofReader.readRecords]. - * - * This is a functional interface with which you can create a [OnHprofRecordListener] from a lambda. */ fun interface OnHprofRecordListener { fun onHprofRecord( diff --git a/shark/shark/build.gradle b/shark/shark/build.gradle index 54ab74db7f..3e9461cddd 100644 --- a/shark/shark/build.gradle +++ b/shark/shark/build.gradle @@ -9,6 +9,7 @@ targetCompatibility = JavaVersion.VERSION_1_8 dependencies { api projects.shark.sharkGraph + implementation libs.coroutines.core implementation libs.kotlin.stdlib implementation libs.okio2 diff --git a/shark/shark/src/main/java/shark/ActualMatchingReferenceReaderFactory.kt b/shark/shark/src/main/java/shark/ActualMatchingReferenceReaderFactory.kt new file mode 100644 index 0000000000..b0ff64a5b6 --- /dev/null +++ b/shark/shark/src/main/java/shark/ActualMatchingReferenceReaderFactory.kt @@ -0,0 +1,19 @@ +package shark + +/** + * Creates [ReferenceReader] instances that will follow references from all [HeapObject]s, + * applying matching rules provided by [referenceMatchers], and not creating any virtual reference. + */ +class ActualMatchingReferenceReaderFactory( + private val referenceMatchers: List +) : ReferenceReader.Factory { + override fun createFor(heapGraph: HeapGraph): ReferenceReader { + return DelegatingObjectReferenceReader( + classReferenceReader = ClassReferenceReader(heapGraph, referenceMatchers), + instanceReferenceReader = ChainingInstanceReferenceReader( + listOf(JavaLocalReferenceReader(heapGraph, referenceMatchers)), + FieldInstanceReferenceReader(heapGraph, referenceMatchers) + ), objectArrayReferenceReader = ObjectArrayReferenceReader() + ) + } +} diff --git a/shark/shark/src/main/java/shark/internal/AndroidNativeSizeMapper.kt b/shark/shark/src/main/java/shark/AndroidNativeSizeMapper.kt similarity index 95% rename from shark/shark/src/main/java/shark/internal/AndroidNativeSizeMapper.kt rename to shark/shark/src/main/java/shark/AndroidNativeSizeMapper.kt index a52e347c08..1444d0f120 100644 --- a/shark/shark/src/main/java/shark/internal/AndroidNativeSizeMapper.kt +++ b/shark/shark/src/main/java/shark/AndroidNativeSizeMapper.kt @@ -1,9 +1,8 @@ -package shark.internal +package shark -import shark.HeapGraph import shark.HeapObject.HeapInstance -internal class AndroidNativeSizeMapper(private val graph: HeapGraph) { +class AndroidNativeSizeMapper(private val graph: HeapGraph) { /** * Returns a map of Object id to native size as tracked by NativeAllocationRegistry$CleanerThunk diff --git a/shark/shark/src/main/java/shark/AndroidReferenceReaderFactory.kt b/shark/shark/src/main/java/shark/AndroidReferenceReaderFactory.kt new file mode 100644 index 0000000000..38e275dfcf --- /dev/null +++ b/shark/shark/src/main/java/shark/AndroidReferenceReaderFactory.kt @@ -0,0 +1,29 @@ +package shark + +// TODO Move to shark-android once HeapAnalyzer is removed. +// TODO Not sure if this class should exist of be some sort of configuration instead. +/** + * Creates [ReferenceReader] instances that will follow references from all [HeapObject]s, + * applying matching rules provided by [referenceMatchers], creating additional virtual instance + * reference based on known Android classes. + */ +class AndroidReferenceReaderFactory( + private val referenceMatchers: List +) : ReferenceReader.Factory { + + private val virtualizingFactory = VirtualizingMatchingReferenceReaderFactory( + referenceMatchers = referenceMatchers, + virtualRefReadersFactory = { graph -> + listOf( + JavaLocalReferenceReader(graph, referenceMatchers), + ) + + OpenJdkInstanceRefReaders.values().mapNotNull { it.create(graph) } + + ApacheHarmonyInstanceRefReaders.values().mapNotNull { it.create(graph) } + + AndroidReferenceReaders.values().mapNotNull { it.create(graph) } + } + ) + + override fun createFor(heapGraph: HeapGraph): ReferenceReader { + return virtualizingFactory.createFor(heapGraph) + } +} diff --git a/shark/shark/src/main/java/shark/internal/AndroidReferenceReaders.kt b/shark/shark/src/main/java/shark/AndroidReferenceReaders.kt similarity index 94% rename from shark/shark/src/main/java/shark/internal/AndroidReferenceReaders.kt rename to shark/shark/src/main/java/shark/AndroidReferenceReaders.kt index 9c07b384fc..b5e52366db 100644 --- a/shark/shark/src/main/java/shark/internal/AndroidReferenceReaders.kt +++ b/shark/shark/src/main/java/shark/AndroidReferenceReaders.kt @@ -1,15 +1,14 @@ -package shark.internal +package shark -import shark.HeapGraph import shark.HeapObject.HeapInstance import shark.ValueHolder.ReferenceHolder -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory -import shark.internal.Reference.LazyDetails -import shark.internal.ReferenceLocationType.ARRAY_ENTRY -import shark.internal.ReferenceLocationType.INSTANCE_FIELD +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory +import shark.Reference.LazyDetails +import shark.ReferenceLocationType.ARRAY_ENTRY +import shark.ReferenceLocationType.INSTANCE_FIELD -internal enum class AndroidReferenceReaders : OptionalFactory { +enum class AndroidReferenceReaders : OptionalFactory { MESSAGE_QUEUE { override fun create(graph: HeapGraph): VirtualInstanceReferenceReader? { diff --git a/shark/shark/src/main/java/shark/internal/ApacheHarmonyInstanceRefReaders.kt b/shark/shark/src/main/java/shark/ApacheHarmonyInstanceRefReaders.kt similarity index 89% rename from shark/shark/src/main/java/shark/internal/ApacheHarmonyInstanceRefReaders.kt rename to shark/shark/src/main/java/shark/ApacheHarmonyInstanceRefReaders.kt index 8e194ed84e..d4f652ba8b 100644 --- a/shark/shark/src/main/java/shark/internal/ApacheHarmonyInstanceRefReaders.kt +++ b/shark/shark/src/main/java/shark/ApacheHarmonyInstanceRefReaders.kt @@ -1,9 +1,11 @@ -package shark.internal +package shark -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader -import shark.HeapGraph +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader import shark.HeapObject.HeapInstance +import shark.internal.InternalSharedArrayListReferenceReader +import shark.internal.InternalSharedHashMapReferenceReader +import shark.internal.InternalSharedLinkedListReferenceReader /** * Defines [VirtualInstanceReferenceReader] factories for common Apache Harmony data structures. @@ -11,7 +13,7 @@ import shark.HeapObject.HeapInstance * Note: the expanders target the direct classes and don't target subclasses, as these might * include additional out going references that would be missed. */ -internal enum class ApacheHarmonyInstanceRefReaders : OptionalFactory { +enum class ApacheHarmonyInstanceRefReaders : OptionalFactory { // https://cs.android.com/android/platform/superproject/+/android-6.0.1_r81:libcore/luni/src/main/java/java/util/LinkedList.java LINKED_LIST { @@ -139,9 +141,9 @@ internal enum class ApacheHarmonyInstanceRefReaders : OptionalFactory { return (instanceClassId == hashSetClassId || instanceClassId == linkedHashSetClassId) } - override fun read(instance: HeapInstance): Sequence { + override fun read(source: HeapInstance): Sequence { // "HashSet.backingMap" is never null. - val map = instance["java.util.HashSet", "backingMap"]!!.valueAsInstance!! + val map = source["java.util.HashSet", "backingMap"]!!.valueAsInstance!! return InternalSharedHashMapReferenceReader( className = "java.util.HashMap", tableFieldName = "table", @@ -152,7 +154,7 @@ internal enum class ApacheHarmonyInstanceRefReaders : OptionalFactory { keyName = "element()", keysOnly = true, matches = { true }, - declaringClassId = { instance.instanceClassId } + declaringClassId = { source.instanceClassId } ).read(map) } } diff --git a/shark/shark/src/main/java/shark/internal/ChainingInstanceReferenceReader.kt b/shark/shark/src/main/java/shark/ChainingInstanceReferenceReader.kt similarity index 78% rename from shark/shark/src/main/java/shark/internal/ChainingInstanceReferenceReader.kt rename to shark/shark/src/main/java/shark/ChainingInstanceReferenceReader.kt index 8ce1d65d1c..63ec1c3f37 100644 --- a/shark/shark/src/main/java/shark/internal/ChainingInstanceReferenceReader.kt +++ b/shark/shark/src/main/java/shark/ChainingInstanceReferenceReader.kt @@ -1,6 +1,5 @@ -package shark.internal +package shark -import shark.HeapGraph import shark.HeapObject.HeapInstance /** @@ -10,7 +9,7 @@ import shark.HeapObject.HeapInstance * that we correctly track which objects have been visited and correctly compute dominators and * retained size. */ -internal class ChainingInstanceReferenceReader( +class ChainingInstanceReferenceReader( private val virtualRefReaders: List, private val fieldRefReader: FieldInstanceReferenceReader ) : ReferenceReader { @@ -44,7 +43,7 @@ internal class ChainingInstanceReferenceReader( fun matches(instance: HeapInstance): Boolean /** - * May create a new InstanceExpander, depending on what's in the heap graph. + * May create a new [VirtualInstanceReferenceReader], depending on what's in the heap graph. * [OptionalFactory] implementations might return a different [ReferenceReader] * depending on which version of a class is present in the heap dump, or they might return null if * that class is missing. @@ -52,5 +51,15 @@ internal class ChainingInstanceReferenceReader( fun interface OptionalFactory { fun create(graph: HeapGraph): VirtualInstanceReferenceReader? } + + /** + * Creates a list of [VirtualInstanceReferenceReader] where the content of the list depends on + * the classes in the heap graph and their implementation. This is a chain as + * [VirtualInstanceReferenceReader] elements in the list will process references in order in + * [ChainingInstanceReferenceReader]. + */ + fun interface ChainFactory { + fun createFor(graph: HeapGraph): List + } } } diff --git a/shark/shark/src/main/java/shark/internal/ClassReferenceReader.kt b/shark/shark/src/main/java/shark/ClassReferenceReader.kt similarity index 88% rename from shark/shark/src/main/java/shark/internal/ClassReferenceReader.kt rename to shark/shark/src/main/java/shark/ClassReferenceReader.kt index d58f549556..1a0a88c844 100644 --- a/shark/shark/src/main/java/shark/internal/ClassReferenceReader.kt +++ b/shark/shark/src/main/java/shark/ClassReferenceReader.kt @@ -1,17 +1,12 @@ -package shark.internal +package shark -import shark.HeapGraph import shark.HeapObject.HeapClass -import shark.IgnoredReferenceMatcher -import shark.LibraryLeakReferenceMatcher -import shark.internal.Reference.LazyDetails -import shark.internal.ReferenceLocationType.STATIC_FIELD -import shark.ReferenceMatcher +import shark.Reference.LazyDetails +import shark.ReferenceLocationType.STATIC_FIELD import shark.ReferencePattern.StaticFieldPattern import shark.ValueHolder.ReferenceHolder -import shark.filterFor -internal class ClassReferenceReader( +class ClassReferenceReader( graph: HeapGraph, referenceMatchers: List ) : ReferenceReader { diff --git a/shark/shark/src/main/java/shark/internal/DelegatingObjectReferenceReader.kt b/shark/shark/src/main/java/shark/DelegatingObjectReferenceReader.kt similarity index 94% rename from shark/shark/src/main/java/shark/internal/DelegatingObjectReferenceReader.kt rename to shark/shark/src/main/java/shark/DelegatingObjectReferenceReader.kt index b9210ad184..4a7a3f6c83 100644 --- a/shark/shark/src/main/java/shark/internal/DelegatingObjectReferenceReader.kt +++ b/shark/shark/src/main/java/shark/DelegatingObjectReferenceReader.kt @@ -1,6 +1,5 @@ -package shark.internal +package shark -import shark.HeapObject import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray diff --git a/shark/shark/src/main/java/shark/internal/DominatorTree.kt b/shark/shark/src/main/java/shark/DominatorTree.kt similarity index 98% rename from shark/shark/src/main/java/shark/internal/DominatorTree.kt rename to shark/shark/src/main/java/shark/DominatorTree.kt index 5420ebab74..e65351682c 100644 --- a/shark/shark/src/main/java/shark/internal/DominatorTree.kt +++ b/shark/shark/src/main/java/shark/DominatorTree.kt @@ -1,13 +1,12 @@ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") -package shark.internal +package shark -import shark.ValueHolder -import shark.internal.ObjectDominators.DominatorNode +import shark.ObjectDominators.DominatorNode import shark.internal.hppc.LongLongScatterMap import shark.internal.hppc.LongLongScatterMap.ForEachCallback import shark.internal.hppc.LongScatterSet -internal class DominatorTree(expectedElements: Int = 4) { +class DominatorTree(expectedElements: Int = 4) { /** * Map of objects to their dominator. diff --git a/shark/shark/src/main/java/shark/internal/FieldInstanceReferenceReader.kt b/shark/shark/src/main/java/shark/FieldInstanceReferenceReader.kt similarity index 95% rename from shark/shark/src/main/java/shark/internal/FieldInstanceReferenceReader.kt rename to shark/shark/src/main/java/shark/FieldInstanceReferenceReader.kt index 9c038804a6..7078719e2e 100644 --- a/shark/shark/src/main/java/shark/internal/FieldInstanceReferenceReader.kt +++ b/shark/shark/src/main/java/shark/FieldInstanceReferenceReader.kt @@ -1,14 +1,10 @@ -package shark.internal +package shark import java.util.LinkedHashMap import kotlin.LazyThreadSafetyMode.NONE -import shark.HeapGraph import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord -import shark.IgnoredReferenceMatcher -import shark.LibraryLeakReferenceMatcher -import shark.PrimitiveType import shark.PrimitiveType.BOOLEAN import shark.PrimitiveType.BYTE import shark.PrimitiveType.CHAR @@ -17,16 +13,15 @@ import shark.PrimitiveType.FLOAT import shark.PrimitiveType.INT import shark.PrimitiveType.LONG import shark.PrimitiveType.SHORT -import shark.internal.Reference.LazyDetails -import shark.internal.ReferenceLocationType.INSTANCE_FIELD -import shark.ReferenceMatcher +import shark.Reference.LazyDetails +import shark.ReferenceLocationType.INSTANCE_FIELD import shark.ReferencePattern.InstanceFieldPattern -import shark.filterFor +import shark.internal.FieldIdReader /** * Expands instance fields that hold non null references. */ -internal class FieldInstanceReferenceReader( +class FieldInstanceReferenceReader( graph: HeapGraph, referenceMatchers: List ) : ReferenceReader { diff --git a/shark/shark/src/main/java/shark/GcRootProvider.kt b/shark/shark/src/main/java/shark/GcRootProvider.kt new file mode 100644 index 0000000000..0660b59661 --- /dev/null +++ b/shark/shark/src/main/java/shark/GcRootProvider.kt @@ -0,0 +1,8 @@ +package shark + +interface GcRootProvider { + /** + * Provides a sequence of GC Roots to traverse the graph from, ideally in a stable order. + */ + fun provideGcRoots(graph: HeapGraph): Sequence +} diff --git a/shark/shark/src/main/java/shark/GcRootReference.kt b/shark/shark/src/main/java/shark/GcRootReference.kt new file mode 100644 index 0000000000..97871e0583 --- /dev/null +++ b/shark/shark/src/main/java/shark/GcRootReference.kt @@ -0,0 +1,8 @@ +package shark + +// TODO Revisit this API. It's more like a GC Root + some priority / tagging. +class GcRootReference( + val gcRoot: GcRoot, + val isLowPriority: Boolean, + val matchedLibraryLeak: LibraryLeakReferenceMatcher?, +) diff --git a/shark/shark/src/main/java/shark/HeapAnalyzer.kt b/shark/shark/src/main/java/shark/HeapAnalyzer.kt index 53a671b840..8e1d4a7f8a 100644 --- a/shark/shark/src/main/java/shark/HeapAnalyzer.kt +++ b/shark/shark/src/main/java/shark/HeapAnalyzer.kt @@ -15,53 +15,24 @@ */ package shark -import shark.HeapAnalyzer.TrieNode.LeafNode -import shark.HeapAnalyzer.TrieNode.ParentNode -import shark.HeapObject.HeapClass -import shark.HeapObject.HeapInstance -import shark.HeapObject.HeapObjectArray -import shark.HeapObject.HeapPrimitiveArray +import java.io.File +import java.util.concurrent.TimeUnit.NANOSECONDS import shark.HprofHeapGraph.Companion.openHeapGraph -import shark.LeakTrace.GcRootType -import shark.LeakTraceObject.LeakingStatus -import shark.LeakTraceObject.LeakingStatus.LEAKING -import shark.LeakTraceObject.LeakingStatus.NOT_LEAKING -import shark.LeakTraceObject.LeakingStatus.UNKNOWN -import shark.LeakTraceObject.ObjectType.ARRAY -import shark.LeakTraceObject.ObjectType.CLASS -import shark.LeakTraceObject.ObjectType.INSTANCE import shark.OnAnalysisProgressListener.Step.BUILDING_LEAK_TRACES import shark.OnAnalysisProgressListener.Step.COMPUTING_NATIVE_RETAINED_SIZE import shark.OnAnalysisProgressListener.Step.COMPUTING_RETAINED_SIZE import shark.OnAnalysisProgressListener.Step.EXTRACTING_METADATA +import shark.OnAnalysisProgressListener.Step.FINDING_DOMINATORS +import shark.OnAnalysisProgressListener.Step.FINDING_PATHS_TO_RETAINED_OBJECTS import shark.OnAnalysisProgressListener.Step.FINDING_RETAINED_OBJECTS import shark.OnAnalysisProgressListener.Step.INSPECTING_OBJECTS import shark.OnAnalysisProgressListener.Step.PARSING_HEAP_DUMP -import shark.internal.AndroidNativeSizeMapper -import shark.internal.DominatorTree -import shark.internal.PathFinder -import shark.internal.PathFinder.PathFindingResults -import shark.internal.ReferencePathNode -import shark.internal.ReferencePathNode.ChildNode -import shark.internal.ReferencePathNode.RootNode -import shark.internal.ShallowSizeCalculator -import shark.internal.createSHA1Hash -import shark.internal.lastSegment -import java.io.File -import java.util.ArrayList -import java.util.concurrent.TimeUnit.NANOSECONDS -import shark.internal.AndroidReferenceReaders -import shark.internal.ApacheHarmonyInstanceRefReaders -import shark.internal.ChainingInstanceReferenceReader -import shark.internal.ClassReferenceReader -import shark.internal.DelegatingObjectReferenceReader -import shark.internal.FieldInstanceReferenceReader -import shark.internal.JavaLocalReferenceReader -import shark.internal.ObjectArrayReferenceReader -import shark.internal.OpenJdkInstanceRefReaders -import shark.internal.ReferenceLocationType -import shark.internal.ReferencePathNode.RootNode.LibraryLeakRootNode -import shark.internal.ReferenceReader +import shark.PrioritizingShortestPathFinder.Event.StartedFindingDominators +import shark.PrioritizingShortestPathFinder.Event.StartedFindingPathsToRetainedObjects +import shark.RealLeakTracerFactory.Event.StartedBuildingLeakTraces +import shark.RealLeakTracerFactory.Event.StartedComputingJavaHeapRetainedSize +import shark.RealLeakTracerFactory.Event.StartedComputingNativeRetainedSize +import shark.RealLeakTracerFactory.Event.StartedInspectingObjects /** * Analyzes heap dumps to look for leaks. @@ -70,14 +41,6 @@ class HeapAnalyzer constructor( private val listener: OnAnalysisProgressListener ) { - private class FindLeakInput( - val graph: HeapGraph, - val referenceMatchers: List, - val computeRetainedHeapSize: Boolean, - val objectInspectors: List, - val referenceReader: ReferenceReader - ) - @Deprecated("Use the non deprecated analyze method instead") fun analyze( heapDumpFile: File, @@ -135,51 +98,6 @@ class HeapAnalyzer constructor( } } - /** - * Searches the heap dump for leaking instances and then computes the shortest strong reference - * path from those instances to the GC roots. - */ - fun analyze( - heapDumpFile: File, - graph: HeapGraph, - leakingObjectFinder: LeakingObjectFinder, - referenceMatchers: List = emptyList(), - computeRetainedHeapSize: Boolean = false, - objectInspectors: List = emptyList(), - metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP, - ): HeapAnalysis { - val analysisStartNanoTime = System.nanoTime() - - val referenceReader = DelegatingObjectReferenceReader( - classReferenceReader = ClassReferenceReader(graph, referenceMatchers), - instanceReferenceReader = ChainingInstanceReferenceReader( - listOf( - JavaLocalReferenceReader(graph, referenceMatchers), - ) - + OpenJdkInstanceRefReaders.values().mapNotNull { it.create(graph) } - + ApacheHarmonyInstanceRefReaders.values().mapNotNull { it.create(graph) } - + AndroidReferenceReaders.values().mapNotNull { it.create(graph) }, - FieldInstanceReferenceReader(graph, referenceMatchers) - ), - objectArrayReferenceReader = ObjectArrayReferenceReader() - ) - return analyze( - heapDumpFile, - graph, - leakingObjectFinder, - referenceMatchers, - computeRetainedHeapSize, - objectInspectors, - metadataExtractor, - referenceReader - ).run { - val updatedDurationMillis = since(analysisStartNanoTime) - when (this) { - is HeapAnalysisSuccess -> copy(analysisDurationMillis = updatedDurationMillis) - is HeapAnalysisFailure -> copy(analysisDurationMillis = updatedDurationMillis) - } - } - } // TODO Callers should add to analysisStartNanoTime // Input should be a builder or part of the object state probs? @@ -195,530 +113,94 @@ class HeapAnalyzer constructor( // metadataExtractor: helper object, not needed for leak finding // referenceReader: can't be helper object, needs graph => param something that can produce it from // graph (and in the impl we give that thing the referenceMatchers) - @Suppress("LongParameterList") - internal fun analyze( - // TODO Kill this file + /** + * Searches the heap dump for leaking instances and then computes the shortest strong reference + * path from those instances to the GC roots. + */ + fun analyze( heapDumpFile: File, graph: HeapGraph, leakingObjectFinder: LeakingObjectFinder, - referenceMatchers: List, - computeRetainedHeapSize: Boolean, - objectInspectors: List, - metadataExtractor: MetadataExtractor, - referenceReader: ReferenceReader + referenceMatchers: List = emptyList(), + computeRetainedHeapSize: Boolean = false, + objectInspectors: List = emptyList(), + metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP, ): HeapAnalysis { val analysisStartNanoTime = System.nanoTime() - return try { - val helpers = - FindLeakInput( - graph, referenceMatchers, computeRetainedHeapSize, objectInspectors, - referenceReader - ) - helpers.analyzeGraph( - metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime - ) - } catch (exception: Throwable) { - HeapAnalysisFailure( - heapDumpFile = heapDumpFile, - createdAtTimeMillis = System.currentTimeMillis(), - analysisDurationMillis = since(analysisStartNanoTime), - exception = HeapAnalysisException(exception) - ) - } - } - - private fun FindLeakInput.analyzeGraph( - metadataExtractor: MetadataExtractor, - leakingObjectFinder: LeakingObjectFinder, - heapDumpFile: File, - analysisStartNanoTime: Long - ): HeapAnalysisSuccess { - listener.onAnalysisProgress(EXTRACTING_METADATA) - val metadata = metadataExtractor.extractMetadata(graph) - - val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph) - .count { it.isRetained && !it.hasReferent } - - // This should rarely happens, as we generally remove all cleared weak refs right before a heap - // dump. - val metadataWithCount = if (retainedClearedWeakRefCount > 0) { - metadata + ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances") - } else { - metadata - } - - listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS) - val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph) - - val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds) - - return HeapAnalysisSuccess( - heapDumpFile = heapDumpFile, - createdAtTimeMillis = System.currentTimeMillis(), - analysisDurationMillis = since(analysisStartNanoTime), - metadata = metadataWithCount, - applicationLeaks = applicationLeaks, - libraryLeaks = libraryLeaks, - unreachableObjects = unreachableObjects - ) - } - - private data class LeaksAndUnreachableObjects( - val applicationLeaks: List, - val libraryLeaks: List, - val unreachableObjects: List - ) - - private fun FindLeakInput.findLeaks(leakingObjectIds: Set): LeaksAndUnreachableObjects { - val pathFinder = PathFinder(graph, listener, referenceReader, referenceMatchers) - val pathFindingResults = - pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize) - - val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds) - - val shortestPaths = - deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects) - - val inspectedObjectsByPath = inspectObjects(shortestPaths) - - val retainedSizes = - if (pathFindingResults.dominatorTree != null) { - computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree) - } else { - null - } - val (applicationLeaks, libraryLeaks) = buildLeakTraces( - shortestPaths, inspectedObjectsByPath, retainedSizes - ) - return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects) - } - - private fun FindLeakInput.findUnreachableObjects( - pathFindingResults: PathFindingResults, - leakingObjectIds: Set - ): List { - val reachableLeakingObjectIds = - pathFindingResults.pathsToLeakingObjects.map { it.objectId }.toSet() - - val unreachableLeakingObjectIds = leakingObjectIds - reachableLeakingObjectIds - - val unreachableObjectReporters = unreachableLeakingObjectIds.map { objectId -> - ObjectReporter(heapObject = graph.findObjectById(objectId)) - } - - objectInspectors.forEach { inspector -> - unreachableObjectReporters.forEach { reporter -> - inspector.inspect(reporter) - } - } - - val unreachableInspectedObjects = unreachableObjectReporters.map { reporter -> - val reason = resolveStatus(reporter, leakingWins = true).let { (status, reason) -> - when (status) { - LEAKING -> reason - UNKNOWN -> "This is a leaking object" - NOT_LEAKING -> "This is a leaking object. Conflicts with $reason" - } - } - InspectedObject( - reporter.heapObject, LEAKING, reason, reporter.labels - ) - } - - return buildLeakTraceObjects(unreachableInspectedObjects, null) - } - - internal sealed class TrieNode { - abstract val objectId: Long - - class ParentNode(override val objectId: Long) : TrieNode() { - val children = mutableMapOf() - override fun toString(): String { - return "ParentNode(objectId=$objectId, children=$children)" - } - } - - class LeafNode( - override val objectId: Long, - val pathNode: ReferencePathNode - ) : TrieNode() - } - - private fun deduplicateShortestPaths( - inputPathResults: List - ): List { - val rootTrieNode = ParentNode(0) - - inputPathResults.forEach { pathNode -> - // Go through the linked list of nodes and build the reverse list of instances from - // root to leaking. - val path = mutableListOf() - var leakNode: ReferencePathNode = pathNode - while (leakNode is ChildNode) { - path.add(0, leakNode.objectId) - leakNode = leakNode.parent - } - path.add(0, leakNode.objectId) - updateTrie(pathNode, path, 0, rootTrieNode) - } - - val outputPathResults = mutableListOf() - findResultsInTrie(rootTrieNode, outputPathResults) - - if (outputPathResults.size != inputPathResults.size) { - SharkLog.d { - "Found ${inputPathResults.size} paths to retained objects," + - " down to ${outputPathResults.size} after removing duplicated paths" - } - } else { - SharkLog.d { "Found ${outputPathResults.size} paths to retained objects" } - } - - return outputPathResults.map { retainedObjectNode -> - val shortestChildPath = mutableListOf() - var node = retainedObjectNode - while (node is ChildNode) { - shortestChildPath.add(0, node) - node = node.parent - } - val rootNode = node as RootNode - ShortestPath(rootNode, shortestChildPath) - } - } - - private fun updateTrie( - pathNode: ReferencePathNode, - path: List, - pathIndex: Int, - parentNode: ParentNode - ) { - val objectId = path[pathIndex] - if (pathIndex == path.lastIndex) { - parentNode.children[objectId] = LeafNode(objectId, pathNode) - } else { - val childNode = parentNode.children[objectId] ?: run { - val newChildNode = ParentNode(objectId) - parentNode.children[objectId] = newChildNode - newChildNode - } - if (childNode is ParentNode) { - updateTrie(pathNode, path, pathIndex + 1, childNode) - } - } - } - - private fun findResultsInTrie( - parentNode: ParentNode, - outputPathResults: MutableList - ) { - parentNode.children.values.forEach { childNode -> - when (childNode) { - is ParentNode -> { - findResultsInTrie(childNode, outputPathResults) - } - is LeafNode -> { - outputPathResults += childNode.pathNode - } - } - } - } - - internal class ShortestPath( - val root: RootNode, - val childPath: List - ) { - - val childPathWithDetails = childPath.map { it to it.lazyDetailsResolver.resolve() } - - fun asList() = listOf(root) + childPath - - fun firstLibraryLeakMatcher(): LibraryLeakReferenceMatcher? { - if (root is LibraryLeakRootNode) { - return root.matcher - } - return childPathWithDetails.map { it.second.matchedLibraryLeak }.firstOrNull { it != null } - } - - fun asNodesWithMatchers(): List> { - val rootMatcher = if (root is LibraryLeakRootNode) { - root.matcher - } else null - val childPathWithMatchers = - childPathWithDetails.map { it.first to it.second.matchedLibraryLeak } - return listOf(root to rootMatcher) + childPathWithMatchers - } - } - - private fun FindLeakInput.buildLeakTraces( - shortestPaths: List, - inspectedObjectsByPath: List>, - retainedSizes: Map>? - ): Pair, List> { - listener.onAnalysisProgress(BUILDING_LEAK_TRACES) - - val applicationLeaksMap = mutableMapOf>() - val libraryLeaksMap = - mutableMapOf>>() - - shortestPaths.forEachIndexed { pathIndex, shortestPath -> - val inspectedObjects = inspectedObjectsByPath[pathIndex] - val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes) + val referenceReader = AndroidReferenceReaderFactory(referenceMatchers).createFor(graph) - val referencePath = buildReferencePath(shortestPath, leakTraceObjects) - - val leakTrace = LeakTrace( - gcRootType = GcRootType.fromGcRoot(shortestPath.root.gcRoot), - referencePath = referencePath, - leakingObject = leakTraceObjects.last() - ) - - val firstLibraryLeakMatcher = shortestPath.firstLibraryLeakMatcher() - if (firstLibraryLeakMatcher != null) { - val signature: String = firstLibraryLeakMatcher.pattern.toString() - .createSHA1Hash() - libraryLeaksMap.getOrPut(signature) { firstLibraryLeakMatcher to mutableListOf() } - .second += leakTrace - } else { - applicationLeaksMap.getOrPut(leakTrace.signature) { mutableListOf() } += leakTrace - } - } - val applicationLeaks = applicationLeaksMap.map { (_, leakTraces) -> - ApplicationLeak(leakTraces) - } - val libraryLeaks = libraryLeaksMap.map { (_, pair) -> - val (matcher, leakTraces) = pair - LibraryLeak(leakTraces, matcher.pattern, matcher.description) - } - return applicationLeaks to libraryLeaks - } - - private fun FindLeakInput.inspectObjects(shortestPaths: List): List> { - listener.onAnalysisProgress(INSPECTING_OBJECTS) - - val leakReportersByPath = shortestPaths.map { path -> - val pathList = path.asNodesWithMatchers() - pathList - .mapIndexed { index, (node, _) -> - val reporter = ObjectReporter(heapObject = graph.findObjectById(node.objectId)) - if (index + 1 < pathList.size) { - val (_, nextMatcher) = pathList[index + 1] - if (nextMatcher != null) { - reporter.labels += "Library leak match: ${nextMatcher.pattern}" + return try { + val leakTracer = RealLeakTracerFactory( + shortestPathFinderFactory = PrioritizingShortestPathFinder.Factory( + listener = { event -> + when (event) { + StartedFindingDominators -> listener.onAnalysisProgress(FINDING_DOMINATORS) + StartedFindingPathsToRetainedObjects -> listener.onAnalysisProgress( + FINDING_PATHS_TO_RETAINED_OBJECTS + ) } - } - reporter + }, + referenceReaderFactory = { referenceReader }, + gcRootProvider = MatchingGcRootProvider(referenceMatchers), + computeRetainedHeapSize = computeRetainedHeapSize, + ), + objectInspectors + ) { event -> + when (event) { + StartedBuildingLeakTraces -> listener.onAnalysisProgress(BUILDING_LEAK_TRACES) + StartedComputingJavaHeapRetainedSize -> listener.onAnalysisProgress( + COMPUTING_RETAINED_SIZE + ) + + StartedComputingNativeRetainedSize -> listener.onAnalysisProgress( + COMPUTING_NATIVE_RETAINED_SIZE + ) + + StartedInspectingObjects -> listener.onAnalysisProgress(INSPECTING_OBJECTS) } - } + }.createFor(graph) - objectInspectors.forEach { inspector -> - leakReportersByPath.forEach { leakReporters -> - leakReporters.forEach { reporter -> - inspector.inspect(reporter) - } - } - } + listener.onAnalysisProgress(EXTRACTING_METADATA) + val metadata = metadataExtractor.extractMetadata(graph) - return leakReportersByPath.map { leakReporters -> - computeLeakStatuses(leakReporters) - } - } + val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph) + .count { it.isRetained && !it.hasReferent } - private fun FindLeakInput.computeRetainedSizes( - inspectedObjectsByPath: List>, - dominatorTree: DominatorTree - ): Map> { - val nodeObjectIds = inspectedObjectsByPath.flatMap { inspectedObjects -> - inspectedObjects.filter { it.leakingStatus == UNKNOWN || it.leakingStatus == LEAKING } - .map { it.heapObject.objectId } - }.toSet() - listener.onAnalysisProgress(COMPUTING_NATIVE_RETAINED_SIZE) - val nativeSizeMapper = AndroidNativeSizeMapper(graph) - val nativeSizes = nativeSizeMapper.mapNativeSizes() - listener.onAnalysisProgress(COMPUTING_RETAINED_SIZE) - val shallowSizeCalculator = ShallowSizeCalculator(graph) - return dominatorTree.computeRetainedSizes(nodeObjectIds) { objectId -> - val nativeSize = nativeSizes[objectId] ?: 0 - val shallowSize = shallowSizeCalculator.computeShallowSize(objectId) - nativeSize + shallowSize - } - } - - private fun buildLeakTraceObjects( - inspectedObjects: List, - retainedSizes: Map>? - ): List { - return inspectedObjects.map { inspectedObject -> - val heapObject = inspectedObject.heapObject - val className = recordClassName(heapObject) - - val objectType = when (heapObject) { - is HeapClass -> CLASS - is HeapObjectArray, is HeapPrimitiveArray -> ARRAY - else -> INSTANCE + // This should rarely happens, as we generally remove all cleared weak refs right before a heap + // dump. + val metadataWithCount = if (retainedClearedWeakRefCount > 0) { + metadata + ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances") + } else { + metadata } - val retainedSizeAndObjectCount = retainedSizes?.get(inspectedObject.heapObject.objectId) + listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS) + val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph) - LeakTraceObject( - type = objectType, - className = className, - labels = inspectedObject.labels, - leakingStatus = inspectedObject.leakingStatus, - leakingStatusReason = inspectedObject.leakingStatusReason, - retainedHeapByteSize = retainedSizeAndObjectCount?.first, - retainedObjectCount = retainedSizeAndObjectCount?.second + val (applicationLeaks, libraryLeaks, unreachableObjects) = leakTracer.traceObjects( + leakingObjectIds ) - } - } - private fun FindLeakInput.buildReferencePath( - shortestPath: ShortestPath, - leakTraceObjects: List - ): List { - return shortestPath.childPathWithDetails.mapIndexed { index, (childNode, details) -> - LeakTraceReference( - originObject = leakTraceObjects[index], - referenceType = when (details.locationType) { - ReferenceLocationType.INSTANCE_FIELD -> LeakTraceReference.ReferenceType.INSTANCE_FIELD - ReferenceLocationType.STATIC_FIELD -> LeakTraceReference.ReferenceType.STATIC_FIELD - ReferenceLocationType.LOCAL -> LeakTraceReference.ReferenceType.LOCAL - ReferenceLocationType.ARRAY_ENTRY -> LeakTraceReference.ReferenceType.ARRAY_ENTRY - }, - owningClassName = graph.findObjectById(details.locationClassObjectId).asClass!!.name, - referenceName = details.name + HeapAnalysisSuccess( + heapDumpFile = heapDumpFile, + createdAtTimeMillis = System.currentTimeMillis(), + analysisDurationMillis = since(analysisStartNanoTime), + metadata = metadataWithCount, + applicationLeaks = applicationLeaks, + libraryLeaks = libraryLeaks, + unreachableObjects = unreachableObjects ) - } - } - - internal class InspectedObject( - val heapObject: HeapObject, - val leakingStatus: LeakingStatus, - val leakingStatusReason: String, - val labels: MutableSet - ) - - private fun computeLeakStatuses(leakReporters: List): List { - val lastElementIndex = leakReporters.size - 1 - - var lastNotLeakingElementIndex = -1 - var firstLeakingElementIndex = lastElementIndex - - val leakStatuses = ArrayList>() - - for ((index, reporter) in leakReporters.withIndex()) { - val resolvedStatusPair = - resolveStatus(reporter, leakingWins = index == lastElementIndex).let { statusPair -> - if (index == lastElementIndex) { - // The last element should always be leaking. - when (statusPair.first) { - LEAKING -> statusPair - UNKNOWN -> LEAKING to "This is the leaking object" - NOT_LEAKING -> LEAKING to "This is the leaking object. Conflicts with ${statusPair.second}" - } - } else statusPair - } - - leakStatuses.add(resolvedStatusPair) - val (leakStatus, _) = resolvedStatusPair - if (leakStatus == NOT_LEAKING) { - lastNotLeakingElementIndex = index - // Reset firstLeakingElementIndex so that we never have - // firstLeakingElementIndex < lastNotLeakingElementIndex - firstLeakingElementIndex = lastElementIndex - } else if (leakStatus == LEAKING && firstLeakingElementIndex == lastElementIndex) { - firstLeakingElementIndex = index - } - } - - val simpleClassNames = leakReporters.map { reporter -> - recordClassName(reporter.heapObject).lastSegment('.') - } - - for (i in 0 until lastNotLeakingElementIndex) { - val (leakStatus, leakStatusReason) = leakStatuses[i] - val nextNotLeakingIndex = generateSequence(i + 1) { index -> - if (index < lastNotLeakingElementIndex) index + 1 else null - }.first { index -> - leakStatuses[index].first == NOT_LEAKING - } - - // Element is forced to NOT_LEAKING - val nextNotLeakingName = simpleClassNames[nextNotLeakingIndex] - leakStatuses[i] = when (leakStatus) { - UNKNOWN -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking" - NOT_LEAKING -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking and $leakStatusReason" - LEAKING -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking. Conflicts with $leakStatusReason" - } - } - - if (firstLeakingElementIndex < lastElementIndex - 1) { - // We already know the status of firstLeakingElementIndex and lastElementIndex - for (i in lastElementIndex - 1 downTo firstLeakingElementIndex + 1) { - val (leakStatus, leakStatusReason) = leakStatuses[i] - val previousLeakingIndex = generateSequence(i - 1) { index -> - if (index > firstLeakingElementIndex) index - 1 else null - }.first { index -> - leakStatuses[index].first == LEAKING - } - - // Element is forced to LEAKING - val previousLeakingName = simpleClassNames[previousLeakingIndex] - leakStatuses[i] = when (leakStatus) { - UNKNOWN -> LEAKING to "$previousLeakingName↑ is leaking" - LEAKING -> LEAKING to "$previousLeakingName↑ is leaking and $leakStatusReason" - NOT_LEAKING -> throw IllegalStateException("Should never happen") - } - } - } - - return leakReporters.mapIndexed { index, objectReporter -> - val (leakingStatus, leakingStatusReason) = leakStatuses[index] - InspectedObject( - objectReporter.heapObject, leakingStatus, leakingStatusReason, objectReporter.labels + } catch (exception: Throwable) { + HeapAnalysisFailure( + heapDumpFile = heapDumpFile, + createdAtTimeMillis = System.currentTimeMillis(), + analysisDurationMillis = since(analysisStartNanoTime), + exception = HeapAnalysisException(exception) ) } } - private fun resolveStatus( - reporter: ObjectReporter, - leakingWins: Boolean - ): Pair { - var status = UNKNOWN - var reason = "" - if (reporter.notLeakingReasons.isNotEmpty()) { - status = NOT_LEAKING - reason = reporter.notLeakingReasons.joinToString(" and ") - } - val leakingReasons = reporter.leakingReasons - if (leakingReasons.isNotEmpty()) { - val winReasons = leakingReasons.joinToString(" and ") - // Conflict - if (status == NOT_LEAKING) { - if (leakingWins) { - status = LEAKING - reason = "$winReasons. Conflicts with $reason" - } else { - reason += ". Conflicts with $winReasons" - } - } else { - status = LEAKING - reason = winReasons - } - } - return status to reason - } - - private fun recordClassName( - heap: HeapObject - ): String { - return when (heap) { - is HeapClass -> heap.name - is HeapInstance -> heap.instanceClassName - is HeapObjectArray -> heap.arrayClassName - is HeapPrimitiveArray -> heap.arrayClassName - } - } private fun since(analysisStartNanoTime: Long): Long { return NANOSECONDS.toMillis(System.nanoTime() - analysisStartNanoTime) diff --git a/shark/shark/src/main/java/shark/internal/JavaLocalReferenceReader.kt b/shark/shark/src/main/java/shark/JavaLocalReferenceReader.kt similarity index 87% rename from shark/shark/src/main/java/shark/internal/JavaLocalReferenceReader.kt rename to shark/shark/src/main/java/shark/JavaLocalReferenceReader.kt index 440f4db471..e945f03bcd 100644 --- a/shark/shark/src/main/java/shark/internal/JavaLocalReferenceReader.kt +++ b/shark/shark/src/main/java/shark/JavaLocalReferenceReader.kt @@ -1,15 +1,12 @@ -package shark.internal +package shark -import shark.HeapGraph -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader import shark.HeapObject.HeapInstance -import shark.IgnoredReferenceMatcher -import shark.LibraryLeakReferenceMatcher -import shark.internal.Reference.LazyDetails -import shark.internal.ReferenceLocationType.LOCAL -import shark.ReferenceMatcher +import shark.Reference.LazyDetails +import shark.ReferenceLocationType.LOCAL import shark.ReferencePattern.JavaLocalPattern -import shark.filterFor +import shark.internal.JavaFrames +import shark.internal.ThreadObjects internal class JavaLocalReferenceReader( val graph: HeapGraph, diff --git a/shark/shark/src/main/java/shark/LeakTracer.kt b/shark/shark/src/main/java/shark/LeakTracer.kt new file mode 100644 index 0000000000..b84d86c405 --- /dev/null +++ b/shark/shark/src/main/java/shark/LeakTracer.kt @@ -0,0 +1,11 @@ +package shark + +fun interface LeakTracer { + + // TODO What should we do about exceptions here? union error or exception? + fun traceObjects(objectIds: Set): LeaksAndUnreachableObjects + + fun interface Factory { + fun createFor(heapGraph: HeapGraph): LeakTracer + } +} diff --git a/shark/shark/src/main/java/shark/LeakingObjectFinder.kt b/shark/shark/src/main/java/shark/LeakingObjectFinder.kt index 8ce1a38ec0..70fbde6c15 100644 --- a/shark/shark/src/main/java/shark/LeakingObjectFinder.kt +++ b/shark/shark/src/main/java/shark/LeakingObjectFinder.kt @@ -3,8 +3,6 @@ package shark /** * Finds the objects that are leaking, for which Shark will compute * leak traces. - * - * This is a functional interface with which you can create a [LeakingObjectFinder] from a lambda. */ fun interface LeakingObjectFinder { diff --git a/shark/shark/src/main/java/shark/LeaksAndUnreachableObjects.kt b/shark/shark/src/main/java/shark/LeaksAndUnreachableObjects.kt new file mode 100644 index 0000000000..c72e091cae --- /dev/null +++ b/shark/shark/src/main/java/shark/LeaksAndUnreachableObjects.kt @@ -0,0 +1,8 @@ +package shark + +// TODO Name? +data class LeaksAndUnreachableObjects( + val applicationLeaks: List, + val libraryLeaks: List, + val unreachableObjects: List +) diff --git a/shark/shark/src/main/java/shark/internal/GcRootProvider.kt b/shark/shark/src/main/java/shark/MatchingGcRootProvider.kt similarity index 73% rename from shark/shark/src/main/java/shark/internal/GcRootProvider.kt rename to shark/shark/src/main/java/shark/MatchingGcRootProvider.kt index 85ccbbbc63..247543037f 100644 --- a/shark/shark/src/main/java/shark/internal/GcRootProvider.kt +++ b/shark/shark/src/main/java/shark/MatchingGcRootProvider.kt @@ -1,57 +1,41 @@ -package shark.internal +package shark -import shark.GcRoot import shark.GcRoot.JavaFrame import shark.GcRoot.JniGlobal import shark.GcRoot.ThreadObject -import shark.HeapGraph -import shark.HeapObject import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray import shark.HeapObject.HeapPrimitiveArray -import shark.IgnoredReferenceMatcher -import shark.LibraryLeakReferenceMatcher -import shark.ReferenceMatcher import shark.ReferencePattern.NativeGlobalVariablePattern -import shark.filterFor +import shark.internal.ThreadObjects /** - * Extracted from PathFinder, this should eventually be part of public API surface - * and we should likely also revisit the gc root type filtering which happens during - * heap parsing, as that's not really a concern for the heap parser and more for path - * finding. There are probably memory concerns as well there though. We could: - * - compress the storing of these roots - * - keep only the roots locations and read / deserialize as needed - * - Ensure a unique / consistent view of roots by doing the work of GcRootProvider - * at parsing time and keeping that list. + * TODO Extracted from PathFinder, this should eventually be part of public API surface + * and we should likely also revisit the gc root type filtering which happens during + * heap parsing, as that's not really a concern for the heap parser and more for path + * finding. There are probably memory concerns as well there though. We could: + * - compress the storing of these roots + * - keep only the roots locations and read / deserialize as needed + * - Ensure a unique / consistent view of roots by doing the work of GcRootProvider + * at parsing time and keeping that list. + * + * A [GcRootProvider] that matches roots against [referenceMatchers]. */ -internal class GcRootProvider( - private val graph: HeapGraph, - referenceMatchers: List -) { +class MatchingGcRootProvider( + private val referenceMatchers: List +) : GcRootProvider { - private val jniGlobalReferenceMatchers: Map - - init { - val jniGlobals = mutableMapOf() + override fun provideGcRoots(graph: HeapGraph): Sequence { + val jniGlobalReferenceMatchers = mutableMapOf() referenceMatchers.filterFor(graph).forEach { referenceMatcher -> val pattern = referenceMatcher.pattern if (pattern is NativeGlobalVariablePattern) { - jniGlobals[pattern.className] = referenceMatcher + jniGlobalReferenceMatchers[pattern.className] = referenceMatcher } } - this.jniGlobalReferenceMatchers = jniGlobals - } - - class GcRootReference( - val gcRoot: GcRoot, - val isLowPriority: Boolean, - val matchedLibraryLeak: LibraryLeakReferenceMatcher?, - ) - fun provideGcRoots(): Sequence { - return sortedGcRoots().asSequence().mapNotNull { (heapObject, gcRoot) -> + return sortedGcRoots(graph).asSequence().mapNotNull { (heapObject, gcRoot) -> when (gcRoot) { // Note: in sortedGcRoots we already filter out any java frame that has an associated // thread. These are the remaining ones (shouldn't be any, this is just in case). @@ -104,7 +88,7 @@ internal class GcRootProvider( * This ensures ThreadObjects are visited before JavaFrames, and threadsBySerialNumber can be * built before JavaFrames. */ - private fun sortedGcRoots(): List> { + private fun sortedGcRoots(graph: HeapGraph): List> { val rootClassName: (HeapObject) -> String = { graphObject -> when (graphObject) { is HeapClass -> { diff --git a/shark/shark/src/main/java/shark/MetadataExtractor.kt b/shark/shark/src/main/java/shark/MetadataExtractor.kt index 7434082257..a690b328f4 100644 --- a/shark/shark/src/main/java/shark/MetadataExtractor.kt +++ b/shark/shark/src/main/java/shark/MetadataExtractor.kt @@ -2,8 +2,6 @@ package shark /** * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata]. - * - * This is a functional interface with which you can create a [MetadataExtractor] from a lambda. */ fun interface MetadataExtractor { fun extractMetadata(graph: HeapGraph): Map diff --git a/shark/shark/src/main/java/shark/internal/ObjectArrayReferenceReader.kt b/shark/shark/src/main/java/shark/ObjectArrayReferenceReader.kt similarity index 92% rename from shark/shark/src/main/java/shark/internal/ObjectArrayReferenceReader.kt rename to shark/shark/src/main/java/shark/ObjectArrayReferenceReader.kt index 9c52d43823..0e5b429060 100644 --- a/shark/shark/src/main/java/shark/internal/ObjectArrayReferenceReader.kt +++ b/shark/shark/src/main/java/shark/ObjectArrayReferenceReader.kt @@ -1,9 +1,8 @@ -package shark.internal +package shark import shark.HeapObject.HeapObjectArray -import shark.internal.Reference.LazyDetails -import shark.internal.ReferenceLocationType.ARRAY_ENTRY -import shark.ValueHolder +import shark.Reference.LazyDetails +import shark.ReferenceLocationType.ARRAY_ENTRY internal class ObjectArrayReferenceReader : ReferenceReader { override fun read(source: HeapObjectArray): Sequence { diff --git a/shark/shark/src/main/java/shark/internal/ObjectDominators.kt b/shark/shark/src/main/java/shark/ObjectDominators.kt similarity index 85% rename from shark/shark/src/main/java/shark/internal/ObjectDominators.kt rename to shark/shark/src/main/java/shark/ObjectDominators.kt index 94149f5ef2..e467bc51f8 100644 --- a/shark/shark/src/main/java/shark/internal/ObjectDominators.kt +++ b/shark/shark/src/main/java/shark/ObjectDominators.kt @@ -1,14 +1,11 @@ -package shark.internal +package shark import shark.GcRoot.ThreadObject -import shark.HeapGraph import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray import shark.HeapObject.HeapPrimitiveArray -import shark.IgnoredReferenceMatcher -import shark.OnAnalysisProgressListener -import shark.ValueHolder +import shark.internal.ShallowSizeCalculator /** * Exposes high level APIs to compute and render a dominator tree. This class @@ -22,7 +19,7 @@ import shark.ValueHolder */ class ObjectDominators { - internal data class DominatorNode( + data class DominatorNode( val shallowSize: Int, val retainedSize: Int, val retainedCount: Int, @@ -138,23 +135,20 @@ class ObjectDominators { graph: HeapGraph, ignoredRefs: List ): Map { - val referenceReader = DelegatingObjectReferenceReader( - classReferenceReader = ClassReferenceReader(graph, emptyList()), - instanceReferenceReader = ChainingInstanceReferenceReader( - listOf(JavaLocalReferenceReader(graph, emptyList())), - FieldInstanceReferenceReader(graph, emptyList()) - ), objectArrayReferenceReader = ObjectArrayReferenceReader() - ) + val pathFinder = PrioritizingShortestPathFinder.Factory( + listener = { }, + referenceReaderFactory = ActualMatchingReferenceReaderFactory( + referenceMatchers = emptyList() + ), + gcRootProvider = MatchingGcRootProvider(ignoredRefs), + computeRetainedHeapSize = true, + ).createFor(graph) - val pathFinder = PathFinder( - graph, - OnAnalysisProgressListener.NO_OP, referenceReader, ignoredRefs - ) val nativeSizeMapper = AndroidNativeSizeMapper(graph) val nativeSizes = nativeSizeMapper.mapNativeSizes() val shallowSizeCalculator = ShallowSizeCalculator(graph) - val result = pathFinder.findPathsFromGcRoots(setOf(), true) + val result = pathFinder.findShortestPathsFromGcRoots(setOf()) return result.dominatorTree!!.buildFullDominatorTree { objectId -> val nativeSize = nativeSizes[objectId] ?: 0 val shallowSize = shallowSizeCalculator.computeShallowSize(objectId) diff --git a/shark/shark/src/main/java/shark/ObjectInspector.kt b/shark/shark/src/main/java/shark/ObjectInspector.kt index 5073e37f2c..7a08075f2b 100644 --- a/shark/shark/src/main/java/shark/ObjectInspector.kt +++ b/shark/shark/src/main/java/shark/ObjectInspector.kt @@ -5,8 +5,6 @@ package shark * heap. [inspect] will be called for each object that LeakCanary wants to know more about. * The implementation can then use the provided [ObjectReporter] to provide insights for that * object. - * - * This is a functional interface with which you can create a [ObjectInspector] from a lambda. */ fun interface ObjectInspector { diff --git a/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt b/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt index 0db4e13f77..42debf0130 100644 --- a/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt +++ b/shark/shark/src/main/java/shark/OnAnalysisProgressListener.kt @@ -4,8 +4,6 @@ import java.util.Locale /** * Reports progress from the [HeapAnalyzer] as they occur, as [Step] values. - * - * This is a functional interface with which you can create a [OnAnalysisProgressListener] from a lambda. */ fun interface OnAnalysisProgressListener { diff --git a/shark/shark/src/main/java/shark/internal/OpenJdkInstanceRefReaders.kt b/shark/shark/src/main/java/shark/OpenJdkInstanceRefReaders.kt similarity index 95% rename from shark/shark/src/main/java/shark/internal/OpenJdkInstanceRefReaders.kt rename to shark/shark/src/main/java/shark/OpenJdkInstanceRefReaders.kt index 8eaccae659..36f256828c 100644 --- a/shark/shark/src/main/java/shark/internal/OpenJdkInstanceRefReaders.kt +++ b/shark/shark/src/main/java/shark/OpenJdkInstanceRefReaders.kt @@ -1,9 +1,11 @@ -package shark.internal +package shark -import shark.HeapGraph -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader import shark.HeapObject.HeapInstance +import shark.internal.InternalSharedArrayListReferenceReader +import shark.internal.InternalSharedHashMapReferenceReader +import shark.internal.InternalSharedLinkedListReferenceReader /** * Defines [VirtualInstanceReferenceReader] factories for common OpenJDK data structures. diff --git a/shark/shark/src/main/java/shark/PathFindingResults.kt b/shark/shark/src/main/java/shark/PathFindingResults.kt new file mode 100644 index 0000000000..b4faedecbf --- /dev/null +++ b/shark/shark/src/main/java/shark/PathFindingResults.kt @@ -0,0 +1,9 @@ +package shark + +import shark.internal.ReferencePathNode + +// TODO Class name +class PathFindingResults( + val pathsToLeakingObjects: List, + val dominatorTree: DominatorTree? +) diff --git a/shark/shark/src/main/java/shark/internal/PathFinder.kt b/shark/shark/src/main/java/shark/PrioritizingShortestPathFinder.kt similarity index 78% rename from shark/shark/src/main/java/shark/internal/PathFinder.kt rename to shark/shark/src/main/java/shark/PrioritizingShortestPathFinder.kt index c329b18296..7fe364d299 100644 --- a/shark/shark/src/main/java/shark/internal/PathFinder.kt +++ b/shark/shark/src/main/java/shark/PrioritizingShortestPathFinder.kt @@ -1,18 +1,14 @@ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") -package shark.internal +package shark import java.util.ArrayDeque import java.util.Deque -import shark.HeapGraph -import shark.HeapObject -import shark.OnAnalysisProgressListener -import shark.OnAnalysisProgressListener.Step.FINDING_DOMINATORS -import shark.OnAnalysisProgressListener.Step.FINDING_PATHS_TO_RETAINED_OBJECTS -import shark.ReferenceMatcher -import shark.ValueHolder -import shark.internal.PathFinder.VisitTracker.Dominated -import shark.internal.PathFinder.VisitTracker.Visited +import shark.PrioritizingShortestPathFinder.Event.StartedFindingDominators +import shark.PrioritizingShortestPathFinder.Event.StartedFindingPathsToRetainedObjects +import shark.PrioritizingShortestPathFinder.VisitTracker.Dominated +import shark.PrioritizingShortestPathFinder.VisitTracker.Visited +import shark.internal.ReferencePathNode import shark.internal.ReferencePathNode.ChildNode import shark.internal.ReferencePathNode.RootNode import shark.internal.ReferencePathNode.RootNode.LibraryLeakRootNode @@ -26,19 +22,43 @@ import shark.internal.hppc.LongScatterSet * identified as "to visit last" and then visiting them as needed if no path is * found. */ -internal class PathFinder( +class PrioritizingShortestPathFinder private constructor( private val graph: HeapGraph, - private val listener: OnAnalysisProgressListener, + private val listener: Event.Listener, private val objectReferenceReader: ReferenceReader, - referenceMatchers: List -) { + private val gcRootProvider: GcRootProvider, + private val computeRetainedHeapSize: Boolean +) : ShortestPathFinder { + + class Factory( + private val listener: Event.Listener, + private val referenceReaderFactory: ReferenceReader.Factory, + private val gcRootProvider: GcRootProvider, + private val computeRetainedHeapSize: Boolean + ) : ShortestPathFinder.Factory { + override fun createFor(heapGraph: HeapGraph): ShortestPathFinder { + return PrioritizingShortestPathFinder( + graph = heapGraph, + listener = listener, + objectReferenceReader = referenceReaderFactory.createFor(heapGraph), + gcRootProvider = gcRootProvider, + computeRetainedHeapSize = computeRetainedHeapSize + ) + } + } - class PathFindingResults( - val pathsToLeakingObjects: List, - val dominatorTree: DominatorTree? - ) + // TODO Enum or sealed? class makes it possible to report progress. Enum + // provides ordering of events. + sealed interface Event { + object StartedFindingPathsToRetainedObjects : Event + object StartedFindingDominators : Event - sealed class VisitTracker { + fun interface Listener { + fun onEvent(event: Event) + } + } + + private sealed class VisitTracker { abstract fun visited( objectId: Long, @@ -47,7 +67,7 @@ internal class PathFinder( class Dominated(expectedElements: Int) : VisitTracker() { /** - * Tracks visited objecs and their dominator. + * Tracks visited objects and their dominator. * If an object is not in [dominatorTree] then it hasn't been enqueued yet. * If an object is in [dominatorTree] but not in [State.toVisitSet] nor [State.toVisitLastSet] * then it has already been dequeued. @@ -115,13 +135,10 @@ internal class PathFinder( var visitingLast = false } - private val gcRootProvider = GcRootProvider(graph, referenceMatchers) - - fun findPathsFromGcRoots( - leakingObjectIds: Set, - computeRetainedHeapSize: Boolean + override fun findShortestPathsFromGcRoots( + leakingObjectIds: Set ): PathFindingResults { - listener.onAnalysisProgress(FINDING_PATHS_TO_RETAINED_OBJECTS) + listener.onEvent(StartedFindingPathsToRetainedObjects) // Estimate of how many objects we'll visit. This is a conservative estimate, we should always // visit more than that but this limits the number of early array growths. val estimatedVisitedObjects = (graph.instanceCount / 2).coerceAtLeast(4) @@ -153,7 +170,7 @@ internal class PathFinder( // Found all refs, stop searching (unless computing retained size) if (shortestPathsToLeakingObjects.size == leakingObjectIds.size()) { if (computeRetainedHeapSize) { - listener.onAnalysisProgress(FINDING_DOMINATORS) + listener.onEvent(StartedFindingDominators) } else { break@visitingQueue } @@ -190,7 +207,7 @@ internal class PathFinder( } private fun State.enqueueGcRoots() { - gcRootProvider.provideGcRoots().forEach { gcRootReference -> + gcRootProvider.provideGcRoots(graph).forEach { gcRootReference -> enqueue( gcRootReference.matchedLibraryLeak?.let { matchedLibraryLeak -> LibraryLeakRootNode( @@ -242,10 +259,12 @@ internal class PathFinder( toVisitLastSet.remove(node.objectId) } } + visitLast -> { toVisitLastQueue.add(node) toVisitLastSet.add(node.objectId) } + else -> { toVisitQueue.add(node) toVisitSet.add(node.objectId) diff --git a/shark/shark/src/main/java/shark/RealLeakTracerFactory.kt b/shark/shark/src/main/java/shark/RealLeakTracerFactory.kt new file mode 100644 index 0000000000..952cdd6dcf --- /dev/null +++ b/shark/shark/src/main/java/shark/RealLeakTracerFactory.kt @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2015 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package shark + +import shark.HeapObject.HeapClass +import shark.HeapObject.HeapInstance +import shark.HeapObject.HeapObjectArray +import shark.HeapObject.HeapPrimitiveArray +import shark.LeakTrace.GcRootType +import shark.LeakTraceObject.LeakingStatus +import shark.LeakTraceObject.LeakingStatus.LEAKING +import shark.LeakTraceObject.LeakingStatus.NOT_LEAKING +import shark.LeakTraceObject.LeakingStatus.UNKNOWN +import shark.LeakTraceObject.ObjectType.ARRAY +import shark.LeakTraceObject.ObjectType.CLASS +import shark.LeakTraceObject.ObjectType.INSTANCE +import shark.internal.ReferencePathNode +import shark.internal.ReferencePathNode.ChildNode +import shark.internal.ReferencePathNode.RootNode +import shark.internal.ShallowSizeCalculator +import shark.internal.createSHA1Hash +import shark.internal.lastSegment +import java.util.ArrayList +import shark.RealLeakTracerFactory.Event.StartedBuildingLeakTraces +import shark.RealLeakTracerFactory.Event.StartedComputingJavaHeapRetainedSize +import shark.RealLeakTracerFactory.Event.StartedComputingNativeRetainedSize +import shark.RealLeakTracerFactory.Event.StartedInspectingObjects +import shark.RealLeakTracerFactory.TrieNode.LeafNode +import shark.RealLeakTracerFactory.TrieNode.ParentNode +import shark.internal.ReferencePathNode.RootNode.LibraryLeakRootNode + +// TODO kdoc +// TODO better name than "real" +// TODO Think about making this easier to build up. Having good defaults, or a builder of sorts. +// objectInspectors and referenceMatchers can default to empty list and listener be a no-op +class RealLeakTracerFactory constructor( + private val shortestPathFinderFactory: ShortestPathFinder.Factory, + private val objectInspectors: List, + private val listener: Event.Listener +): LeakTracer.Factory { + + // TODO Enum or sealed? class makes it possible to report progress. Enum + // provides ordering of events. + sealed interface Event { + object StartedBuildingLeakTraces : Event + object StartedInspectingObjects : Event + object StartedComputingNativeRetainedSize: Event + object StartedComputingJavaHeapRetainedSize: Event + + fun interface Listener { + fun onEvent(event: Event) + } + } + + override fun createFor(heapGraph: HeapGraph): LeakTracer { + // TODO Remove the listener and replace that by specific events + // Also for each event some notion of progress? Should that be configurable? + // We should be able to tell the total number of objects so we'll know when we've + // traversed the whole graph. + // referenceMatchers are only needed for the NativeGlobalVariablePattern, which is related + // to GC roots + return LeakTracer { objectIds-> + val helpers = FindLeakInput( + heapGraph, + shortestPathFinderFactory.createFor(heapGraph), + objectInspectors, + ) + helpers.findLeaks(objectIds) + } + } + + + private class FindLeakInput( + val graph: HeapGraph, + val shortestPathFinder: ShortestPathFinder, + val objectInspectors: List, + ) + + private fun FindLeakInput.findLeaks(leakingObjectIds: Set): LeaksAndUnreachableObjects { + val pathFindingResults = + shortestPathFinder.findShortestPathsFromGcRoots(leakingObjectIds) + + val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds) + + val shortestPaths = + deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects) + + val inspectedObjectsByPath = inspectObjects(shortestPaths) + + val retainedSizes = + if (pathFindingResults.dominatorTree != null) { + computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree) + } else { + null + } + val (applicationLeaks, libraryLeaks) = buildLeakTraces( + shortestPaths, inspectedObjectsByPath, retainedSizes + ) + return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects) + } + + private fun FindLeakInput.findUnreachableObjects( + pathFindingResults: PathFindingResults, + leakingObjectIds: Set + ): List { + val reachableLeakingObjectIds = + pathFindingResults.pathsToLeakingObjects.map { it.objectId }.toSet() + + val unreachableLeakingObjectIds = leakingObjectIds - reachableLeakingObjectIds + + val unreachableObjectReporters = unreachableLeakingObjectIds.map { objectId -> + ObjectReporter(heapObject = graph.findObjectById(objectId)) + } + + objectInspectors.forEach { inspector -> + unreachableObjectReporters.forEach { reporter -> + inspector.inspect(reporter) + } + } + + val unreachableInspectedObjects = unreachableObjectReporters.map { reporter -> + val reason = resolveStatus(reporter, leakingWins = true).let { (status, reason) -> + when (status) { + LEAKING -> reason + UNKNOWN -> "This is a leaking object" + NOT_LEAKING -> "This is a leaking object. Conflicts with $reason" + } + } + InspectedObject( + reporter.heapObject, LEAKING, reason, reporter.labels + ) + } + + return buildLeakTraceObjects(unreachableInspectedObjects, null) + } + + internal sealed class TrieNode { + abstract val objectId: Long + + class ParentNode(override val objectId: Long) : TrieNode() { + val children = mutableMapOf() + override fun toString(): String { + return "ParentNode(objectId=$objectId, children=$children)" + } + } + + class LeafNode( + override val objectId: Long, + val pathNode: ReferencePathNode + ) : TrieNode() + } + + private fun deduplicateShortestPaths( + inputPathResults: List + ): List { + val rootTrieNode = ParentNode(0) + + inputPathResults.forEach { pathNode -> + // Go through the linked list of nodes and build the reverse list of instances from + // root to leaking. + val path = mutableListOf() + var leakNode: ReferencePathNode = pathNode + while (leakNode is ChildNode) { + path.add(0, leakNode.objectId) + leakNode = leakNode.parent + } + path.add(0, leakNode.objectId) + updateTrie(pathNode, path, 0, rootTrieNode) + } + + val outputPathResults = mutableListOf() + findResultsInTrie(rootTrieNode, outputPathResults) + + if (outputPathResults.size != inputPathResults.size) { + SharkLog.d { + "Found ${inputPathResults.size} paths to retained objects," + + " down to ${outputPathResults.size} after removing duplicated paths" + } + } else { + SharkLog.d { "Found ${outputPathResults.size} paths to retained objects" } + } + + return outputPathResults.map { retainedObjectNode -> + val shortestChildPath = mutableListOf() + var node = retainedObjectNode + while (node is ChildNode) { + shortestChildPath.add(0, node) + node = node.parent + } + val rootNode = node as RootNode + ShortestPath(rootNode, shortestChildPath) + } + } + + private fun updateTrie( + pathNode: ReferencePathNode, + path: List, + pathIndex: Int, + parentNode: ParentNode + ) { + val objectId = path[pathIndex] + if (pathIndex == path.lastIndex) { + parentNode.children[objectId] = LeafNode(objectId, pathNode) + } else { + val childNode = parentNode.children[objectId] ?: run { + val newChildNode = ParentNode(objectId) + parentNode.children[objectId] = newChildNode + newChildNode + } + if (childNode is ParentNode) { + updateTrie(pathNode, path, pathIndex + 1, childNode) + } + } + } + + private fun findResultsInTrie( + parentNode: ParentNode, + outputPathResults: MutableList + ) { + parentNode.children.values.forEach { childNode -> + when (childNode) { + is ParentNode -> { + findResultsInTrie(childNode, outputPathResults) + } + is LeafNode -> { + outputPathResults += childNode.pathNode + } + } + } + } + + internal class ShortestPath( + val root: RootNode, + childPath: List + ) { + + val childPathWithDetails = childPath.map { it to it.lazyDetailsResolver.resolve() } + + fun firstLibraryLeakMatcher(): LibraryLeakReferenceMatcher? { + if (root is LibraryLeakRootNode) { + return root.matcher + } + return childPathWithDetails.map { it.second.matchedLibraryLeak }.firstOrNull { it != null } + } + + fun asNodesWithMatchers(): List> { + val rootMatcher = if (root is LibraryLeakRootNode) { + root.matcher + } else null + val childPathWithMatchers = + childPathWithDetails.map { it.first to it.second.matchedLibraryLeak } + return listOf(root to rootMatcher) + childPathWithMatchers + } + } + + private fun FindLeakInput.buildLeakTraces( + shortestPaths: List, + inspectedObjectsByPath: List>, + retainedSizes: Map>? + ): Pair, List> { + listener.onEvent(StartedBuildingLeakTraces) + + val applicationLeaksMap = mutableMapOf>() + val libraryLeaksMap = + mutableMapOf>>() + + shortestPaths.forEachIndexed { pathIndex, shortestPath -> + val inspectedObjects = inspectedObjectsByPath[pathIndex] + + val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes) + + val referencePath = buildReferencePath(shortestPath, leakTraceObjects) + + val leakTrace = LeakTrace( + gcRootType = GcRootType.fromGcRoot(shortestPath.root.gcRoot), + referencePath = referencePath, + leakingObject = leakTraceObjects.last() + ) + + val firstLibraryLeakMatcher = shortestPath.firstLibraryLeakMatcher() + if (firstLibraryLeakMatcher != null) { + val signature: String = firstLibraryLeakMatcher.pattern.toString() + .createSHA1Hash() + libraryLeaksMap.getOrPut(signature) { firstLibraryLeakMatcher to mutableListOf() } + .second += leakTrace + } else { + applicationLeaksMap.getOrPut(leakTrace.signature) { mutableListOf() } += leakTrace + } + } + val applicationLeaks = applicationLeaksMap.map { (_, leakTraces) -> + ApplicationLeak(leakTraces) + } + val libraryLeaks = libraryLeaksMap.map { (_, pair) -> + val (matcher, leakTraces) = pair + LibraryLeak(leakTraces, matcher.pattern, matcher.description) + } + return applicationLeaks to libraryLeaks + } + + private fun FindLeakInput.inspectObjects(shortestPaths: List): List> { + listener.onEvent(StartedInspectingObjects) + + val leakReportersByPath = shortestPaths.map { path -> + val pathList = path.asNodesWithMatchers() + pathList + .mapIndexed { index, (node, _) -> + val reporter = ObjectReporter(heapObject = graph.findObjectById(node.objectId)) + if (index + 1 < pathList.size) { + val (_, nextMatcher) = pathList[index + 1] + if (nextMatcher != null) { + reporter.labels += "Library leak match: ${nextMatcher.pattern}" + } + } + reporter + } + } + + objectInspectors.forEach { inspector -> + leakReportersByPath.forEach { leakReporters -> + leakReporters.forEach { reporter -> + inspector.inspect(reporter) + } + } + } + + return leakReportersByPath.map { leakReporters -> + computeLeakStatuses(leakReporters) + } + } + + private fun FindLeakInput.computeRetainedSizes( + inspectedObjectsByPath: List>, + dominatorTree: DominatorTree + ): Map> { + val nodeObjectIds = inspectedObjectsByPath.flatMap { inspectedObjects -> + // TODO Stop at the first leaking object + inspectedObjects.filter { it.leakingStatus == UNKNOWN || it.leakingStatus == LEAKING } + .map { it.heapObject.objectId } + }.toSet() + listener.onEvent(StartedComputingNativeRetainedSize) + val nativeSizeMapper = AndroidNativeSizeMapper(graph) + val nativeSizes = nativeSizeMapper.mapNativeSizes() + listener.onEvent(StartedComputingJavaHeapRetainedSize) + val shallowSizeCalculator = ShallowSizeCalculator(graph) + return dominatorTree.computeRetainedSizes(nodeObjectIds) { objectId -> + val nativeSize = nativeSizes[objectId] ?: 0 + val shallowSize = shallowSizeCalculator.computeShallowSize(objectId) + nativeSize + shallowSize + } + } + + private fun buildLeakTraceObjects( + inspectedObjects: List, + retainedSizes: Map>? + ): List { + return inspectedObjects.map { inspectedObject -> + val heapObject = inspectedObject.heapObject + val className = recordClassName(heapObject) + + val objectType = when (heapObject) { + is HeapClass -> CLASS + is HeapObjectArray, is HeapPrimitiveArray -> ARRAY + else -> INSTANCE + } + + val retainedSizeAndObjectCount = retainedSizes?.get(inspectedObject.heapObject.objectId) + + LeakTraceObject( + type = objectType, + className = className, + labels = inspectedObject.labels, + leakingStatus = inspectedObject.leakingStatus, + leakingStatusReason = inspectedObject.leakingStatusReason, + retainedHeapByteSize = retainedSizeAndObjectCount?.first, + retainedObjectCount = retainedSizeAndObjectCount?.second + ) + } + } + + private fun FindLeakInput.buildReferencePath( + shortestPath: ShortestPath, + leakTraceObjects: List + ): List { + return shortestPath.childPathWithDetails.mapIndexed { index, (_, details) -> + LeakTraceReference( + originObject = leakTraceObjects[index], + referenceType = when (details.locationType) { + ReferenceLocationType.INSTANCE_FIELD -> LeakTraceReference.ReferenceType.INSTANCE_FIELD + ReferenceLocationType.STATIC_FIELD -> LeakTraceReference.ReferenceType.STATIC_FIELD + ReferenceLocationType.LOCAL -> LeakTraceReference.ReferenceType.LOCAL + ReferenceLocationType.ARRAY_ENTRY -> LeakTraceReference.ReferenceType.ARRAY_ENTRY + }, + owningClassName = graph.findObjectById(details.locationClassObjectId).asClass!!.name, + referenceName = details.name + ) + } + } + + internal class InspectedObject( + val heapObject: HeapObject, + val leakingStatus: LeakingStatus, + val leakingStatusReason: String, + val labels: MutableSet + ) + + private fun computeLeakStatuses(leakReporters: List): List { + val lastElementIndex = leakReporters.size - 1 + + var lastNotLeakingElementIndex = -1 + var firstLeakingElementIndex = lastElementIndex + + val leakStatuses = ArrayList>() + + for ((index, reporter) in leakReporters.withIndex()) { + val resolvedStatusPair = + resolveStatus(reporter, leakingWins = index == lastElementIndex).let { statusPair -> + if (index == lastElementIndex) { + // The last element should always be leaking. + when (statusPair.first) { + LEAKING -> statusPair + UNKNOWN -> LEAKING to "This is the leaking object" + NOT_LEAKING -> LEAKING to "This is the leaking object. Conflicts with ${statusPair.second}" + } + } else statusPair + } + + leakStatuses.add(resolvedStatusPair) + val (leakStatus, _) = resolvedStatusPair + if (leakStatus == NOT_LEAKING) { + lastNotLeakingElementIndex = index + // Reset firstLeakingElementIndex so that we never have + // firstLeakingElementIndex < lastNotLeakingElementIndex + firstLeakingElementIndex = lastElementIndex + } else if (leakStatus == LEAKING && firstLeakingElementIndex == lastElementIndex) { + firstLeakingElementIndex = index + } + } + + val simpleClassNames = leakReporters.map { reporter -> + recordClassName(reporter.heapObject).lastSegment('.') + } + + for (i in 0 until lastNotLeakingElementIndex) { + val (leakStatus, leakStatusReason) = leakStatuses[i] + val nextNotLeakingIndex = generateSequence(i + 1) { index -> + if (index < lastNotLeakingElementIndex) index + 1 else null + }.first { index -> + leakStatuses[index].first == NOT_LEAKING + } + + // Element is forced to NOT_LEAKING + val nextNotLeakingName = simpleClassNames[nextNotLeakingIndex] + leakStatuses[i] = when (leakStatus) { + UNKNOWN -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking" + NOT_LEAKING -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking and $leakStatusReason" + LEAKING -> NOT_LEAKING to "$nextNotLeakingName↓ is not leaking. Conflicts with $leakStatusReason" + } + } + + if (firstLeakingElementIndex < lastElementIndex - 1) { + // We already know the status of firstLeakingElementIndex and lastElementIndex + for (i in lastElementIndex - 1 downTo firstLeakingElementIndex + 1) { + val (leakStatus, leakStatusReason) = leakStatuses[i] + val previousLeakingIndex = generateSequence(i - 1) { index -> + if (index > firstLeakingElementIndex) index - 1 else null + }.first { index -> + leakStatuses[index].first == LEAKING + } + + // Element is forced to LEAKING + val previousLeakingName = simpleClassNames[previousLeakingIndex] + leakStatuses[i] = when (leakStatus) { + UNKNOWN -> LEAKING to "$previousLeakingName↑ is leaking" + LEAKING -> LEAKING to "$previousLeakingName↑ is leaking and $leakStatusReason" + NOT_LEAKING -> throw IllegalStateException("Should never happen") + } + } + } + + return leakReporters.mapIndexed { index, objectReporter -> + val (leakingStatus, leakingStatusReason) = leakStatuses[index] + InspectedObject( + objectReporter.heapObject, leakingStatus, leakingStatusReason, objectReporter.labels + ) + } + } + + private fun resolveStatus( + reporter: ObjectReporter, + leakingWins: Boolean + ): Pair { + var status = UNKNOWN + var reason = "" + if (reporter.notLeakingReasons.isNotEmpty()) { + status = NOT_LEAKING + reason = reporter.notLeakingReasons.joinToString(" and ") + } + val leakingReasons = reporter.leakingReasons + if (leakingReasons.isNotEmpty()) { + val winReasons = leakingReasons.joinToString(" and ") + // Conflict + if (status == NOT_LEAKING) { + if (leakingWins) { + status = LEAKING + reason = "$winReasons. Conflicts with $reason" + } else { + reason += ". Conflicts with $winReasons" + } + } else { + status = LEAKING + reason = winReasons + } + } + return status to reason + } + + private fun recordClassName( + heap: HeapObject + ): String { + return when (heap) { + is HeapClass -> heap.name + is HeapInstance -> heap.instanceClassName + is HeapObjectArray -> heap.arrayClassName + is HeapPrimitiveArray -> heap.arrayClassName + } + } +} diff --git a/shark/shark/src/main/java/shark/internal/Reference.kt b/shark/shark/src/main/java/shark/Reference.kt similarity index 91% rename from shark/shark/src/main/java/shark/internal/Reference.kt rename to shark/shark/src/main/java/shark/Reference.kt index 8fe675e9e8..944c881f3c 100644 --- a/shark/shark/src/main/java/shark/internal/Reference.kt +++ b/shark/shark/src/main/java/shark/Reference.kt @@ -1,9 +1,11 @@ -package shark.internal +package shark -import shark.LibraryLeakReferenceMatcher -import shark.internal.Reference.LazyDetails.Resolver +import shark.Reference.LazyDetails.Resolver -internal class Reference( +/** + * TODO Review as public API. + */ +class Reference( /** * The value of the reference, i.e. the object the reference is pointing to. */ diff --git a/shark/shark/src/main/java/shark/ReferenceLocationType.kt b/shark/shark/src/main/java/shark/ReferenceLocationType.kt new file mode 100644 index 0000000000..78010df4de --- /dev/null +++ b/shark/shark/src/main/java/shark/ReferenceLocationType.kt @@ -0,0 +1,11 @@ +package shark + +/** + * TODO This is quite similar to the leaktrace equivalent + */ +enum class ReferenceLocationType { + INSTANCE_FIELD, + STATIC_FIELD, + LOCAL, + ARRAY_ENTRY +} diff --git a/shark/shark/src/main/java/shark/internal/ReferenceReader.kt b/shark/shark/src/main/java/shark/ReferenceReader.kt similarity index 74% rename from shark/shark/src/main/java/shark/internal/ReferenceReader.kt rename to shark/shark/src/main/java/shark/ReferenceReader.kt index 07cad4618e..a630e62fcd 100644 --- a/shark/shark/src/main/java/shark/internal/ReferenceReader.kt +++ b/shark/shark/src/main/java/shark/ReferenceReader.kt @@ -1,9 +1,7 @@ -package shark.internal +package shark -import shark.HeapGraph -import shark.HeapObject - -internal fun interface ReferenceReader { +// TODO Reevaluate name. Maybe not "Reader"? Something about navigating? +fun interface ReferenceReader { /** * Returns the sequences of non null outgoing references from [source]. Outgoing refs @@ -19,6 +17,6 @@ internal fun interface ReferenceReader { fun read(source: T): Sequence fun interface Factory { - fun create(graph: HeapGraph): ReferenceReader + fun createFor(heapGraph: HeapGraph): ReferenceReader } } diff --git a/shark/shark/src/main/java/shark/ShortestPathFinder.kt b/shark/shark/src/main/java/shark/ShortestPathFinder.kt new file mode 100644 index 0000000000..246aa774a5 --- /dev/null +++ b/shark/shark/src/main/java/shark/ShortestPathFinder.kt @@ -0,0 +1,18 @@ +package shark + +// TODO Mention that this can also compute the dominator tree? +// Ideally this would be split out, e.g. ObjectDominators uses this without +// any target just to navigate the whole graph. But we do want to be able traverse the +// whole thing in one go, and both find leaking objects + compute dominators. +// Maybe there's a new "heap traversal" class that can navigate the whole heap starting from +// gc roots and following references, and we plug in 2 things on it: the one for leaks +// and the one for dominators. And both can say when to stop (dominators: never). +fun interface ShortestPathFinder { + fun findShortestPathsFromGcRoots( + leakingObjectIds: Set + ): PathFindingResults + + fun interface Factory { + fun createFor(heapGraph: HeapGraph): ShortestPathFinder + } +} diff --git a/shark/shark/src/main/java/shark/VirtualizingMatchingReferenceReaderFactory.kt b/shark/shark/src/main/java/shark/VirtualizingMatchingReferenceReaderFactory.kt new file mode 100644 index 0000000000..2193668813 --- /dev/null +++ b/shark/shark/src/main/java/shark/VirtualizingMatchingReferenceReaderFactory.kt @@ -0,0 +1,26 @@ +package shark + +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader + +// TODO Move to shark-android once HeapAnalyzer is removed. +// TODO This should be configurable to provide your own ref readers. Maybe just +/** + * Creates [ReferenceReader] instances that will follow references from all [HeapObject]s, + * applying matching rules provided by [referenceMatchers], creating additional virtual instance + * reference based on the list of [VirtualInstanceReferenceReader] created by [virtualRefReadersFactory]. + */ +class VirtualizingMatchingReferenceReaderFactory( + private val referenceMatchers: List, + private val virtualRefReadersFactory: VirtualInstanceReferenceReader.ChainFactory + ) : ReferenceReader.Factory { + override fun createFor(heapGraph: HeapGraph): ReferenceReader { + return DelegatingObjectReferenceReader( + classReferenceReader = ClassReferenceReader(heapGraph, referenceMatchers), + instanceReferenceReader = ChainingInstanceReferenceReader( + virtualRefReaders = virtualRefReadersFactory.createFor(heapGraph), + fieldRefReader = FieldInstanceReferenceReader(heapGraph, referenceMatchers) + ), + objectArrayReferenceReader = ObjectArrayReferenceReader() + ) + } +} diff --git a/shark/shark/src/main/java/shark/internal/InternalSharedExpanderHelpers.kt b/shark/shark/src/main/java/shark/internal/InternalSharedExpanderHelpers.kt index dc329a9dcc..ff2daea8f3 100644 --- a/shark/shark/src/main/java/shark/internal/InternalSharedExpanderHelpers.kt +++ b/shark/shark/src/main/java/shark/internal/InternalSharedExpanderHelpers.kt @@ -1,10 +1,11 @@ package shark.internal -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader import shark.HeapObject.HeapInstance import shark.HeapValue -import shark.internal.Reference.LazyDetails -import shark.internal.ReferenceLocationType.ARRAY_ENTRY +import shark.Reference +import shark.Reference.LazyDetails +import shark.ReferenceLocationType.ARRAY_ENTRY internal class InternalSharedHashMapReferenceReader( private val className: String, diff --git a/shark/shark/src/main/java/shark/internal/InternalSharkCollectionsHelper.kt b/shark/shark/src/main/java/shark/internal/InternalSharkCollectionsHelper.kt index 99cf87185a..463e41e756 100644 --- a/shark/shark/src/main/java/shark/internal/InternalSharkCollectionsHelper.kt +++ b/shark/shark/src/main/java/shark/internal/InternalSharkCollectionsHelper.kt @@ -1,10 +1,12 @@ package shark.internal +import shark.ApacheHarmonyInstanceRefReaders import shark.HeapObject import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray import shark.HeapObject.HeapPrimitiveArray +import shark.OpenJdkInstanceRefReaders /** * INTERNAL diff --git a/shark/shark/src/main/java/shark/internal/ReferenceLocationType.kt b/shark/shark/src/main/java/shark/internal/ReferenceLocationType.kt deleted file mode 100644 index e1c83db754..0000000000 --- a/shark/shark/src/main/java/shark/internal/ReferenceLocationType.kt +++ /dev/null @@ -1,8 +0,0 @@ -package shark.internal - -internal enum class ReferenceLocationType { - INSTANCE_FIELD, - STATIC_FIELD, - LOCAL, - ARRAY_ENTRY -} diff --git a/shark/shark/src/main/java/shark/internal/ReferencePathNode.kt b/shark/shark/src/main/java/shark/internal/ReferencePathNode.kt index 9164145bab..e8637beb42 100644 --- a/shark/shark/src/main/java/shark/internal/ReferencePathNode.kt +++ b/shark/shark/src/main/java/shark/internal/ReferencePathNode.kt @@ -2,9 +2,9 @@ package shark.internal import shark.GcRoot import shark.LibraryLeakReferenceMatcher -import shark.internal.Reference.LazyDetails +import shark.Reference.LazyDetails -internal sealed class ReferencePathNode { +sealed class ReferencePathNode { abstract val objectId: Long sealed class RootNode : ReferencePathNode() { diff --git a/shark/shark/src/main/java/shark/internal/ShallowSizeCalculator.kt b/shark/shark/src/main/java/shark/internal/ShallowSizeCalculator.kt index 07db5601be..f807e4a122 100644 --- a/shark/shark/src/main/java/shark/internal/ShallowSizeCalculator.kt +++ b/shark/shark/src/main/java/shark/internal/ShallowSizeCalculator.kt @@ -5,7 +5,7 @@ import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray import shark.HeapObject.HeapPrimitiveArray -import shark.internal.ObjectArrayReferenceReader.Companion.isSkippablePrimitiveWrapperArray +import shark.ObjectArrayReferenceReader.Companion.isSkippablePrimitiveWrapperArray import shark.ValueHolder /** diff --git a/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt b/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt index b27dd7496d..88f0faed99 100644 --- a/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt +++ b/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt @@ -6,20 +6,13 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import shark.FilteringLeakingObjectFinder.LeakingObjectFilter -import shark.internal.OpenJdkInstanceRefReaders.LINKED_LIST +import shark.OpenJdkInstanceRefReaders.LINKED_LIST import java.io.File import java.util.LinkedList import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList -import shark.internal.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory +import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory import shark.HprofHeapGraph.Companion.openHeapGraph -import shark.internal.ChainingInstanceReferenceReader -import shark.internal.ClassReferenceReader -import shark.internal.DelegatingObjectReferenceReader -import shark.internal.FieldInstanceReferenceReader -import shark.internal.JavaLocalReferenceReader -import shark.internal.ObjectArrayReferenceReader -import shark.internal.OpenJdkInstanceRefReaders class OpenJdkInstanceRefReadersTest { @@ -292,7 +285,9 @@ class OpenJdkInstanceRefReadersTest { val refPath = findLeak(OpenJdkInstanceRefReaders.HASH_MAP) with(refPath.first()) { - assertThat(referenceDisplayName).matches("\\[instance @\\d* of shark\\.OpenJdkInstanceRefReadersTest\\\$SomeKey]") + assertThat(referenceDisplayName).matches( + "\\[instance @\\d* of shark\\.OpenJdkInstanceRefReadersTest\\\$SomeKey]" + ) } } @@ -374,9 +369,7 @@ class OpenJdkInstanceRefReadersTest { computeRetainedHeapSize: Boolean, virtualRefReaderFactory: OptionalFactory, ): List { - val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener.NO_OP) - - val analysis = openHeapGraph().use { graph -> + val leaks = openHeapGraph().use { graph -> val referenceMatchers = defaultReferenceMatchers val virtualRefReaders = virtualRefReaderFactory.create(graph)?.let { @@ -394,27 +387,26 @@ class OpenJdkInstanceRefReadersTest { objectArrayReferenceReader = ObjectArrayReferenceReader() ) - heapAnalyzer.analyze( - heapDumpFile = this, - graph = graph, - leakingObjectFinder = FilteringLeakingObjectFinder(listOf(object : - LeakingObjectFilter { - override fun isLeakingObject(heapObject: HeapObject): Boolean { - return heapObject.asInstance?.instanceOf(Retained::class) ?: false - } - })), + val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(object : + LeakingObjectFilter { + override fun isLeakingObject(heapObject: HeapObject): Boolean { + return heapObject.asInstance?.instanceOf(Retained::class) ?: false + } + })) + val objectIds = leakingObjectFinder.findLeakingObjectIds(graph) + val tracer = RealLeakTracerFactory( referenceMatchers = referenceMatchers, computeRetainedHeapSize = computeRetainedHeapSize, objectInspectors = emptyList(), - metadataExtractor = MetadataExtractor.NO_OP, - referenceReader = referenceReader - ) - }.apply { - if (this is HeapAnalysisFailure) { + referenceReaderFactory = { referenceReader }, + listener = {} + ).createFor(graph) + + tracer.traceObjects(objectIds).apply { println(this) } - } as HeapAnalysisSuccess - val leakTrace = analysis.applicationLeaks[0].leakTraces.first() + } + val leakTrace = leaks.applicationLeaks[0].leakTraces.first() println(leakTrace.toSimplePathString()) val index = leakTrace.referencePath.indexOfFirst { it.referenceName == ::leakRoot.name } val refFromExpandedTypeIndex = index + 1 diff --git a/shark/shark/src/test/java/shark/internal/AndroidReferenceReadersHprofTest.kt b/shark/shark/src/test/java/shark/internal/AndroidReferenceReadersHprofTest.kt index 62d7a097f5..f3f07ee74d 100644 --- a/shark/shark/src/test/java/shark/internal/AndroidReferenceReadersHprofTest.kt +++ b/shark/shark/src/test/java/shark/internal/AndroidReferenceReadersHprofTest.kt @@ -3,20 +3,14 @@ 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 { diff --git a/shark/shark/src/test/java/shark/internal/DominatorTreeTest.kt b/shark/shark/src/test/java/shark/internal/DominatorTreeTest.kt index 6c2e496ff3..7b2d42ec51 100644 --- a/shark/shark/src/test/java/shark/internal/DominatorTreeTest.kt +++ b/shark/shark/src/test/java/shark/internal/DominatorTreeTest.kt @@ -2,6 +2,7 @@ package shark.internal import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import shark.DominatorTree import shark.ValueHolder @Suppress("UsePropertyAccessSyntax") @@ -197,4 +198,4 @@ class DominatorTreeTest { assertThat(fullDominatorTree.getValue(grandChild).retainedSize).isEqualTo(10) assertThat(fullDominatorTree.getValue(ValueHolder.NULL_REFERENCE).retainedSize).isEqualTo(50) } -} \ No newline at end of file +} From b93c4c33a0435293d34b51f1684deb1000bf4744 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Wed, 31 May 2023 10:49:22 -0700 Subject: [PATCH 12/13] updating missing APIs --- shark/shark/api/shark.api | 285 +++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 4 deletions(-) diff --git a/shark/shark/api/shark.api b/shark/shark/api/shark.api index 305d5510cd..c857d699a6 100644 --- a/shark/shark/api/shark.api +++ b/shark/shark/api/shark.api @@ -1,3 +1,48 @@ +public final class shark/ActualMatchingReferenceReaderFactory : shark/ReferenceReader$Factory { + public fun (Ljava/util/List;)V + public fun createFor (Lshark/HeapGraph;)Lshark/ReferenceReader; +} + +public final class shark/AndroidNativeSizeMapper { + public static final field Companion Lshark/AndroidNativeSizeMapper$Companion; + public fun (Lshark/HeapGraph;)V + public final fun mapNativeSizes ()Ljava/util/Map; +} + +public final class shark/AndroidNativeSizeMapper$Companion { + public final fun mapNativeSizes (Lshark/HeapGraph;)Ljava/util/Map; +} + +public final class shark/AndroidReferenceReaderFactory : shark/ReferenceReader$Factory { + public fun (Ljava/util/List;)V + public fun createFor (Lshark/HeapGraph;)Lshark/ReferenceReader; +} + +public abstract class shark/AndroidReferenceReaders : java/lang/Enum, shark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader$OptionalFactory { + public static final field ANIMATOR_WEAK_REF_SUCKS Lshark/AndroidReferenceReaders; + public static final field ARRAY_SET Lshark/AndroidReferenceReaders; + public static final field Companion Lshark/AndroidReferenceReaders$Companion; + public static final field MESSAGE_QUEUE Lshark/AndroidReferenceReaders; + public static final field SAFE_ITERABLE_MAP Lshark/AndroidReferenceReaders; + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public static fun valueOf (Ljava/lang/String;)Lshark/AndroidReferenceReaders; + public static fun values ()[Lshark/AndroidReferenceReaders; +} + +public final class shark/AndroidReferenceReaders$Companion { +} + +public abstract class shark/ApacheHarmonyInstanceRefReaders : java/lang/Enum, shark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader$OptionalFactory { + public static final field ARRAY_LIST Lshark/ApacheHarmonyInstanceRefReaders; + public static final field COPY_ON_WRITE_ARRAY_LIST Lshark/ApacheHarmonyInstanceRefReaders; + public static final field HASH_MAP Lshark/ApacheHarmonyInstanceRefReaders; + public static final field HASH_SET Lshark/ApacheHarmonyInstanceRefReaders; + public static final field LINKED_LIST Lshark/ApacheHarmonyInstanceRefReaders; + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public static fun valueOf (Ljava/lang/String;)Lshark/ApacheHarmonyInstanceRefReaders; + public static fun values ()[Lshark/ApacheHarmonyInstanceRefReaders; +} + public final class shark/AppSingletonInspector : shark/ObjectInspector { public fun ([Ljava/lang/String;)V public fun inspect (Lshark/ObjectReporter;)V @@ -20,6 +65,46 @@ public final class shark/ApplicationLeak : shark/Leak { public final class shark/ApplicationLeak$Companion { } +public final class shark/ChainingInstanceReferenceReader : shark/ReferenceReader { + public fun (Ljava/util/List;Lshark/FieldInstanceReferenceReader;)V + public fun read (Lshark/HeapObject$HeapInstance;)Lkotlin/sequences/Sequence; + public synthetic fun read (Lshark/HeapObject;)Lkotlin/sequences/Sequence; +} + +public abstract interface class shark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader : shark/ReferenceReader { + public abstract fun matches (Lshark/HeapObject$HeapInstance;)Z +} + +public abstract interface class shark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader$ChainFactory { + public abstract fun createFor (Lshark/HeapGraph;)Ljava/util/List; +} + +public abstract interface class shark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader$OptionalFactory { + public abstract fun create (Lshark/HeapGraph;)Lshark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader; +} + +public final class shark/ClassReferenceReader : shark/ReferenceReader { + public fun (Lshark/HeapGraph;Ljava/util/List;)V + public fun read (Lshark/HeapObject$HeapClass;)Lkotlin/sequences/Sequence; + public synthetic fun read (Lshark/HeapObject;)Lkotlin/sequences/Sequence; +} + +public final class shark/DominatorTree { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun buildFullDominatorTree (Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public final fun computeRetainedSizes (Ljava/util/Set;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public final fun updateDominated (JJ)Z + public final fun updateDominatedAsRoot (J)Z +} + +public final class shark/FieldInstanceReferenceReader : shark/ReferenceReader { + public fun (Lshark/HeapGraph;Ljava/util/List;)V + public fun read (Lshark/HeapObject$HeapInstance;)Lkotlin/sequences/Sequence; + public synthetic fun read (Lshark/HeapObject;)Lkotlin/sequences/Sequence; +} + public final class shark/FilteringLeakingObjectFinder : shark/LeakingObjectFinder { public fun (Ljava/util/List;)V public fun findLeakingObjectIds (Lshark/HeapGraph;)Ljava/util/Set; @@ -29,6 +114,17 @@ public abstract interface class shark/FilteringLeakingObjectFinder$LeakingObject public abstract fun isLeakingObject (Lshark/HeapObject;)Z } +public abstract interface class shark/GcRootProvider { + public abstract fun provideGcRoots (Lshark/HeapGraph;)Lkotlin/sequences/Sequence; +} + +public final class shark/GcRootReference { + public fun (Lshark/GcRoot;ZLshark/LibraryLeakReferenceMatcher;)V + public final fun getGcRoot ()Lshark/GcRoot; + public final fun getMatchedLibraryLeak ()Lshark/LibraryLeakReferenceMatcher; + public final fun isLowPriority ()Z +} + public abstract class shark/HeapAnalysis : java/io/Serializable { public static final field Companion Lshark/HeapAnalysis$Companion; public static final field DUMP_DURATION_UNKNOWN J @@ -261,10 +357,33 @@ public final class shark/LeakTraceReference$ReferenceType : java/lang/Enum { public static fun values ()[Lshark/LeakTraceReference$ReferenceType; } +public abstract interface class shark/LeakTracer { + public abstract fun traceObjects (Ljava/util/Set;)Lshark/LeaksAndUnreachableObjects; +} + +public abstract interface class shark/LeakTracer$Factory { + public abstract fun createFor (Lshark/HeapGraph;)Lshark/LeakTracer; +} + public abstract interface class shark/LeakingObjectFinder { public abstract fun findLeakingObjectIds (Lshark/HeapGraph;)Ljava/util/Set; } +public final class shark/LeaksAndUnreachableObjects { + public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lshark/LeaksAndUnreachableObjects; + public static synthetic fun copy$default (Lshark/LeaksAndUnreachableObjects;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lshark/LeaksAndUnreachableObjects; + public fun equals (Ljava/lang/Object;)Z + public final fun getApplicationLeaks ()Ljava/util/List; + public final fun getLibraryLeaks ()Ljava/util/List; + public final fun getUnreachableObjects ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class shark/LibraryLeak : shark/Leak { public static final field Companion Lshark/LibraryLeak$Companion; public fun (Ljava/util/List;Lshark/ReferencePattern;Ljava/lang/String;)V @@ -302,6 +421,11 @@ public final class shark/LibraryLeakReferenceMatcher : shark/ReferenceMatcher { public fun toString ()Ljava/lang/String; } +public final class shark/MatchingGcRootProvider : shark/GcRootProvider { + public fun (Ljava/util/List;)V + public fun provideGcRoots (Lshark/HeapGraph;)Lkotlin/sequences/Sequence; +} + public abstract interface class shark/MetadataExtractor { public static final field Companion Lshark/MetadataExtractor$Companion; public abstract fun extractMetadata (Lshark/HeapGraph;)Ljava/util/Map; @@ -311,6 +435,29 @@ public final class shark/MetadataExtractor$Companion { public final fun getNO_OP ()Lshark/MetadataExtractor; } +public final class shark/ObjectDominators { + public fun ()V + public final fun renderDominatorTree (Lshark/HeapGraph;Ljava/util/List;ILjava/lang/String;Z)Ljava/lang/String; + public static synthetic fun renderDominatorTree$default (Lshark/ObjectDominators;Lshark/HeapGraph;Ljava/util/List;ILjava/lang/String;ZILjava/lang/Object;)Ljava/lang/String; +} + +public final class shark/ObjectDominators$DominatorNode { + public fun (IIILjava/util/List;)V + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()I + public final fun component4 ()Ljava/util/List; + public final fun copy (IIILjava/util/List;)Lshark/ObjectDominators$DominatorNode; + public static synthetic fun copy$default (Lshark/ObjectDominators$DominatorNode;IIILjava/util/List;ILjava/lang/Object;)Lshark/ObjectDominators$DominatorNode; + public fun equals (Ljava/lang/Object;)Z + public final fun getDominatedObjectIds ()Ljava/util/List; + public final fun getRetainedCount ()I + public final fun getRetainedSize ()I + public final fun getShallowSize ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract interface class shark/ObjectInspector { public abstract fun inspect (Lshark/ObjectReporter;)V } @@ -368,6 +515,94 @@ public final class shark/OnAnalysisProgressListener$Step : java/lang/Enum { public static fun values ()[Lshark/OnAnalysisProgressListener$Step; } +public final class shark/PathFindingResults { + public fun (Ljava/util/List;Lshark/DominatorTree;)V + public final fun getDominatorTree ()Lshark/DominatorTree; + public final fun getPathsToLeakingObjects ()Ljava/util/List; +} + +public final class shark/PrioritizingShortestPathFinder : shark/ShortestPathFinder { + public synthetic fun (Lshark/HeapGraph;Lshark/PrioritizingShortestPathFinder$Event$Listener;Lshark/ReferenceReader;Lshark/GcRootProvider;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun findShortestPathsFromGcRoots (Ljava/util/Set;)Lshark/PathFindingResults; +} + +public abstract interface class shark/PrioritizingShortestPathFinder$Event { +} + +public abstract interface class shark/PrioritizingShortestPathFinder$Event$Listener { + public abstract fun onEvent (Lshark/PrioritizingShortestPathFinder$Event;)V +} + +public final class shark/PrioritizingShortestPathFinder$Event$StartedFindingDominators : shark/PrioritizingShortestPathFinder$Event { + public static final field INSTANCE Lshark/PrioritizingShortestPathFinder$Event$StartedFindingDominators; +} + +public final class shark/PrioritizingShortestPathFinder$Event$StartedFindingPathsToRetainedObjects : shark/PrioritizingShortestPathFinder$Event { + public static final field INSTANCE Lshark/PrioritizingShortestPathFinder$Event$StartedFindingPathsToRetainedObjects; +} + +public final class shark/PrioritizingShortestPathFinder$Factory : shark/ShortestPathFinder$Factory { + public fun (Lshark/PrioritizingShortestPathFinder$Event$Listener;Lshark/ReferenceReader$Factory;Lshark/GcRootProvider;Z)V + public fun createFor (Lshark/HeapGraph;)Lshark/ShortestPathFinder; +} + +public final class shark/RealLeakTracerFactory : shark/LeakTracer$Factory { + public fun (Lshark/ShortestPathFinder$Factory;Ljava/util/List;Lshark/RealLeakTracerFactory$Event$Listener;)V + public fun createFor (Lshark/HeapGraph;)Lshark/LeakTracer; +} + +public abstract interface class shark/RealLeakTracerFactory$Event { +} + +public abstract interface class shark/RealLeakTracerFactory$Event$Listener { + public abstract fun onEvent (Lshark/RealLeakTracerFactory$Event;)V +} + +public final class shark/RealLeakTracerFactory$Event$StartedBuildingLeakTraces : shark/RealLeakTracerFactory$Event { + public static final field INSTANCE Lshark/RealLeakTracerFactory$Event$StartedBuildingLeakTraces; +} + +public final class shark/RealLeakTracerFactory$Event$StartedComputingJavaHeapRetainedSize : shark/RealLeakTracerFactory$Event { + public static final field INSTANCE Lshark/RealLeakTracerFactory$Event$StartedComputingJavaHeapRetainedSize; +} + +public final class shark/RealLeakTracerFactory$Event$StartedComputingNativeRetainedSize : shark/RealLeakTracerFactory$Event { + public static final field INSTANCE Lshark/RealLeakTracerFactory$Event$StartedComputingNativeRetainedSize; +} + +public final class shark/RealLeakTracerFactory$Event$StartedInspectingObjects : shark/RealLeakTracerFactory$Event { + public static final field INSTANCE Lshark/RealLeakTracerFactory$Event$StartedInspectingObjects; +} + +public final class shark/Reference { + public fun (JZLshark/Reference$LazyDetails$Resolver;)V + public final fun getLazyDetailsResolver ()Lshark/Reference$LazyDetails$Resolver; + public final fun getValueObjectId ()J + public final fun isLowPriority ()Z +} + +public final class shark/Reference$LazyDetails { + public fun (Ljava/lang/String;JLshark/ReferenceLocationType;Lshark/LibraryLeakReferenceMatcher;Z)V + public final fun getLocationClassObjectId ()J + public final fun getLocationType ()Lshark/ReferenceLocationType; + public final fun getMatchedLibraryLeak ()Lshark/LibraryLeakReferenceMatcher; + public final fun getName ()Ljava/lang/String; + public final fun isVirtual ()Z +} + +public abstract interface class shark/Reference$LazyDetails$Resolver { + public abstract fun resolve ()Lshark/Reference$LazyDetails; +} + +public final class shark/ReferenceLocationType : java/lang/Enum { + public static final field ARRAY_ENTRY Lshark/ReferenceLocationType; + public static final field INSTANCE_FIELD Lshark/ReferenceLocationType; + public static final field LOCAL Lshark/ReferenceLocationType; + public static final field STATIC_FIELD Lshark/ReferenceLocationType; + public static fun valueOf (Ljava/lang/String;)Lshark/ReferenceLocationType; + public static fun values ()[Lshark/ReferenceLocationType; +} + public abstract class shark/ReferenceMatcher { public abstract fun getPattern ()Lshark/ReferencePattern; } @@ -443,14 +678,56 @@ public final class shark/ReferencePattern$StaticFieldPattern : shark/ReferencePa public final class shark/ReferencePattern$StaticFieldPattern$Companion { } +public abstract interface class shark/ReferenceReader { + public abstract fun read (Lshark/HeapObject;)Lkotlin/sequences/Sequence; +} + +public abstract interface class shark/ReferenceReader$Factory { + public abstract fun createFor (Lshark/HeapGraph;)Lshark/ReferenceReader; +} + +public abstract interface class shark/ShortestPathFinder { + public abstract fun findShortestPathsFromGcRoots (Ljava/util/Set;)Lshark/PathFindingResults; +} + +public abstract interface class shark/ShortestPathFinder$Factory { + public abstract fun createFor (Lshark/HeapGraph;)Lshark/ShortestPathFinder; +} + +public final class shark/VirtualizingMatchingReferenceReaderFactory : shark/ReferenceReader$Factory { + public fun (Ljava/util/List;Lshark/ChainingInstanceReferenceReader$VirtualInstanceReferenceReader$ChainFactory;)V + public fun createFor (Lshark/HeapGraph;)Lshark/ReferenceReader; +} + public final class shark/internal/InternalSharkCollectionsHelper { public static final field INSTANCE Lshark/internal/InternalSharkCollectionsHelper; public final fun arrayListValues (Lshark/HeapObject$HeapInstance;)Lkotlin/sequences/Sequence; } -public final class shark/internal/ObjectDominators { - public fun ()V - public final fun renderDominatorTree (Lshark/HeapGraph;Ljava/util/List;ILjava/lang/String;Z)Ljava/lang/String; - public static synthetic fun renderDominatorTree$default (Lshark/internal/ObjectDominators;Lshark/HeapGraph;Ljava/util/List;ILjava/lang/String;ZILjava/lang/Object;)Ljava/lang/String; +public abstract class shark/internal/ReferencePathNode { + public abstract fun getObjectId ()J +} + +public final class shark/internal/ReferencePathNode$ChildNode : shark/internal/ReferencePathNode { + public fun (JLshark/internal/ReferencePathNode;Lshark/Reference$LazyDetails$Resolver;)V + public final fun getLazyDetailsResolver ()Lshark/Reference$LazyDetails$Resolver; + public fun getObjectId ()J + public final fun getParent ()Lshark/internal/ReferencePathNode; +} + +public abstract class shark/internal/ReferencePathNode$RootNode : shark/internal/ReferencePathNode { + public abstract fun getGcRoot ()Lshark/GcRoot; + public fun getObjectId ()J +} + +public final class shark/internal/ReferencePathNode$RootNode$LibraryLeakRootNode : shark/internal/ReferencePathNode$RootNode { + public fun (Lshark/GcRoot;Lshark/LibraryLeakReferenceMatcher;)V + public fun getGcRoot ()Lshark/GcRoot; + public final fun getMatcher ()Lshark/LibraryLeakReferenceMatcher; +} + +public final class shark/internal/ReferencePathNode$RootNode$NormalRootNode : shark/internal/ReferencePathNode$RootNode { + public fun (Lshark/GcRoot;)V + public fun getGcRoot ()Lshark/GcRoot; } From b172ed559d92da7d7a8ddbe58c72bea85189cdb7 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Wed, 31 May 2023 11:04:05 -0700 Subject: [PATCH 13/13] Fix build failures --- .../src/test/java/shark/HprofIOPerfTest.kt | 20 ++++++++++------- .../shark/src/main/java/shark/HeapAnalyzer.kt | 4 +--- .../shark/OpenJdkInstanceRefReadersTest.kt | 22 +++++++++++-------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt b/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt index 83d35657ee..4c7988fb3b 100644 --- a/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt +++ b/shark/shark-android/src/test/java/shark/HprofIOPerfTest.kt @@ -179,7 +179,7 @@ class HprofIOPerfTest { ) .isEqualTo( listOf( - 25760, 40.0, 1309045, 25765, 40.0, 1309225 + 24384, 40.0, 1244990, 25653, 40.0, 1302298 ) ) } @@ -197,7 +197,7 @@ class HprofIOPerfTest { ) .isEqualTo( listOf( - 22493, 40.0, 2203818, 22498, 40.0, 2203998 + 22472, 40.0, 2202271, 22477, 40.0, 2202451 ) ) } @@ -215,7 +215,7 @@ class HprofIOPerfTest { ) .isEqualTo( listOf( - 16889, 32.0, 768692, 16891, 32.0, 768756 + 16829, 32.0, 765450, 16831, 32.0, 765514 ) ) } @@ -251,13 +251,17 @@ class HprofIOPerfTest { ) val objectIds = leakingObjectFinder.findLeakingObjectIds(graph) + + val referenceMatchers = AndroidReferenceMatchers.appDefaults + val tracer = RealLeakTracerFactory( - referenceMatchers = AndroidReferenceMatchers.appDefaults, - computeRetainedHeapSize = computeRetainedHeapSize, - objectInspectors = AndroidObjectInspectors.appDefaults, - referenceReaderFactory = AndroidReferenceReaderFactory( - AndroidReferenceMatchers.appDefaults + shortestPathFinderFactory = PrioritizingShortestPathFinder.Factory( + listener = {}, + referenceReaderFactory = AndroidReferenceReaderFactory(referenceMatchers), + gcRootProvider = MatchingGcRootProvider(referenceMatchers), + computeRetainedHeapSize = computeRetainedHeapSize, ), + objectInspectors = AndroidObjectInspectors.appDefaults, listener = {} ).createFor(graph) diff --git a/shark/shark/src/main/java/shark/HeapAnalyzer.kt b/shark/shark/src/main/java/shark/HeapAnalyzer.kt index 8e1d4a7f8a..53bb66cb50 100644 --- a/shark/shark/src/main/java/shark/HeapAnalyzer.kt +++ b/shark/shark/src/main/java/shark/HeapAnalyzer.kt @@ -128,8 +128,6 @@ class HeapAnalyzer constructor( ): HeapAnalysis { val analysisStartNanoTime = System.nanoTime() - val referenceReader = AndroidReferenceReaderFactory(referenceMatchers).createFor(graph) - return try { val leakTracer = RealLeakTracerFactory( shortestPathFinderFactory = PrioritizingShortestPathFinder.Factory( @@ -141,7 +139,7 @@ class HeapAnalyzer constructor( ) } }, - referenceReaderFactory = { referenceReader }, + referenceReaderFactory = AndroidReferenceReaderFactory(referenceMatchers), gcRootProvider = MatchingGcRootProvider(referenceMatchers), computeRetainedHeapSize = computeRetainedHeapSize, ), diff --git a/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt b/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt index 88f0faed99..d0da256df7 100644 --- a/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt +++ b/shark/shark/src/test/java/shark/OpenJdkInstanceRefReadersTest.kt @@ -1,18 +1,18 @@ package shark +import java.io.File +import java.util.LinkedList +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import shark.FilteringLeakingObjectFinder.LeakingObjectFilter -import shark.OpenJdkInstanceRefReaders.LINKED_LIST -import java.io.File -import java.util.LinkedList -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArrayList import shark.ChainingInstanceReferenceReader.VirtualInstanceReferenceReader.OptionalFactory +import shark.FilteringLeakingObjectFinder.LeakingObjectFilter import shark.HprofHeapGraph.Companion.openHeapGraph +import shark.OpenJdkInstanceRefReaders.LINKED_LIST class OpenJdkInstanceRefReadersTest { @@ -394,11 +394,15 @@ class OpenJdkInstanceRefReadersTest { } })) val objectIds = leakingObjectFinder.findLeakingObjectIds(graph) + val tracer = RealLeakTracerFactory( - referenceMatchers = referenceMatchers, - computeRetainedHeapSize = computeRetainedHeapSize, + shortestPathFinderFactory = PrioritizingShortestPathFinder.Factory( + listener = {}, + referenceReaderFactory = { referenceReader }, + gcRootProvider = MatchingGcRootProvider(referenceMatchers), + computeRetainedHeapSize = computeRetainedHeapSize, + ), objectInspectors = emptyList(), - referenceReaderFactory = { referenceReader }, listener = {} ).createFor(graph)