From cccb1b78d9ab7e611b17d225d83734a58eb2c7e8 Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Tue, 15 Feb 2022 15:26:14 +0000 Subject: [PATCH] Add safer Bundle APIs and deprecated old ones Add safer Bundle APIs that take an extra Class argument that checks that the type about to be deserialized is a child of the type passed in parameter *before* actually deserializing it, while also deprecating old APIs. This allows use to reap the benefits of the new typed Parcel APIs and enhances security. Only the APIs that could involve custom object injection are modified. So, besides the obvious ones that have that design (eg. readParcelableList()), subtler cases such as readIntegerArrayList() could result in custom object deserialization, and since it's all generics, even the casting inside Bundle wouldn't fail, only after the client unpacked the list items would it blow up. Now those are checked beforehand. Since Bundle always calls Parcel.readValue() under the hood (instead of specialized APIs such as readParcelable() etc), we had to augment that method (that's used by LazyValue when retrieving the item) to accept item types now for containers, which I implemented as a vararg of Class parameters (this is all private/@hide). This way we could retrieve a list of intents like readValue(.., List.class, Intent.class), or a map of string to intents like readValue(.., Map.class, String.class, Intent.class). For non-container items, we can just pass no arguments for the vararg. This is explained in internal javadocs. Inside readValue() now, we also check the container types before calling the internal methods for deserialization. So, if the thing on the wire is a VAL_MAP and we know the method we're about to call will return a HashMap, we verify that the type passed in parameter is a super type of that (if it's non-null, if it's null it means "perform no check"). Now, LazyValue became a BiFunction, Class[], Object> to receive those extra "item types" for containers. The reason for separating the first from the rest is that the first defines the return type in the new APIs and inside Parcel, so we need the T from Class to ensure type-safety. (I was torn here between using BiFunction or just exposing LazyValue as @hide for Bundle since it feels like we're missing meaning/abstraction, but end up leaving this way, advise if you'd prefer the other way) There was a bit of a refactor in Parcel so readValue() could call internal methods that accepted nullable Class parameters with the meaning that null = "no verification" and non-null = "check against type provided" (because the external APIs all require non-null parameters). Now we can return null in all cases when there is a type mismatch. Note that the Bundle APIs catch ClassCastException to return null, but that only works for non-generic types (eg. getSizeF()). For generic types wrapping "return (T) o" with try-catch doesn't work because the type gets erased to its bound at runtime, so the type mismatch escapes that try-catch to the caller, potentially causing a crash. Now they happen inside the getters, as the non-generic ones. Test: Boots for now Test: Working on CTS Test: atest -d android.os.cts.ParcelTest android.os.cts.BundleTest android.os.BundleTest android.os.ParcelTest CTS-Coverage-Bug: 219980813 Change-Id: Ifcbeb34b4684d7de105756b9d414162a9205ffaa --- core/api/current.txt | 17 +- .../os/BadTypeParcelableException.java | 30 +++ core/java/android/os/BaseBundle.java | 147 +++++++++---- core/java/android/os/Bundle.java | 152 +++++++++++-- core/java/android/os/Parcel.java | 205 ++++++++++++------ .../com/android/internal/util/ArrayUtils.java | 9 + 6 files changed, 424 insertions(+), 136 deletions(-) create mode 100644 core/java/android/os/BadTypeParcelableException.java diff --git a/core/api/current.txt b/core/api/current.txt index 4651747ddfda2..ba430af43ea86 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -29424,7 +29424,7 @@ package android.os { public class BaseBundle { method public void clear(); method public boolean containsKey(String); - method @Nullable public Object get(String); + method @Deprecated @Nullable public Object get(String); method public boolean getBoolean(String); method public boolean getBoolean(String, boolean); method @Nullable public boolean[] getBooleanArray(@Nullable String); @@ -29665,16 +29665,21 @@ package android.os { method public float getFloat(String, float); method @Nullable public float[] getFloatArray(@Nullable String); method @Nullable public java.util.ArrayList getIntegerArrayList(@Nullable String); - method @Nullable public T getParcelable(@Nullable String); - method @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String); - method @Nullable public java.util.ArrayList getParcelableArrayList(@Nullable String); - method @Nullable public java.io.Serializable getSerializable(@Nullable String); + method @Deprecated @Nullable public T getParcelable(@Nullable String); + method @Nullable public T getParcelable(@Nullable String, @NonNull Class); + method @Deprecated @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String); + method @Nullable public T[] getParcelableArray(@Nullable String, @NonNull Class); + method @Deprecated @Nullable public java.util.ArrayList getParcelableArrayList(@Nullable String); + method @Nullable public java.util.ArrayList getParcelableArrayList(@Nullable String, @NonNull Class); + method @Deprecated @Nullable public java.io.Serializable getSerializable(@Nullable String); + method @Nullable public T getSerializable(@Nullable String, @NonNull Class); method public short getShort(String); method public short getShort(String, short); method @Nullable public short[] getShortArray(@Nullable String); method @Nullable public android.util.Size getSize(@Nullable String); method @Nullable public android.util.SizeF getSizeF(@Nullable String); - method @Nullable public android.util.SparseArray getSparseParcelableArray(@Nullable String); + method @Deprecated @Nullable public android.util.SparseArray getSparseParcelableArray(@Nullable String); + method @Nullable public android.util.SparseArray getSparseParcelableArray(@Nullable String, @NonNull Class); method @Nullable public java.util.ArrayList getStringArrayList(@Nullable String); method public boolean hasFileDescriptors(); method public void putAll(android.os.Bundle); diff --git a/core/java/android/os/BadTypeParcelableException.java b/core/java/android/os/BadTypeParcelableException.java new file mode 100644 index 0000000000000..2ca3bd2adca1c --- /dev/null +++ b/core/java/android/os/BadTypeParcelableException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * 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 android.os; + +/** Used by Parcel to signal that the type on the payload was not expected by the caller. */ +class BadTypeParcelableException extends BadParcelableException { + BadTypeParcelableException(String msg) { + super(msg); + } + BadTypeParcelableException(Exception cause) { + super(cause); + } + BadTypeParcelableException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 244335d3aa067..45812e5516188 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -16,6 +16,8 @@ package android.os; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -31,7 +33,7 @@ import com.android.internal.util.IndentingPrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Set; -import java.util.function.Function; +import java.util.function.BiFunction; /** * A mapping from String keys to values of various types. In most cases, you @@ -254,8 +256,8 @@ public class BaseBundle { } try { return getValueAt(0, String.class); - } catch (ClassCastException | BadParcelableException e) { - typeWarning("getPairValue()", /* value */ null, "String", e); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning("getPairValue()", "String", e); return null; } } @@ -320,28 +322,46 @@ public class BaseBundle { * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. * + * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in + * other deprecated APIs. + * * @hide */ + @Deprecated + @Nullable final Object getValue(String key) { return getValue(key, /* clazz */ null); } + /** Same as {@link #getValue(String, Class, Class[])} with no item types. */ + @Nullable + final T getValue(String key, @Nullable Class clazz) { + // Avoids allocating Class[0] array + return getValue(key, clazz, (Class[]) null); + } + /** - * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code + * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code * null} for no type check). * + * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}. + * * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. * * @hide */ - final T getValue(String key, @Nullable Class clazz) { + @Nullable + final T getValue(String key, @Nullable Class clazz, @Nullable Class... itemTypes) { int i = mMap.indexOfKey(key); - return (i >= 0) ? getValueAt(i, clazz) : null; + return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null; } /** - * Returns the value for a certain position in the array map. + * Returns the value for a certain position in the array map for expected return type {@code + * clazz} (or pass {@code null} for no type check). + * + * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}. * * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. @@ -349,11 +369,12 @@ public class BaseBundle { * @hide */ @SuppressWarnings("unchecked") - final T getValueAt(int i, @Nullable Class clazz) { + @Nullable + final T getValueAt(int i, @Nullable Class clazz, @Nullable Class... itemTypes) { Object object = mMap.valueAt(i); - if (object instanceof Function) { + if (object instanceof BiFunction) { try { - object = ((Function, ?>) object).apply(clazz); + object = ((BiFunction, Class[], ?>) object).apply(clazz, itemTypes); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); @@ -615,13 +636,43 @@ public class BaseBundle { * * @param key a String key * @return an Object, or null + * + * @deprecated Use the type-safe specific APIs depending on the type of the item to be + * retrieved, eg. {@link #getString(String)}. */ + @Deprecated @Nullable public Object get(String key) { unparcel(); return getValue(key); } + /** + * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if: + *
    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
+ * + *

Use the more specific APIs where possible, especially in the case of containers such as + * lists, since those APIs allow you to specify the type of the items. + * + * @param key String key + * @param clazz The type of the object expected + * @return an Object, or null + */ + @Nullable + T get(@Nullable String key, @NonNull Class clazz) { + unparcel(); + try { + return getValue(key, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, clazz.getCanonicalName(), e); + return null; + } + } + /** * Removes any entry with the given key from the mapping of this Bundle. * @@ -1006,7 +1057,7 @@ public class BaseBundle { sb.append(" but value was a "); sb.append(value.getClass().getName()); } else { - sb.append(" but value was of a different type "); + sb.append(" but value was of a different type"); } sb.append(". The default value "); sb.append(defaultValue); @@ -1019,6 +1070,10 @@ public class BaseBundle { typeWarning(key, value, className, "", e); } + void typeWarning(String key, String className, RuntimeException e) { + typeWarning(key, /* value */ null, className, "", e); + } + /** * Returns the value associated with the given key, or defaultValue if * no mapping of the desired type exists for the given key. @@ -1358,7 +1413,11 @@ public class BaseBundle { * * @param key a String, or null * @return a Serializable value, or null + * + * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in + * other deprecated APIs. */ + @Deprecated @Nullable Serializable getSerializable(@Nullable String key) { unparcel(); @@ -1374,6 +1433,36 @@ public class BaseBundle { } } + /** + * Returns the value associated with the given key, or {@code null} if: + *

    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
+ * + * @param key a String, or null + * @param clazz The expected class of the returned type + * @return a Serializable value, or null + */ + @Nullable + T getSerializable(@Nullable String key, @NonNull Class clazz) { + return get(key, clazz); + } + + + @SuppressWarnings("unchecked") + @Nullable + ArrayList getArrayList(@Nullable String key, @NonNull Class clazz) { + unparcel(); + try { + return getValue(key, ArrayList.class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", e); + return null; + } + } + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null @@ -1384,17 +1473,7 @@ public class BaseBundle { */ @Nullable ArrayList getIntegerArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList", e); - return null; - } + return getArrayList(key, Integer.class); } /** @@ -1407,17 +1486,7 @@ public class BaseBundle { */ @Nullable ArrayList getStringArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList", e); - return null; - } + return getArrayList(key, String.class); } /** @@ -1430,17 +1499,7 @@ public class BaseBundle { */ @Nullable ArrayList getCharSequenceArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList", e); - return null; - } + return getArrayList(key, CharSequence.class); } /** diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 2b13f20ffe6dc..edbbb59ac77d0 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Size; @@ -876,7 +877,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { @Nullable public Bundle getBundle(@Nullable String key) { unparcel(); - Object o = getValue(key); + Object o = mMap.get(key); if (o == null) { return null; } @@ -899,7 +900,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return a Parcelable value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public T getParcelable(@Nullable String key) { unparcel(); @@ -916,30 +921,28 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** - * Returns the value associated with the given key, or {@code null} if - * no mapping of the desired type exists for the given key or a {@code null} - * value is explicitly associated with the key. + * Returns the value associated with the given key or {@code null} if: + *
    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
* *

Note: if the expected value is not a class provided by the Android platform, * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. * Otherwise, this method might throw an exception or return {@code null}. * * @param key a String, or {@code null} - * @param clazz The type of the object expected or {@code null} for performing no checks. + * @param clazz The type of the object expected * @return a Parcelable value, or {@code null} - * - * @hide */ @SuppressWarnings("unchecked") @Nullable public T getParcelable(@Nullable String key, @NonNull Class clazz) { - unparcel(); - try { - return getValue(key, requireNonNull(clazz)); - } catch (ClassCastException | BadParcelableException e) { - typeWarning(key, /* value */ null, "Parcelable", e); - return null; - } + // The reason for not using is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + return get(key, clazz); } /** @@ -953,7 +956,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return a Parcelable[] value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from + * Android {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public Parcelable[] getParcelableArray(@Nullable String key) { unparcel(); @@ -969,6 +976,39 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } } + /** + * Returns the value associated with the given key, or {@code null} if: + *

    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
+ * + *

Note: if the expected value is not a class provided by the Android platform, + * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. + * Otherwise, this method might throw an exception or return {@code null}. + * + * @param key a String, or {@code null} + * @param clazz The type of the items inside the array + * @return a Parcelable[] value, or {@code null} + */ + @SuppressLint({"ArrayReturn", "NullableCollection"}) + @SuppressWarnings("unchecked") + @Nullable + public T[] getParcelableArray(@Nullable String key, @NonNull Class clazz) { + // The reason for not using is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + unparcel(); + try { + // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting. + return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, clazz.getCanonicalName() + "[]", e); + return null; + } + } + /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} @@ -980,7 +1020,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return an ArrayList value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public ArrayList getParcelableArrayList(@Nullable String key) { unparcel(); @@ -996,15 +1040,44 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } } + /** + * Returns the value associated with the given key, or {@code null} if: + *

    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
+ * + *

Note: if the expected value is not a class provided by the Android platform, + * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. + * Otherwise, this method might throw an exception or return {@code null}. + * + * @param key a String, or {@code null} + * @param clazz The type of the items inside the array list + * @return an ArrayList value, or {@code null} + */ + @SuppressLint("NullableCollection") + @SuppressWarnings("unchecked") + @Nullable + public ArrayList getParcelableArrayList(@Nullable String key, @NonNull Class clazz) { + // The reason for not using is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + return getArrayList(key, clazz); + } + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. * * @param key a String, or null - * * @return a SparseArray of T values, or null + * + * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from + * Android {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public SparseArray getSparseParcelableArray(@Nullable String key) { unparcel(); @@ -1020,6 +1093,33 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } } + /** + * Returns the value associated with the given key, or {@code null} if: + *

    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
+ * + * @param key a String, or null + * @return a SparseArray of T values, or null + */ + @SuppressWarnings("unchecked") + @Nullable + public SparseArray getSparseParcelableArray(@Nullable String key, + @NonNull Class clazz) { + // The reason for not using is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + unparcel(); + try { + return (SparseArray) getValue(key, SparseArray.class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", e); + return null; + } + } + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null @@ -1027,13 +1127,35 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or null * @return a Serializable value, or null + * + * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Override @Nullable public Serializable getSerializable(@Nullable String key) { return super.getSerializable(key); } + /** + * Returns the value associated with the given key, or {@code null} if: + *
    + *
  • No mapping of the desired type exists for the given key. + *
  • A {@code null} value is explicitly associated with the key. + *
  • The object is not of type {@code clazz}. + *
+ * + * @param key a String, or null + * @param clazz The expected class of the returned type + * @return a Serializable value, or null + */ + @Nullable + public T getSerializable(@Nullable String key, + @NonNull Class clazz) { + return super.getSerializable(key, requireNonNull(clazz)); + } + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 32cee94506a24..d3b35a032aff4 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -16,6 +16,8 @@ package android.os; +import static com.android.internal.util.Preconditions.checkArgument; + import static java.util.Objects.requireNonNull; import android.annotation.IntDef; @@ -65,6 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -287,26 +290,26 @@ public final class Parcel { private static final int VAL_NULL = -1; private static final int VAL_STRING = 0; private static final int VAL_INTEGER = 1; - private static final int VAL_MAP = 2; + private static final int VAL_MAP = 2; // length-prefixed private static final int VAL_BUNDLE = 3; - private static final int VAL_PARCELABLE = 4; + private static final int VAL_PARCELABLE = 4; // length-prefixed private static final int VAL_SHORT = 5; private static final int VAL_LONG = 6; private static final int VAL_FLOAT = 7; private static final int VAL_DOUBLE = 8; private static final int VAL_BOOLEAN = 9; private static final int VAL_CHARSEQUENCE = 10; - private static final int VAL_LIST = 11; - private static final int VAL_SPARSEARRAY = 12; + private static final int VAL_LIST = 11; // length-prefixed + private static final int VAL_SPARSEARRAY = 12; // length-prefixed private static final int VAL_BYTEARRAY = 13; private static final int VAL_STRINGARRAY = 14; private static final int VAL_IBINDER = 15; - private static final int VAL_PARCELABLEARRAY = 16; - private static final int VAL_OBJECTARRAY = 17; + private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed + private static final int VAL_OBJECTARRAY = 17; // length-prefixed private static final int VAL_INTARRAY = 18; private static final int VAL_LONGARRAY = 19; private static final int VAL_BYTE = 20; - private static final int VAL_SERIALIZABLE = 21; + private static final int VAL_SERIALIZABLE = 21; // length-prefixed private static final int VAL_SPARSEBOOLEANARRAY = 22; private static final int VAL_BOOLEANARRAY = 23; private static final int VAL_CHARSEQUENCEARRAY = 24; @@ -3170,8 +3173,7 @@ public final class Parcel { */ @Deprecated public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) { - int n = readInt(); - readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null); + readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null); } /** @@ -3186,8 +3188,7 @@ public final class Parcel { @NonNull Class clazzValue) { Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); - int n = readInt(); - readMapInternal(outVal, n, loader, clazzKey, clazzValue); + readMapInternal(outVal, loader, clazzKey, clazzValue); } /** @@ -3236,13 +3237,7 @@ public final class Parcel { @Deprecated @Nullable public HashMap readHashMap(@Nullable ClassLoader loader) { - int n = readInt(); - if (n < 0) { - return null; - } - HashMap m = new HashMap(n); - readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null); - return m; + return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null); } /** @@ -3258,13 +3253,7 @@ public final class Parcel { @NonNull Class clazzKey, @NonNull Class clazzValue) { Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); - int n = readInt(); - if (n < 0) { - return null; - } - HashMap map = new HashMap<>(n); - readMapInternal(map, n, loader, clazzKey, clazzValue); - return map; + return readHashMapInternal(loader, clazzKey, clazzValue); } /** @@ -4289,16 +4278,17 @@ public final class Parcel { /** - * @param clazz The type of the object expected or {@code null} for performing no checks. + * @see #readValue(int, ClassLoader, Class, Class[]) */ @Nullable - private T readValue(@Nullable ClassLoader loader, @Nullable Class clazz) { + private T readValue(@Nullable ClassLoader loader, @Nullable Class clazz, + @Nullable Class... itemTypes) { int type = readInt(); final T object; if (isLengthPrefixed(type)) { int length = readInt(); int start = dataPosition(); - object = readValue(type, loader, clazz); + object = readValue(type, loader, clazz, itemTypes); int actual = dataPosition() - start; if (actual != length) { Slog.wtfStack(TAG, @@ -4306,25 +4296,26 @@ public final class Parcel { + " consumed " + actual + " bytes, but " + length + " expected."); } } else { - object = readValue(type, loader, clazz); + object = readValue(type, loader, clazz, itemTypes); } return object; } /** - * This will return a {@link Function} for length-prefixed types that deserializes the object - * when {@link Function#apply} is called with the expected class of the return object (or {@code - * null} for no type check), for other types it will return the object itself. + * This will return a {@link BiFunction} for length-prefixed types that deserializes the object + * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link + * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it + * will return the object itself. * - *

After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that - * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No + *

After calling {@link BiFunction#apply} the parcel cursor will not change. Note that you + * shouldn't recycle the parcel, not at least until all objects have been retrieved. No * synchronization attempts are made. * *

The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two * function objects are equal if either of the following is true: *
    - *
  • {@link Function#apply} has been called on both and both objects returned are equal. - *
  • {@link Function#apply} hasn't been called on either one and everything below is true: + *
  • {@link BiFunction#apply} has been called on both and both objects returned are equal. + *
  • {@link BiFunction#apply} hasn't been called on either one and everything below is true: *
      *
    • The {@code loader} parameters used to retrieve each are equal. *
    • They both have the same type. @@ -4351,7 +4342,7 @@ public final class Parcel { } - private static final class LazyValue implements Function, Object> { + private static final class LazyValue implements BiFunction, Class[], Object> { /** * | 4B | 4B | * mSource = Parcel{... | type | length | object | ...} @@ -4383,7 +4374,7 @@ public final class Parcel { } @Override - public Object apply(@Nullable Class clazz) { + public Object apply(@Nullable Class clazz, @Nullable Class[] itemTypes) { Parcel source = mSource; if (source != null) { synchronized (source) { @@ -4392,7 +4383,7 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader, clazz); + mObject = source.readValue(mLoader, clazz, itemTypes); } finally { source.setDataPosition(restore); } @@ -4472,14 +4463,25 @@ public final class Parcel { } } + /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ + private T readValue(int type, @Nullable ClassLoader loader, @Nullable Class clazz) { + // Avoids allocating Class[0] array + return readValue(type, loader, clazz, (Class[]) null); + } + /** * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the * type first. + * * @param clazz The type of the object expected or {@code null} for performing no checks. + * @param itemTypes If the value is a container, these represent the item types (eg. for a list + * it's the item type, for a map, it's the key type, followed by the value + * type). */ @SuppressWarnings("unchecked") @Nullable - private T readValue(int type, @Nullable ClassLoader loader, @Nullable Class clazz) { + private T readValue(int type, @Nullable ClassLoader loader, @Nullable Class clazz, + @Nullable Class... itemTypes) { final Object object; switch (type) { case VAL_NULL: @@ -4495,7 +4497,11 @@ public final class Parcel { break; case VAL_MAP: - object = readHashMap(loader); + checkTypeToUnparcel(clazz, HashMap.class); + Class keyType = ArrayUtils.getOrNull(itemTypes, 0); + Class valueType = ArrayUtils.getOrNull(itemTypes, 1); + checkArgument((keyType == null) == (valueType == null)); + object = readHashMapInternal(loader, keyType, valueType); break; case VAL_PARCELABLE: @@ -4526,10 +4532,12 @@ public final class Parcel { object = readCharSequence(); break; - case VAL_LIST: - object = readArrayList(loader); + case VAL_LIST: { + checkTypeToUnparcel(clazz, ArrayList.class); + Class itemType = ArrayUtils.getOrNull(itemTypes, 0); + object = readArrayListInternal(loader, itemType); break; - + } case VAL_BOOLEANARRAY: object = createBooleanArray(); break; @@ -4550,10 +4558,12 @@ public final class Parcel { object = readStrongBinder(); break; - case VAL_OBJECTARRAY: - object = readArray(loader); + case VAL_OBJECTARRAY: { + Class itemType = ArrayUtils.getOrNull(itemTypes, 0); + checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class); + object = readArrayInternal(loader, itemType); break; - + } case VAL_INTARRAY: object = createIntArray(); break; @@ -4570,14 +4580,18 @@ public final class Parcel { object = readSerializableInternal(loader, clazz); break; - case VAL_PARCELABLEARRAY: - object = readParcelableArray(loader); + case VAL_PARCELABLEARRAY: { + Class itemType = ArrayUtils.getOrNull(itemTypes, 0); + checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class); + object = readParcelableArrayInternal(loader, itemType); break; - - case VAL_SPARSEARRAY: - object = readSparseArray(loader); + } + case VAL_SPARSEARRAY: { + checkTypeToUnparcel(clazz, SparseArray.class); + Class itemType = ArrayUtils.getOrNull(itemTypes, 0); + object = readSparseArrayInternal(loader, itemType); break; - + } case VAL_SPARSEBOOLEANARRAY: object = readSparseBooleanArray(); break; @@ -4625,7 +4639,7 @@ public final class Parcel { + " at offset " + off); } if (object != null && clazz != null && !clazz.isInstance(object)) { - throw new BadParcelableException("Unparcelled object " + object + throw new BadTypeParcelableException("Unparcelled object " + object + " is not an instance of required class " + clazz.getName() + " provided in the parameter"); } @@ -4651,6 +4665,38 @@ public final class Parcel { } } + /** + * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of + * {@code requiredArrayType}. + */ + private void checkArrayTypeToUnparcel(@Nullable Class requiredArrayType, + Class componentTypeToUnparcel) { + if (requiredArrayType != null) { + // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check + Class requiredComponentType = requiredArrayType.getComponentType(); + if (requiredComponentType == null) { + throw new BadTypeParcelableException( + "About to unparcel an array but type " + + requiredArrayType.getCanonicalName() + + " required by caller is not an array."); + } + checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel); + } + } + + /** + * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code + * requiredType} is not {@code null}. + */ + private void checkTypeToUnparcel(@Nullable Class requiredType, Class typeToUnparcel) { + if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) { + throw new BadTypeParcelableException( + "About to unparcel a " + typeToUnparcel.getCanonicalName() + + ", which is not a subtype of type " + requiredType.getCanonicalName() + + " required by caller."); + } + } + /** * Read and return a new Parcelable from the parcel. The given class loader * will be used to load any enclosed Parcelables. If it is null, the default @@ -4781,7 +4827,7 @@ public final class Parcel { if (clazz != null) { Class parcelableClass = creator.getClass().getEnclosingClass(); if (!clazz.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable creator " + name + " is not " + throw new BadTypeParcelableException("Parcelable creator " + name + " is not " + "a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4804,7 +4850,7 @@ public final class Parcel { } if (clazz != null) { if (!clazz.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable creator " + name + " is not " + throw new BadTypeParcelableException("Parcelable creator " + name + " is not " + "a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4865,15 +4911,7 @@ public final class Parcel { @Deprecated @Nullable public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { - return null; - } - Parcelable[] p = new Parcelable[N]; - for (int i = 0; i < N; i++) { - p[i] = readParcelable(loader); - } - return p; + return readParcelableArrayInternal(loader, /* clazz */ null); } /** @@ -4885,14 +4923,20 @@ public final class Parcel { * trying to instantiate an element. */ @SuppressLint({"ArrayReturn", "NullableCollection"}) - @SuppressWarnings("unchecked") @Nullable public T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class clazz) { + return readParcelableArrayInternal(loader, requireNonNull(clazz)); + } + + @SuppressWarnings("unchecked") + @Nullable + private T[] readParcelableArrayInternal(@Nullable ClassLoader loader, + @Nullable Class clazz) { int n = readInt(); if (n < 0) { return null; } - T[] p = (T[]) Array.newInstance(clazz, n); + T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n)); for (int i = 0; i < n; i++) { p[i] = readParcelableInternal(loader, clazz); } @@ -4955,7 +4999,7 @@ public final class Parcel { // the class the same way as ObjectInputStream, using the provided classloader. Class cl = Class.forName(name, false, loader); if (!clazz.isAssignableFrom(cl)) { - throw new BadParcelableException("Serializable object " + throw new BadTypeParcelableException("Serializable object " + cl.getName() + " is not a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4980,7 +5024,7 @@ public final class Parcel { // the deserialized object, as we cannot resolve the class the same way as // ObjectInputStream. if (!clazz.isAssignableFrom(object.getClass())) { - throw new BadParcelableException("Serializable object " + throw new BadTypeParcelableException("Serializable object " + object.getClass().getName() + " is not a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -5090,7 +5134,26 @@ public final class Parcel { readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null); } - /* package */ void readMapInternal(@NonNull Map outVal, int n, + @Nullable + private HashMap readHashMapInternal(@Nullable ClassLoader loader, + @NonNull Class clazzKey, @NonNull Class clazzValue) { + int n = readInt(); + if (n < 0) { + return null; + } + HashMap map = new HashMap<>(n); + readMapInternal(map, n, loader, clazzKey, clazzValue); + return map; + } + + private void readMapInternal(@NonNull Map outVal, + @Nullable ClassLoader loader, @Nullable Class clazzKey, + @Nullable Class clazzValue) { + int n = readInt(); + readMapInternal(outVal, n, loader, clazzKey, clazzValue); + } + + private void readMapInternal(@NonNull Map outVal, int n, @Nullable ClassLoader loader, @Nullable Class clazzKey, @Nullable Class clazzValue) { while (n > 0) { @@ -5101,7 +5164,7 @@ public final class Parcel { } } - /* package */ void readArrayMapInternal(@NonNull ArrayMap outVal, + private void readArrayMapInternal(@NonNull ArrayMap outVal, int size, @Nullable ClassLoader loader) { readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index c6fd6eec2f915..55df09a95838a 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -888,6 +888,15 @@ public class ArrayUtils { } } + /** + * Returns the {@code i}-th item in {@code items}, if it exists and {@code items} is not {@code + * null}, otherwise returns {@code null}. + */ + @Nullable + public static T getOrNull(@Nullable T[] items, int i) { + return (items != null && items.length > i) ? items[i] : null; + } + public static @Nullable T firstOrNull(T[] items) { return items.length > 0 ? items[0] : null; }