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

Commit a81b8c09 authored by Fyodor Kupolov's avatar Fyodor Kupolov
Browse files

Provide remote stack trace information

The stack is truncated up to 5 lines at parcel time. When unparceling,
a separate RemoteException will be created and set as a cause of the
exception being thrown.

Performance results(in nanoseconds):
timeWriteExceptionWithStackTraceParceling  4168
timeWriteException                         2201
timeReadException                         15878
timeReadExceptionWithStackTraceParceling  23805

Test: manual + ParcelPerfTest
Bug: 36561158
Change-Id: I18b64a6c39c24ab067115874ddb5bd71f556a601
parent 2a64dbce
Loading
Loading
Loading
Loading
+80 −0
Original line number Diff line number Diff line
@@ -27,6 +27,10 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class ParcelPerfTest {
@@ -167,4 +171,80 @@ public class ParcelPerfTest {
            Parcel.obtain().recycle();
        }
    }

    @Test
    public void timeWriteException() {
        timeWriteException(false);
    }

    @Test
    public void timeWriteExceptionWithStackTraceParceling() {
        timeWriteException(true);
    }

    @Test
    public void timeReadException() {
        timeReadException(false);
    }

    @Test
    public void timeReadExceptionWithStackTraceParceling() {
        timeReadException(true);
    }

    private void timeWriteException(boolean enableParceling) {
        if (enableParceling) {
            Parcel.setStackTraceParceling(true);
        }
        try {
            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
            Parcel p = Parcel.obtain();
            SecurityException e = new SecurityException("TestMessage");
            while (state.keepRunning()) {
                p.setDataPosition(0);
                p.writeException(e);
            }
        } finally {
            if (enableParceling) {
                Parcel.setStackTraceParceling(false);
            }
        }
    }

    private void timeReadException(boolean enableParceling) {
        if (enableParceling) {
            Parcel.setStackTraceParceling(true);
        }
        try {
            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
            Parcel p = Parcel.obtain();
            String msg = "TestMessage";
            p.writeException(new SecurityException(msg));
            p.setDataPosition(0);
            // First verify that remote cause is set (if parceling is enabled)
            try {
                p.readException();
            } catch (SecurityException e) {
                assertEquals(e.getMessage(), msg);
                if (enableParceling) {
                    assertTrue(e.getCause() instanceof RemoteException);
                } else {
                    assertNull(e.getCause());
                }
            }

            while (state.keepRunning()) {
                p.setDataPosition(0);
                try {
                    p.readException();
                } catch (SecurityException expected) {
                }
            }
        } finally {
            if (enableParceling) {
                Parcel.setStackTraceParceling(false);
            }
        }
    }

}
+72 −13
Original line number Diff line number Diff line
@@ -191,6 +191,7 @@ import java.util.Set;
 * {@link #readSparseArray(ClassLoader)}.
 */
public final class Parcel {

    private static final boolean DEBUG_RECYCLE = false;
    private static final boolean DEBUG_ARRAY_MAP = false;
    private static final String TAG = "Parcel";
@@ -209,6 +210,12 @@ public final class Parcel {

    private RuntimeException mStack;

    /**
     * Whether or not to parcel the stack trace of an exception. This has a performance
     * impact, so should only be included in specific processes and only on debug builds.
     */
    private static boolean sParcelExceptionStackTrace;

    private static final int POOL_SIZE = 6;
    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
    private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
@@ -325,6 +332,11 @@ public final class Parcel {
    private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
    private static native void nativeEnforceInterface(long nativePtr, String interfaceName);

    /** Last time exception with a stack trace was written */
    private static volatile long sLastWriteExceptionStackTrace;
    /** Used for throttling of writing stack trace, which is costly */
    private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;

    @CriticalNative
    private static native long nativeGetBlobAshmemSize(long nativePtr);

@@ -1696,6 +1708,11 @@ public final class Parcel {
        }
    }

    /** @hide For debugging purposes */
    public static void setStackTraceParceling(boolean enabled) {
        sParcelExceptionStackTrace = enabled;
    }

    /**
     * Special function for writing an exception result at the header of
     * a parcel, to be used when returning an exception from a transaction.
@@ -1753,6 +1770,27 @@ public final class Parcel {
            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;
            final int sizePosition = dataPosition();
            writeInt(0); // Header size will be filled in later
            StackTraceElement[] stackTrace = e.getStackTrace();
            final int truncatedSize = Math.min(stackTrace.length, 5);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < truncatedSize; i++) {
                sb.append("\tat ").append(stackTrace[i]);
            }
            writeString(sb.toString());
            final int payloadPosition = dataPosition();
            setDataPosition(sizePosition);
            // 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);
@@ -1818,7 +1856,19 @@ public final class Parcel {
        int code = readExceptionCode();
        if (code != 0) {
            String msg = readString();
            readException(code, msg);
            String remoteStackTrace = null;
            final int remoteStackPayloadSize = readInt();
            if (remoteStackPayloadSize > 0) {
                remoteStackTrace = readString();
            }
            Exception e = createException(code, msg);
            // Attach remote stack trace if availalble
            if (remoteStackTrace != null) {
                RemoteException cause = new RemoteException(
                        "Remote stack trace:\n" + remoteStackTrace, null, false, false);
                e.initCause(cause);
            }
            SneakyThrow.sneakyThrow(e);
        }
    }

@@ -1863,32 +1913,41 @@ public final class Parcel {
     * @param msg The exception message.
     */
    public final void readException(int code, String msg) {
        SneakyThrow.sneakyThrow(createException(code, msg));
    }

    /**
     * Creates an exception with the given message.
     *
     * @param code Used to determine which exception class to throw.
     * @param msg The exception message.
     */
    private Exception createException(int code, String msg) {
        switch (code) {
            case EX_PARCELABLE:
                if (readInt() > 0) {
                    SneakyThrow.sneakyThrow(
                            (Exception) readParcelable(Parcelable.class.getClassLoader()));
                    return (Exception) readParcelable(Parcelable.class.getClassLoader());
                } else {
                    throw new RuntimeException(msg + " [missing Parcelable]");
                    return new RuntimeException(msg + " [missing Parcelable]");
                }
            case EX_SECURITY:
                throw new SecurityException(msg);
                return new SecurityException(msg);
            case EX_BAD_PARCELABLE:
                throw new BadParcelableException(msg);
                return new BadParcelableException(msg);
            case EX_ILLEGAL_ARGUMENT:
                throw new IllegalArgumentException(msg);
                return new IllegalArgumentException(msg);
            case EX_NULL_POINTER:
                throw new NullPointerException(msg);
                return new NullPointerException(msg);
            case EX_ILLEGAL_STATE:
                throw new IllegalStateException(msg);
                return new IllegalStateException(msg);
            case EX_NETWORK_MAIN_THREAD:
                throw new NetworkOnMainThreadException();
                return new NetworkOnMainThreadException();
            case EX_UNSUPPORTED_OPERATION:
                throw new UnsupportedOperationException(msg);
                return new UnsupportedOperationException(msg);
            case EX_SERVICE_SPECIFIC:
                throw new ServiceSpecificException(readInt(), msg);
                return new ServiceSpecificException(readInt(), msg);
        }
        throw new RuntimeException("Unknown exception code: " + code
        return new RuntimeException("Unknown exception code: " + code
                + " msg " + msg);
    }

+6 −0
Original line number Diff line number Diff line
@@ -30,6 +30,12 @@ public class RemoteException extends AndroidException {
        super(message);
    }

    /** @hide */
    public RemoteException(String message, Throwable cause, boolean enableSuppression,
            boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    /** {@hide} */
    public RuntimeException rethrowAsRuntimeException() {
        throw new RuntimeException(this);
+6 −0
Original line number Diff line number Diff line
@@ -34,5 +34,11 @@ public class AndroidException extends Exception {
    public AndroidException(Exception cause) {
        super(cause);
    }

    /** @hide */
    protected AndroidException(String message, Throwable cause, boolean enableSuppression,
            boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
};
+4 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.os.FileUtils;
import android.os.IIncidentManager;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
@@ -360,6 +361,9 @@ public final class SystemServer {
            // to avoid throwing BadParcelableException.
            BaseBundle.setShouldDefuse(true);

            // Within the system server, when parceling exceptions, include the stack trace
            Parcel.setStackTraceParceling(true);

            // Ensure binder calls into the system always run at foreground priority.
            BinderInternal.disableBackgroundScheduling(true);