From e628b7d446484bbcf467536769a301760eeb097b Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 17 Jan 2017 13:50:20 -0700 Subject: [PATCH] Add Binder support for Parcelable exceptions. If an Exception thrown by a Binder call implements the Parcelable interface, then parcel it and rethrow back at the caller. There is strict requirement that these Parcelable exceptions must be defined by the system (as determined by checking the ClassLoader). We prefix the Parcelable contents with a length so that native code can skip over the blobs. Define a new ParcelableException class that can be used to transport exceptions that cannot be modified to add Parcelable behavior, and switch ExceptionUtils to use this new class for sending IOExceptions. Test: builds, boots, wrapped exceptions work Bug: 33749182 Change-Id: I1352ea1566ddf01120d9d0e819ba6f70fc407e11 --- core/java/android/os/Parcel.java | 33 ++++++- core/java/android/os/ParcelableException.java | 88 +++++++++++++++++++ core/java/android/util/ExceptionUtils.java | 14 ++- 3 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 core/java/android/os/ParcelableException.java diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index f6e6ad6067bb0..b5ab908346d94 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -27,6 +27,8 @@ import android.util.SizeF; import android.util.SparseArray; import android.util.SparseBooleanArray; +import libcore.util.SneakyThrow; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -249,6 +251,7 @@ public final class Parcel { private static final int EX_NETWORK_MAIN_THREAD = -6; private static final int EX_UNSUPPORTED_OPERATION = -7; private static final int EX_SERVICE_SPECIFIC = -8; + private static final int EX_PARCELABLE = -9; private static final int EX_HAS_REPLY_HEADER = -128; // special; see below // EX_TRANSACTION_FAILED is used exclusively in native code. // see libbinder's binder/Status.h @@ -1555,7 +1558,12 @@ public final class Parcel { */ public final void writeException(Exception e) { int code = 0; - if (e instanceof SecurityException) { + if (e instanceof Parcelable + && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) { + // We only send Parcelable exceptions that are in the + // BootClassLoader to ensure that the receiver can unpack them + code = EX_PARCELABLE; + } else if (e instanceof SecurityException) { code = EX_SECURITY; } else if (e instanceof BadParcelableException) { code = EX_BAD_PARCELABLE; @@ -1581,8 +1589,20 @@ public final class Parcel { throw new RuntimeException(e); } writeString(e.getMessage()); - if (e instanceof ServiceSpecificException) { - writeInt(((ServiceSpecificException)e).errorCode); + switch (code) { + case EX_SERVICE_SPECIFIC: + writeInt(((ServiceSpecificException) e).errorCode); + break; + case EX_PARCELABLE: + // Write parceled exception prefixed by length + final int sizePosition = dataPosition(); + writeInt(0); + writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + final int payloadPosition = dataPosition(); + setDataPosition(sizePosition); + writeInt(payloadPosition - sizePosition); + setDataPosition(payloadPosition); + break; } } @@ -1680,6 +1700,13 @@ public final class Parcel { */ public final void readException(int code, String msg) { switch (code) { + case EX_PARCELABLE: + if (readInt() > 0) { + SneakyThrow.sneakyThrow( + (Exception) readParcelable(Parcelable.class.getClassLoader())); + } else { + throw new RuntimeException(msg + " [missing Parcelable]"); + } case EX_SECURITY: throw new SecurityException(msg); case EX_BAD_PARCELABLE: diff --git a/core/java/android/os/ParcelableException.java b/core/java/android/os/ParcelableException.java new file mode 100644 index 0000000000000..d84d62997d939 --- /dev/null +++ b/core/java/android/os/ParcelableException.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 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; + +import java.io.IOException; + +/** + * Wrapper class that offers to transport typical {@link Throwable} across a + * {@link Binder} call. This class is typically used to transport exceptions + * that cannot be modified to add {@link Parcelable} behavior, such as + * {@link IOException}. + * + * + * @hide + */ +public final class ParcelableException extends RuntimeException implements Parcelable { + public ParcelableException(Throwable t) { + super(t); + } + + @SuppressWarnings("unchecked") + public void maybeRethrow(Class clazz) throws T { + if (clazz.isAssignableFrom(getCause().getClass())) { + throw (T) getCause(); + } + } + + /** {@hide} */ + public static Throwable readFromParcel(Parcel in) { + final String name = in.readString(); + final String msg = in.readString(); + try { + final Class clazz = Class.forName(name, true, Parcelable.class.getClassLoader()); + return (Throwable) clazz.getConstructor(String.class).newInstance(msg); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(name + ": " + msg); + } + } + + /** {@hide} */ + public static void writeToParcel(Parcel out, Throwable t) { + out.writeString(t.getClass().getName()); + out.writeString(t.getMessage()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + writeToParcel(dest, getCause()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ParcelableException createFromParcel(Parcel source) { + return new ParcelableException(readFromParcel(source)); + } + + @Override + public ParcelableException[] newArray(int size) { + return new ParcelableException[size]; + } + }; +} diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java index f5d515d5e5d5f..da0b609dbd9b7 100644 --- a/core/java/android/util/ExceptionUtils.java +++ b/core/java/android/util/ExceptionUtils.java @@ -16,6 +16,8 @@ package android.util; +import android.os.ParcelableException; + import java.io.IOException; /** @@ -24,19 +26,13 @@ import java.io.IOException; * @hide */ public class ExceptionUtils { - // TODO: longer term these should be replaced with first-class - // Parcel.read/writeException() and AIDL support, but for now do this using - // a nasty hack. - - private static final String PREFIX_IO = "\u2603"; - public static RuntimeException wrap(IOException e) { - throw new IllegalStateException(PREFIX_IO + e.getMessage()); + throw new ParcelableException(e); } public static void maybeUnwrapIOException(RuntimeException e) throws IOException { - if ((e instanceof IllegalStateException) && e.getMessage().startsWith(PREFIX_IO)) { - throw new IOException(e.getMessage().substring(PREFIX_IO.length())); + if (e instanceof ParcelableException) { + ((ParcelableException) e).maybeRethrow(IOException.class); } }