Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HEAP ANALYSIS FAILED: KotlinNullPointerException #2527

Closed
Pitel opened this issue Jun 1, 2023 · 4 comments · Fixed by #2528
Closed

HEAP ANALYSIS FAILED: KotlinNullPointerException #2527

Pitel opened this issue Jun 1, 2023 · 4 comments · Fixed by #2528
Milestone

Comments

@Pitel
Copy link

Pitel commented Jun 1, 2023

kotlin.KotlinNullPointerException
                            	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY.getLifecycleRegistryState(AndroidObjectInspectors.kt:857)
                            	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY.access$getLifecycleRegistryState$p(AndroidObjectInspectors.kt:841)
                            	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY$inspect$1.invoke(AndroidObjectInspectors.kt:844)
                            	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY$inspect$1.invoke(AndroidObjectInspectors.kt:841)
                            	at shark.ObjectReporter.whenInstanceOf(ObjectReporter.kt:61)
                            	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY.inspect(AndroidObjectInspectors.kt:843)
                            	at shark.HeapAnalyzer.inspectObjects(HeapAnalyzer.kt:517)
                            	at shark.HeapAnalyzer.findLeaks(HeapAnalyzer.kt:282)
                            	at shark.HeapAnalyzer.analyzeGraph(HeapAnalyzer.kt:253)
                            	at shark.HeapAnalyzer.analyze$shark(HeapAnalyzer.kt:217)
                            	at shark.HeapAnalyzer.analyze(HeapAnalyzer.kt:166)
                            	at leakcanary.internal.AndroidDebugHeapAnalyzer.analyzeHeap(AndroidDebugHeapAnalyzer.kt:156)
                            	at leakcanary.internal.AndroidDebugHeapAnalyzer.runAnalysisBlocking(AndroidDebugHeapAnalyzer.kt:59)
                            	at leakcanary.internal.AndroidDebugHeapAnalyzer.runAnalysisBlocking$default(AndroidDebugHeapAnalyzer.kt:46)
                            	at leakcanary.internal.HeapAnalyzerWorker.doWork(HeapAnalyzerWorker.kt:18)
                            	at androidx.work.Worker$1.run(Worker.java:82)
                            	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
                            	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
                            	at java.lang.Thread.run(Thread.java:764)
Build.VERSION.SDK_INT: 28
                            Build.MANUFACTURER: Amino
                            LeakCanary version: 2.11
                            Analysis duration: 166966 ms
                            Heap dump file path: /data/user/0/com.twentyfouri.youfonelauncher/cache/leakcanary/2023-06-01_14-19-24_248.hprof
                            Heap dump timestamp: 1685622518945

2023-06-01_14-19-24_248.zip

Also reproducible with shark-cli and this hprof file.

@Pitel Pitel added the type: bug label Jun 1, 2023
@pyricau
Copy link
Member

pyricau commented Jun 1, 2023

This is crashing here: https://github.com/square/leakcanary/blob/main/shark/shark-android/src/main/java/shark/AndroidObjectInspectors.kt#L857

val state = this["androidx.lifecycle.LifecycleRegistry", "mState"]!!.valueAsInstance!!

I did repro with ./shark-cli.sh -h /Users/py/Downloads/2023-06-01_14-19-24_248.hprof analyze

Splitting the code into two lines:

        val field = this["androidx.lifecycle.LifecycleRegistry", "mState"]!!
        val state = field.valueAsInstance!!

I see that the NPE is on:

this["androidx.lifecycle.LifecycleRegistry", "mState"]!!

This implies that androidx.lifecycle.LifecycleRegistry does not have a mState field.

I then changed the code to dump the list of fields in the error:

        val field = this["androidx.lifecycle.LifecycleRegistry", "mState"]
        if (field == null) {
          val fields = readFields().joinToString("\n") { "${it.name}: ${it.value.holder}" }
          error("mState field doesn't exist. Fields:\n$fields")
        }
        val state = field.valueAsInstance!!

Which gives me:

