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

Commit 141751af authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Propagate exception stacktrace via AndroidFuture IPC"

parents 1293e629 49b8cdf5
Loading
Loading
Loading
Loading
+59 −47
Original line number Diff line number Diff line
@@ -1886,6 +1886,43 @@ public final class Parcel {
    public final void writeException(@NonNull Exception e) {
        AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);

        int code = getExceptionCode(e);
        writeInt(code);
        StrictMode.clearGatheredViolations();
        if (code == 0) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }
        writeString(e.getMessage());
        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
            sLastWriteExceptionStackTrace = timeNow;
            writeStackTrace(e);
        } else {
            writeInt(0);
        }
        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;
        }
    }

    /** @hide */
    public static int getExceptionCode(@NonNull Throwable e) {
        int code = 0;
        if (e instanceof Parcelable
                && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1909,19 +1946,11 @@ public final class Parcel {
        } else if (e instanceof ServiceSpecificException) {
            code = EX_SERVICE_SPECIFIC;
        }
        writeInt(code);
        StrictMode.clearGatheredViolations();
        if (code == 0) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        return code;
    }
        writeString(e.getMessage());
        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
            sLastWriteExceptionStackTrace = timeNow;

    /** @hide */
    public void writeStackTrace(@NonNull Throwable e) {
        final int sizePosition = dataPosition();
        writeInt(0); // Header size will be filled in later
        StackTraceElement[] stackTrace = e.getStackTrace();
@@ -1936,24 +1965,6 @@ public final class Parcel {
        // Write stack trace header size. Used in native side to skip the header
        writeInt(payloadPosition - sizePosition);
        setDataPosition(payloadPosition);
        } else {
            writeInt(0);
        }
        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;
        }
    }

    /**
@@ -2069,14 +2080,7 @@ public final class Parcel {
        if (remoteStackTrace != null) {
            RemoteException cause = new RemoteException(
                    "Remote stack trace:\n" + remoteStackTrace, null, false, false);
            try {
                Throwable rootCause = ExceptionUtils.getRootCause(e);
                if (rootCause != null) {
                    rootCause.initCause(cause);
                }
            } catch (RuntimeException ex) {
                Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
            }
            ExceptionUtils.appendCause(e, cause);
        }
        SneakyThrow.sneakyThrow(e);
    }
@@ -2088,6 +2092,14 @@ public final class Parcel {
     * @param msg The exception message.
     */
    private Exception createException(int code, String msg) {
        Exception exception = createExceptionOrNull(code, msg);
        return exception != null
                ? exception
                : new RuntimeException("Unknown exception code: " + code + " msg " + msg);
    }

    /** @hide */
    public Exception createExceptionOrNull(int code, String msg) {
        switch (code) {
            case EX_PARCELABLE:
                if (readInt() > 0) {
@@ -2111,9 +2123,9 @@ public final class Parcel {
                return new UnsupportedOperationException(msg);
            case EX_SERVICE_SPECIFIC:
                return new ServiceSpecificException(readInt(), msg);
            default:
                return null;
        }
        return new RuntimeException("Unknown exception code: " + code
                + " msg " + msg);
    }

    /**
+66 −24
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable

    private static final boolean DEBUG = false;
    private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
    private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];

    private final @NonNull Object mLock = new Object();
    @GuardedBy("mLock")
@@ -95,15 +96,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
            // Done
            if (in.readBoolean()) {
                // Failed
                try {
                    in.readException();
                } catch (Throwable e) {
                    completeExceptionally(e);
                }
                if (!isCompletedExceptionally()) {
                    throw new IllegalStateException(
                            "Error unparceling AndroidFuture: exception expected");
                }
                completeExceptionally(unparcelException(in));
            } else {
                // Success
                complete((T) in.readValue(null));
@@ -512,14 +505,9 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
            T result;
            try {
                result = get();
            } catch (Exception t) {
                // Exceptions coming out of get() are wrapped in ExecutionException, which is not
                // handled by Parcel.
                if (t instanceof ExecutionException && t.getCause() instanceof Exception) {
                    t = (Exception) t.getCause();
                }
            } catch (Throwable t) {
                dest.writeBoolean(true);
                dest.writeException(t);
                parcelException(dest, unwrapExecutionException(t));
                return;
            }
            dest.writeBoolean(false);
@@ -528,22 +516,76 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
            dest.writeStrongBinder(new IAndroidFuture.Stub() {
                @Override
                public void complete(AndroidFuture resultContainer) {
                    boolean changed;
                    try {
                        AndroidFuture.this.complete((T) resultContainer.get());
                        changed = AndroidFuture.this.complete((T) resultContainer.get());
                    } catch (Throwable t) {
                        // If resultContainer was completed exceptionally, get() wraps the
                        // underlying exception in an ExecutionException. Unwrap it now to avoid
                        // double-layering ExecutionExceptions.
                        if (t instanceof ExecutionException && t.getCause() != null) {
                            t = t.getCause();
                        changed = completeExceptionally(unwrapExecutionException(t));
                    }
                        completeExceptionally(t);
                    if (!changed) {
                        Log.w(LOG_TAG, "Remote result " + resultContainer
                                + " ignored, as local future is already completed: "
                                + AndroidFuture.this);
                    }
                }
            }.asBinder());
        }
    }

    /**
     * Exceptions coming out of {@link #get} are wrapped in {@link ExecutionException}
     */
    Throwable unwrapExecutionException(Throwable t) {
        return t instanceof ExecutionException
                ? t.getCause()
                : t;
    }

    /**
     * Alternative to {@link Parcel#writeException} that stores the stack trace, in a
     * way consistent with the binder IPC exception propagation behavior.
     */
    private static void parcelException(Parcel p, @Nullable Throwable t) {
        p.writeBoolean(t == null);
        if (t == null) {
            return;
        }

        p.writeInt(Parcel.getExceptionCode(t));
        p.writeString(t.getClass().getName());
        p.writeString(t.getMessage());
        p.writeStackTrace(t);
        parcelException(p, t.getCause());
    }

    /**
     * @see #parcelException
     */
    private static @Nullable Throwable unparcelException(Parcel p) {
        if (p.readBoolean()) {
            return null;
        }

        int exCode = p.readInt();
        String cls = p.readString();
        String msg = p.readString();
        String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>";
        msg += "\n" + stackTrace;

        Exception ex = p.createExceptionOrNull(exCode, msg);
        if (ex == null) {
            ex = new RuntimeException(cls + ": " + msg);
        }
        ex.setStackTrace(EMPTY_STACK_TRACE);

        Throwable cause = unparcelException(p);
        if (cause != null) {
            ex.initCause(ex);
        }

        return ex;
    }

    @Override
    public int describeContents() {
        return 0;
+6 −1
Original line number Diff line number Diff line
@@ -121,7 +121,12 @@ public class AndroidFutureTest {
        AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
        ExecutionException executionException =
                expectThrows(ExecutionException.class, future2::get);
        assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);

        Throwable cause = executionException.getCause();
        String msg = cause.getMessage();
        assertThat(cause).isInstanceOf(UnsupportedOperationException.class);
        assertThat(msg).contains(getClass().getName());
        assertThat(msg).contains("testWriteToParcel_Exception");
    }

    @Test