Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e628b7d4 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent 68d03b23
Loading
Loading
Loading
Loading
+30 −3
Original line number Diff line number Diff line
@@ -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) {
        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:
+88 −0
Original line number Diff line number Diff line
/*
 * 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}.
 * <ul>
 * <li>The wrapped throwable must be defined as system class (that is, it must
 * be in the same {@link ClassLoader} as {@link Parcelable}).
 * <li>The wrapped throwable must support the
 * {@link Throwable#Throwable(String)} constructor.
 * <li>The receiver side must catch any thrown {@link ParcelableException} and
 * call {@link #maybeRethrow(Class)} for all expected exception types.
 * </ul>
 *
 * @hide
 */
public final class ParcelableException extends RuntimeException implements Parcelable {
    public ParcelableException(Throwable t) {
        super(t);
    }

    @SuppressWarnings("unchecked")
    public <T extends Throwable> void maybeRethrow(Class<T> 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<ParcelableException> CREATOR = new Creator<ParcelableException>() {
        @Override
        public ParcelableException createFromParcel(Parcel source) {
            return new ParcelableException(readFromParcel(source));
        }

        @Override
        public ParcelableException[] newArray(int size) {
            return new ParcelableException[size];
        }
    };
}
+5 −9
Original line number Diff line number Diff line
@@ -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);
        }
    }