livefront / bridge

An Android library for avoiding TransactionTooLargeException during state saving and restoration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

IllegalArgumentException in restoreInstanceState

shashachu opened this issue · comments

This is another 2.0 crash that we're seeing in production. No local repro yet. It seems to occur mostly on OS 8.1.0 and 7.0 but it's happening across OS levels.

Caused by: java.lang.IllegalArgumentException
        at android.os.Parcel.nativeAppendFrom(Parcel.java:-2)
        at android.os.Parcel.appendFrom(Parcel.java:446)
        at android.os.BaseBundle.readFromParcelInner(BaseBundle.java:1356)
        at android.os.BaseBundle.<init>(BaseBundle.java:90)
        at android.os.Bundle.<init>(Bundle.java:68)
        at android.os.Parcel.readBundle(Parcel.java:1704)
        at com.livefront.bridge.util.BundleUtil.fromBytes(BundleUtil:58)
        at com.livefront.bridge.BridgeDelegate.readFromDisk(BridgeDelegate:241)
        at com.livefront.bridge.BridgeDelegate.getSavedBundleAndUnwrap(BridgeDelegate:138)
        at com.livefront.bridge.BridgeDelegate.restoreInstanceState(BridgeDelegate:302)
        at com.livefront.bridge.Bridge.restoreInstanceState(Bridge:111)
        <our code>

Any ideas?

@shashachu Interesting. That doesn't seem like the sort of thing that really shouldn't have changed at all when going from 1.x to 2.x. Are you sure you didn't see that on previous versions? In any case, that doesn't look like anything obvious to me so I'd have to dig into it a bit. It might be a case where I just have to catch that exception and just return null for the result.

For what its worth, there seemed to be a somewhat similar problem very early on in the Android framework that got updated: https://android.googlesource.com/platform/frameworks/base/+/6c5406a%5E%21/

So that's a least some kind of reference point to work from.

It did seem strange but I searched Bugsnag and can't find any similar crashes from before the 2.0 upgrade, and downgrading did seem to resolve the issue.

I also found that bug - you and I have similar Google-habits. :) I don't exactly know how you'd apply it to the library, though.

@shashachu OK thanks for the information. The only part of the change to 2.0 that I could think might have any effect on the behavior would be updating the compile / target SDK versions to 30. I can't imagine anything else could be related. I wonder if there's some subtle changes to the Parcel code in version 29 / 30 that could lead to this. I also found this issue, which is similar to the one I posted above. Those suggest possible threading issues but they aren't 100% the same as the issue you are seeing, though, so it's not clear if the cause would be the same.

I'll try to investigate a bit more in the next few days but I'm guessing the only solution I'll be able to come up with in the short-term is to catch the exception and return a null Bundle. It's not ideal and would result in some state loss but it's not as bad as a crash in this case. If you have find out anything more about how to reproduce it please let me know.

Thanks @byencho! We should only ever be calling save/restore on the main thread so I'm unsure how threading issues could cause this. Agree that state loss is better than crashing, so I'm comfortable with a bandaid fix.

@shashachu Could you tell me which Bridge version you were using before you updated to v2.0.0 and which version you have since reverted back to? In particular I'm wondering if you were on a version before or after v1.3.0. I changed the file storage mechanism in that version and it would make a bit more sense to me if that is when the problems began happening.

For a little context, the crash is happening here in BaseBundle:

    void readFromParcelInner(Parcel parcel) {
        // Keep implementation in sync with readFromParcel() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        int length = parcel.readInt(); // <----------------- This is where the length is read
        readFromParcelInner(parcel, length);
    }

    private void readFromParcelInner(Parcel parcel, int length) {
        ...
        
        // Advance within this Parcel
        int offset = parcel.dataPosition();
        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));

        Parcel p = Parcel.obtain();
        p.setDataPosition(0);
        p.appendFrom(parcel, offset, length); // <--------- This is where the problem occurs
        p.adoptClassCookies(parcel);
        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
                + ": " + length + " bundle bytes starting at " + offset);
        p.setDataPosition(0);

        mParcelledData = p;
        mParcelledByNative = isNativeBundle;
    }

I've played around with this a bit and the IllegalArgumentException gets thrown if the value for length is longer than the actual amount of data that can be consumed. Because the length is derived by reading data from the Parcel, the only scenario I found that could manually reproduce the crash when calling the BundleUtil methods is by passing it data that has been artificially truncated:

        val bundle = Bundle().apply {
            putString("test", "value")
        }

        val bytes = BundleUtil.toBytes(bundle)

        // Here we will copy the array but lose a bit of the end
        val truncatedBytes = bytes.copyOfRange(0, (bytes.size - 1) - 4)

        // This will trigger the IllegalArgumentException
        val outBundle = BundleUtil.fromBytes(truncatedBytes)

This would mean that the real problem isn't when reading the data, it's when we are writing it. I'm wondering if what is happening is that the data is getting partially written to disk but something is interrupting it, so we lose the end of the byte array and we only have partial results written to disk. When trying to read that data back, the Parcel thinks it's a different size than it really is and the crash happens.

Sure, I'll check our versions tomorrow. Thanks!

@byencho Confirmed - the version we reverted to was 1.2.1.

@shashachu OK, good to know. Thanks for checking that! I'm pretty confident this issue is resulting from the changes in v1.3.0 then. In retrospect it makes sense that are there are going to be some rare cases where the data is only partially written to disk, so errors like this when reading that partial data shouldn't be surprising. As mentioned before, I'll just make sure there is safer handling when attempting to convert data back to a Bundle and I think that's the best we can do. Thanks for bringing this issue to my attention!

@shashachu This should be fixed in v2.0.2. I'm going to close this for now but let me know if you see any other issues when you do end up updating again.

Thanks! I'll report back if we see any additional issues. I really appreciate the quick turnaround.

Hi @byencho just letting you know that we're rolling out a new version with 2.0.2 and so far are not seeing this crash. 🎉

Excellent, thanks for letting me know @shashachu !