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

Commit 49b8cdf5 authored by Eugene Susla's avatar Eugene Susla
Browse files

Propagate exception stacktrace via AndroidFuture IPC

Re-uses the code from Parcel to make it consistent with binder IPC exception
stacktrace propagation behavior.

Test: atest AndroidFutureTest
Change-Id: I5c73c42544af5bf964c7b6cd646bcb347e3ae146
parent c1ee9c7c
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