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

Prevent NPE in ImageLayer.getBounds() #2578

Merged
merged 2 commits into from
Mar 4, 2025

Conversation

allenchen1154
Copy link
Contributor

@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
Contributor 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)

Copy link

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

@@ -107,7 +107,10 @@ public class ImageLayer extends BaseLayer {
if (lottieDrawable.getMaintainOriginalImageBounds()) {
outBounds.set(0, 0, lottieImageAsset.getWidth() * scale, lottieImageAsset.getHeight() * scale);
} else {
outBounds.set(0, 0, getBitmap().getWidth() * scale, getBitmap().getHeight() * scale);
Bitmap bitmap = getBitmap();
if (bitmap != null) {
Copy link
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
Contributor 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
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
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
Contributor 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.

Copy link

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