java.lang.IllegalStateException: mState field doesn't exist. Fields:
addingObserverCounter: IntHolder(value=0)
enforceMainThread: BooleanHolder(value=true)
handlingEvent: BooleanHolder(value=false)
lifecycleOwner: ReferenceHolder(value=340688760)
newEventOccurred: BooleanHolder(value=false)
observerMap: ReferenceHolder(value=340688784)
parentStates: ReferenceHolder(value=340688816)
state: ReferenceHolder(value=323850840)
internalScopeRef: ReferenceHolder(value=340688744)
shadow$_klass_: ReferenceHolder(value=324239120)
shadow$_monitor_: IntHolder(value=-2051338022)
	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY.getLifecycleRegistryState(AndroidObjectInspectors.kt:860)
	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY.access$getLifecycleRegistryState(AndroidObjectInspectors.kt:841)
	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY$inspect$1.invoke(AndroidObjectInspectors.kt:844)
	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY$inspect$1.invoke(AndroidObjectInspectors.kt:843)
	at shark.ObjectReporter.whenInstanceOf(ObjectReporter.kt:49)
	at shark.AndroidObjectInspectors$LIFECYCLE_REGISTRY.inspect(AndroidObjectInspectors.kt:843)
	at shark.RealLeakTracerFactory.inspectObjects(RealLeakTracerFactory.kt:334)
	at shark.RealLeakTracerFactory.findLeaks(RealLeakTracerFactory.kt:101)
	at shark.RealLeakTracerFactory.createFor$lambda$0(RealLeakTracerFactory.kt:81)
	at shark.HeapAnalyzer.analyze(HeapAnalyzer.kt:179)

No mState field, but I can see a state field.

Looking at the Android X sources, LifecycleRegistry was converted to Kotlin on 2022-11-16 and the mState field changed to state.

https://cs.android.com/androidx/platform/frameworks/support/+/36833f9ab0c50bf449fc795e297a0e124df3356e:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.kt;dlc=19acbc4b7d73c6bcf1b023658ccece32d76f28cd

Changing the LeakCanary to look for state fixes the issue, here's the new output for the hprof file you provided:

====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS

References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.

