Skip to content

Prevent NPE in ImageLayer.getBounds()#2578

Merged
gpeal merged 2 commits intoairbnb:masterfrom
allenchen1154:allen--prevent-imagelayer-npe
Mar 4, 2025
Merged

Prevent NPE in ImageLayer.getBounds()#2578
gpeal merged 2 commits intoairbnb:masterfrom
allenchen1154:allen--prevent-imagelayer-npe

Conversation

@allenchen1154
Copy link
Copy Markdown
Collaborator

@allenchen1154 allenchen1154 commented Nov 15, 2024

The recent improvements to drop shadows added a dereference of a nullable result from the getBitmap() call.

Fixes #2601

The recent [improvements to drop shadows](https://github.com/airbnb/lottie-android/pull/2548/files#diff-31e777f53a917d69dcf1b234ae6c77db843316c34911e200d0a9a160c058b621R110) added a dereference of a nullable result from the `getBitmap()` call.
@allenchen1154 allenchen1154 requested a review from gpeal November 15, 2024 20:27
@allenchen1154
Copy link
Copy Markdown
Collaborator Author

Full stacktrace for reference:

java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
        at com.airbnb.lottie.model.layer.ImageLayer.getBounds(ImageLayer:110)
        at com.airbnb.lottie.model.layer.BaseLayer.draw(BaseLayer:281)
        at com.airbnb.lottie.model.layer.CompositionLayer.drawLayer(CompositionLayer:161)
        at com.airbnb.lottie.model.layer.BaseLayer.draw(BaseLayer:270)
        at com.airbnb.lottie.model.layer.CompositionLayer.drawLayer(CompositionLayer:161)
        at com.airbnb.lottie.model.layer.BaseLayer.draw(BaseLayer:270)
        at com.airbnb.lottie.LottieDrawable.draw(LottieDrawable:826)
        at com.airbnb.lottie.LottieDrawable.draw(LottieDrawable:804)
        at com.airbnb.lottie.compose.LottieAnimationKt$LottieAnimation$2.invoke(LottieAnimationKt:145)
        at com.airbnb.lottie.compose.LottieAnimationKt$LottieAnimation$2.invoke(LottieAnimationKt:107)
        at androidx.compose.ui.draw.DrawBackgroundModifier.draw(DrawBackgroundModifier:116)
        at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope:105)
        at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope:86)
        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator:364)
        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator:353)
        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator:176)
        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator:361)
        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator:353)
        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator:176)
        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator:361)
        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator:353)
        at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode:926)
        at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator:174)
        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator:361)
        at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator:54)
        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator:383)
        at androidx.compose.foundation.FocusablePinnableContainerNode$retrievePinnableContainer$1.invoke(FocusablePinnableContainerNode:75)
        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator:382)
        at androidx.compose.foundation.FocusablePinnableContainerNode$retrievePinnableContainer$1.invoke(FocusablePinnableContainerNode:20)
        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot:2303)
        at com.airbnb.android.utils.ParcelableUtils.observe(ParcelableUtils:59)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver:500)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver:51)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver:256)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver:140)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver:133)
        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator:382)
        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator:380)
        at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier$layerBlock$1.invoke(SimpleGraphicsLayerModifier:91)
        at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29:209)
        at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer:335)
        at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView:1236)
        at android.view.View.draw(View.java:24630)
        at android.view.View.updateDisplayListIfDirty(View.java:23493)
        at android.view.View.draw(View.java:24357)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4576)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4337)
        at android.view.View.updateDisplayListIfDirty(View.java:23484)
        at android.view.View.draw(View.java:24357)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4576)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4337)
        at android.view.View.updateDisplayListIfDirty(View.java:23484)
        at android.view.View.draw(View.java:24357)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4576)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4337)
        at android.view.View.updateDisplayListIfDirty(View.java:23484)
        at android.view.View.draw(View.java:24357)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4576)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4337)
        at android.view.View.draw(View.java:24630)
        at com.android.internal.policy.DecorView.draw(DecorView.java:827)
        at android.view.View.updateDisplayListIfDirty(View.java:23493)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:694)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:700)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:798)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:5313)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4975)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4093)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2718)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9937)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1406)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1415)
        at android.view.Choreographer.doCallbacks(Choreographer.java:1015)
        at android.view.Choreographer.doFrame(Choreographer.java:945)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1389)
        at android.os.Handler.handleCallback(Handler.java:959)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loopOnce(Looper.java:232)
        at android.os.Looper.loop(Looper.java:317)
        at android.app.ActivityThread.main(ActivityThread.java:8592)
        at java.lang.reflect.Method.invoke(Method.java:-2)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)

@github-actions
Copy link
Copy Markdown

Snapshot Tests
API 23: Report Diff
API 31: Report Diff

} else {
outBounds.set(0, 0, getBitmap().getWidth() * scale, getBitmap().getHeight() * scale);
Bitmap bitmap = getBitmap();
if (bitmap != null) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this is null? Will outBounds potentially have a stale value from another frame?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gpeal I don't have the full context here so maybe @geomaster can chime in on what scenarios would cause the bitmap to be null and whether we need alternate behavior around updating outBounds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I'm not sure why the bitmap would be null - presumably, it has not loaded yet, or it didn't load correctly?

If the bitmap is null, we would be rendering nothing, so I think arguably the correct thing to do is simply to set to an empty rectangle, with outBounds.set(0, 0, 0, 0).

Thoughts?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geomaster @allenchen1154 do either of you want to take this across the finish line?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gpeal I've updated the else case as @geomaster recommended - PTAL.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 4, 2025

Snapshot Tests
API 23: Report Diff
API 31: Report Diff

@gpeal gpeal merged commit 3c18f4c into airbnb:master Mar 4, 2025
7 checks passed
allenchen1154 added a commit to allenchen1154/lottie-android that referenced this pull request Mar 19, 2025
The changes in airbnb#2578 set the bounds returned by `ImageLayer.getBounds()` to have 0 width and 0 height if there is no Bitmap available. This change instead calls the previous version of the logic which reads the width and height of the `LottieImageAsset`.
gpeal pushed a commit that referenced this pull request Mar 20, 2025
The changes in #2578 set the bounds returned by `ImageLayer.getBounds()` to have 0 width and 0 height if there is no Bitmap available. This change instead calls the previous version of the logic which reads the width and height of the `LottieImageAsset`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash at ImageLayer.getBounds when ImageLayer.getBitmap() is null in version 6.6.1

3 participants