15213 bytes retained by leaking objects
Displaying only 1 leak trace out of 3 with the same signature
Signature: 82352fc12454e2ffbc198996575a499d890a5f3d
┬───
│ GC Root: System class
│
├─ android.net.ConnectivityManager class
│    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│    ↓ static ConnectivityManager.sCallbacks
├─ java.util.HashMap instance
│    Leaking: NO (MainActivity↓ is not leaking)
│    ↓ HashMap[instance @338776616 of android.net.NetworkRequest]
├─ com.twentyfouri.tvlauncher.common.utils.NetworkConnectionState$LauncherNetworkCallback instance
│    Leaking: NO (MainActivity↓ is not leaking)
│    context instance of com.twentyfouri.tvlauncher.ui.MainActivity with mDestroyed = false
│    ↓ NetworkConnectionState$LauncherNetworkCallback.context
├─ com.twentyfouri.tvlauncher.ui.MainActivity instance
│    Leaking: NO (LifecycleRegistry↓ is not leaking and Activity#mDestroyed is false)
│    mApplication instance of com.twentyfouri.tvlauncher.ui.LauncherApplication
│    mBase instance of androidx.appcompat.view.ContextThemeWrapper
│    ↓ ComponentActivity.mLifecycleRegistry
├─ androidx.lifecycle.LifecycleRegistry instance
│    Leaking: NO (mState is not DESTROYED)
│    mState = RESUMED
│    ↓ LifecycleRegistry.observerMap
│                        ~~~~~~~~~~~
├─ androidx.arch.core.internal.FastSafeIterableMap instance
│    Leaking: UNKNOWN
│    Retaining 27.4 MB in 335714 objects
│    ↓ FastSafeIterableMap[key()]
│                         ~~~~~~~
├─ androidx.lifecycle.LiveData$LifecycleBoundObserver instance
│    Leaking: UNKNOWN
│    Retaining 32 B in 1 objects
│    mOwner instance of com.twentyfouri.tvlauncher.ui.MainActivity with mDestroyed = false
│    ↓ LiveData$ObserverWrapper.mObserver
│                               ~~~~~~~~~
├─ com.twentyfouri.tvlauncher.widgets.RowView$sam$androidx_lifecycle_Observer$0 instance
│    Leaking: UNKNOWN
│    Retaining 864.6 kB in 12372 objects
│    ↓ RowView$sam$androidx_lifecycle_Observer$0.function
│                                                ~~~~~~~~
├─ com.twentyfouri.tvlauncher.widgets.RowView$setupRowDataObservers$3 instance
│    Leaking: UNKNOWN
│    Retaining 864.6 kB in 12371 objects
│    Anonymous subclass of kotlin.jvm.internal.Lambda
│    ↓ RowView$setupRowDataObservers$3.this$0
│                                      ~~~~~~
├─ com.twentyfouri.tvlauncher.widgets.RowView instance
│    Leaking: UNKNOWN
│    Retaining 864.6 kB in 12370 objects
│    View not part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mID = R.id.row_view
│    View.mWindowAttachCount = 1
│    mContext instance of com.twentyfouri.tvlauncher.ui.MainActivity with mDestroyed = false
│    ↓ View.mParent
│           ~~~~~~~
├─ android.widget.LinearLayout instance
│    Leaking: UNKNOWN
│    Retaining 488.8 kB in 7333 objects
│    View not part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mID = R.id.parent_transition_view
│    View.mWindowAttachCount = 1
│    mContext instance of com.twentyfouri.tvlauncher.ui.MainActivity with mDestroyed = false
│    ↓ View.mParent
│           ~~~~~~~
╰→ androidx.constraintlayout.widget.ConstraintLayout instance
     Leaking: YES (ObjectWatcher was watching this because com.twentyfouri.tvlauncher.ui.DetailFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
     Retaining 5.6 kB in 88 objects
     key = eeed1c5f-0b73-44ec-b327-72534aa88ec2
     watchDurationMillis = 23662
     retainedDurationMillis = 18661
     View not part of a window view hierarchy
     View.mAttachInfo is null (view detached)
     View.mID = R.id.detail_layout
     View.mWindowAttachCount = 1
     mContext instance of com.twentyfouri.tvlauncher.ui.MainActivity with mDestroyed = false
====================================
0 LIBRARY LEAKS

A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
====================================
0 UNREACHABLE OBJECTS

An unreachable object is still in memory but LeakCanary could not find a strong reference path
from GC roots.
====================================
METADATA

Please include this in bug reports and Stack Overflow questions.

Build.VERSION.SDK_INT: 28
Build.MANUFACTURER: Amino
LeakCanary version: 2.11
App process name: com.twentyfouri.youfonelauncher
Class count: 21376
Instance count: 568170
Primitive array count: 269183
Object array count: 82005
Thread count: 76
Heap total bytes: 41817853
Bitmap count: 99
Bitmap total bytes: 57903380
Large bitmap count: 0
Large bitmap total bytes: 0
Db 1: closed /data/user/0/com.twentyfouri.youfonelauncher/databases/google_app_measurement_local.db
Db 2: open /data/user/0/com.twentyfouri.youfonelauncher/databases/epg_database
Db 3: open /data/user/0/com.twentyfouri.youfonelauncher/databases/database
Db 4: open /data/user/0/com.twentyfouri.youfonelauncher/no_backup/androidx.work.workdb
Db 5: open /data/user/0/com.twentyfouri.youfonelauncher/databases/usage-database
Db 6: open /data/user/0/com.twentyfouri.youfonelauncher/databases/usage-database
Db 7: open /data/user/0/com.twentyfouri.youfonelauncher/databases/com.google.android.datatransport.events
Db 8: closed /data/user/0/com.twentyfouri.youfonelauncher/databases/google_app_measurement_local.db
Db 9: closed /data/user/0/com.twentyfouri.youfonelauncher/databases/google_app_measurement_local.db
Db 10: closed /data/user/0/com.twentyfouri.youfonelauncher/databases/google_app_measurement_local.db
Stats: LruCache[maxSize=3000,hits=210810,misses=557228,hitRate=27%] RandomAccess[bytes=33041251,reads=557228,travel=483197342424,range=57075651,size=62870068]
Analysis duration: 4100 ms
Heap dump file path: /Users/py/Downloads/2023-06-01_14-19-24_248.hprof
Heap dump timestamp: 1685636262276
Heap dump duration: Unknown
====================================

@pyricau
Copy link
Member

pyricau commented Jun 1, 2023

The source of your leak is most likely that com.twentyfouri.tvlauncher.widgets.RowView$setupRowDataObservers$3 is setting up a lifecycle observer but forgets to unset it when RowView gets detached, so the lifecycle observer ends up leaking that view.

#2528 has the fix for the crash but LeakCanary 3 won't be released for a while.
I created a LeakCanary 2 main branch for bugfixes like this one. Here's the PR for it: #2529

@pyricau pyricau added this to the 2.12 milestone Jun 1, 2023
@Pitel
Copy link
Author

Pitel commented Jun 2, 2023

Great! Thanks for the analysis, we've fixed that leak. 💪🏻

@pyricau
Copy link
Member

pyricau commented Jun 29, 2023

LeakCanary 2.12 has been released with the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants