DO NOT MERGE Re-implement reading/writing Throwables from/to Parcel, without

Parcel private APIs.

Bug:197228210
Test: atest CtsSecurityTestCases:android.security.cts.AndroidFutureTest
(cherry picked from I577da5a3bc4ed537123b7eceaa5addf8f7bb0d92 and
Icc5ce702f0cd84e9136dee3c65f63619df697358)

Change-Id: I1d488c475f2f7af835a67496535cecdd6987c0cf
This commit is contained in:
Hai Zhang
2020-12-30 18:30:25 -08:00
committed by Ganesh Olekar
parent c9bc394198
commit 562f1bd91f
2 changed files with 86 additions and 58 deletions

View File

@@ -16,23 +16,21 @@
package com.android.internal.infra; package com.android.internal.infra;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.CallSuper; import android.annotation.CallSuper;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Looper;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.ExceptionUtils; import android.util.EventLog;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.reflect.Constructor;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletionStage;
@@ -75,14 +73,16 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static final String LOG_TAG = AndroidFuture.class.getSimpleName(); private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
private static final Executor DIRECT_EXECUTOR = Runnable::run;
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
private static @Nullable Handler sMainHandler;
private final @NonNull Object mLock = new Object(); private final @NonNull Object mLock = new Object();
@GuardedBy("mLock") @GuardedBy("mLock")
private @Nullable BiConsumer<? super T, ? super Throwable> mListener; private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
@GuardedBy("mLock") @GuardedBy("mLock")
private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR; private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR;
private @NonNull Handler mTimeoutHandler = Handler.getMain(); private @NonNull Handler mTimeoutHandler = getMainHandler();
private final @Nullable IAndroidFuture mRemoteOrigin; private final @Nullable IAndroidFuture mRemoteOrigin;
public AndroidFuture() { public AndroidFuture() {
@@ -96,7 +96,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
// Done // Done
if (in.readBoolean()) { if (in.readBoolean()) {
// Failed // Failed
completeExceptionally(unparcelException(in)); completeExceptionally(readThrowable(in));
} else { } else {
// Success // Success
complete((T) in.readValue(null)); complete((T) in.readValue(null));
@@ -108,6 +108,15 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
} }
} }
@NonNull
private static Handler getMainHandler() {
// This isn't thread-safe but we are okay with it.
if (sMainHandler == null) {
sMainHandler = new Handler(Looper.getMainLooper());
}
return sMainHandler;
}
/** /**
* Create a completed future with the given value. * Create a completed future with the given value.
* *
@@ -236,9 +245,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
if (mListenerExecutor == DIRECT_EXECUTOR) { if (mListenerExecutor == DIRECT_EXECUTOR) {
callListener(listener, res, err); callListener(listener, res, err);
} else { } else {
mListenerExecutor.execute(PooledLambda mListenerExecutor.execute(() -> callListener(listener, res, err));
.obtainRunnable(AndroidFuture::callListener, listener, res, err)
.recycleOnUse());
} }
} }
@@ -260,7 +267,8 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
} else { } else {
// listener exception-case threw // listener exception-case threw
// give up on listener but preserve the original exception when throwing up // give up on listener but preserve the original exception when throwing up
throw ExceptionUtils.appendCause(t, err); t.addSuppressed(err);
throw t;
} }
} }
} catch (Throwable t2) { } catch (Throwable t2) {
@@ -272,9 +280,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
/** @inheritDoc */ /** @inheritDoc */
//@Override //TODO uncomment once java 9 APIs are exposed to frameworks //@Override //TODO uncomment once java 9 APIs are exposed to frameworks
public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) { public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) {
Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this); mTimeoutHandler.postDelayed(this::triggerTimeout, this, unit.toMillis(timeout));
msg.obj = this;
mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout));
return this; return this;
} }
@@ -507,7 +513,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
result = get(); result = get();
} catch (Throwable t) { } catch (Throwable t) {
dest.writeBoolean(true); dest.writeBoolean(true);
parcelException(dest, unwrapExecutionException(t)); writeThrowable(dest, unwrapExecutionException(t));
return; return;
} }
dest.writeBoolean(false); dest.writeBoolean(false);
@@ -545,45 +551,75 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
* Alternative to {@link Parcel#writeException} that stores the stack trace, in a * Alternative to {@link Parcel#writeException} that stores the stack trace, in a
* way consistent with the binder IPC exception propagation behavior. * way consistent with the binder IPC exception propagation behavior.
*/ */
private static void parcelException(Parcel p, @Nullable Throwable t) { private static void writeThrowable(@NonNull Parcel parcel, @Nullable Throwable throwable) {
p.writeBoolean(t == null); boolean hasThrowable = throwable != null;
if (t == null) { parcel.writeBoolean(hasThrowable);
if (!hasThrowable) {
return; return;
} }
p.writeInt(Parcel.getExceptionCode(t)); boolean isFrameworkParcelable = throwable instanceof Parcelable
p.writeString(t.getClass().getName()); && throwable.getClass().getClassLoader() == Parcelable.class.getClassLoader();
p.writeString(t.getMessage()); parcel.writeBoolean(isFrameworkParcelable);
p.writeStackTrace(t); if (isFrameworkParcelable) {
parcelException(p, t.getCause()); parcel.writeParcelable((Parcelable) throwable,
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
return;
}
parcel.writeString(throwable.getClass().getName());
parcel.writeString(throwable.getMessage());
StackTraceElement[] stackTrace = throwable.getStackTrace();
StringBuilder stackTraceBuilder = new StringBuilder();
int truncatedStackTraceLength = Math.min(stackTrace != null ? stackTrace.length : 0, 5);
for (int i = 0; i < truncatedStackTraceLength; i++) {
if (i > 0) {
stackTraceBuilder.append('\n');
}
stackTraceBuilder.append("\tat ").append(stackTrace[i]);
}
parcel.writeString(stackTraceBuilder.toString());
writeThrowable(parcel, throwable.getCause());
} }
/** /**
* @see #parcelException * @see #writeThrowable
*/ */
private static @Nullable Throwable unparcelException(Parcel p) { private static @Nullable Throwable readThrowable(@NonNull Parcel parcel) {
if (p.readBoolean()) { final boolean hasThrowable = parcel.readBoolean();
if (!hasThrowable) {
return null; return null;
} }
int exCode = p.readInt(); boolean isFrameworkParcelable = parcel.readBoolean();
String cls = p.readString(); if (isFrameworkParcelable) {
String msg = p.readString(); return parcel.readParcelable(Parcelable.class.getClassLoader());
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); String className = parcel.readString();
String message = parcel.readString();
String stackTrace = parcel.readString();
String messageWithStackTrace = message + '\n' + stackTrace;
Throwable throwable;
try {
Class<?> clazz = Class.forName(className, true, Parcelable.class.getClassLoader());
if (Throwable.class.isAssignableFrom(clazz)) {
Constructor<?> constructor = clazz.getConstructor(String.class);
throwable = (Throwable) constructor.newInstance(messageWithStackTrace);
} else {
android.util.EventLog.writeEvent(0x534e4554, "186530450", -1, "");
throwable = new RuntimeException(className + ": " + messageWithStackTrace);
}
} catch (Throwable t) {
throwable = new RuntimeException(className + ": " + messageWithStackTrace);
throwable.addSuppressed(t);
}
throwable.setStackTrace(EMPTY_STACK_TRACE);
Throwable cause = readThrowable(parcel);
if (cause != null) { if (cause != null) {
ex.initCause(ex); throwable.initCause(cause);
} }
return throwable;
return ex;
} }
@Override @Override

View File

@@ -26,14 +26,11 @@ import android.content.ServiceConnection;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.IInterface; import android.os.IInterface;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.text.TextUtils;
import android.util.DebugUtils;
import android.util.Log; import android.util.Log;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
@@ -47,7 +44,6 @@ import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
/** /**
* Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon * Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon
* a certain timeout. * a certain timeout.
@@ -220,6 +216,7 @@ public interface ServiceConnector<I extends IInterface> {
private final @NonNull Queue<Job<I, ?>> mQueue = this; private final @NonNull Queue<Job<I, ?>> mQueue = this;
private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>(); private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
private final @NonNull ServiceConnection mServiceConnection = this; private final @NonNull ServiceConnection mServiceConnection = this;
private final @NonNull Runnable mTimeoutDisconnect = this; private final @NonNull Runnable mTimeoutDisconnect = this;
@@ -250,9 +247,8 @@ public interface ServiceConnector<I extends IInterface> {
* {@link IInterface}. * {@link IInterface}.
* Typically this is {@code IMyInterface.Stub::asInterface} * Typically this is {@code IMyInterface.Stub::asInterface}
*/ */
public Impl(@NonNull Context context, @NonNull Intent intent, public Impl(@NonNull Context context, @NonNull Intent intent, int bindingFlags,
@Context.BindServiceFlags int bindingFlags, @UserIdInt int userId, @UserIdInt int userId, @Nullable Function<IBinder, I> binderAsInterface) {
@Nullable Function<IBinder, I> binderAsInterface) {
mContext = context; mContext = context;
mIntent = intent; mIntent = intent;
mBindingFlags = bindingFlags; mBindingFlags = bindingFlags;
@@ -264,7 +260,7 @@ public interface ServiceConnector<I extends IInterface> {
* {@link Handler} on which {@link Job}s will be called * {@link Handler} on which {@link Job}s will be called
*/ */
protected Handler getJobHandler() { protected Handler getJobHandler() {
return Handler.getMain(); return mMainHandler;
} }
/** /**
@@ -391,8 +387,7 @@ public interface ServiceConnector<I extends IInterface> {
private boolean enqueue(@NonNull Job<I, ?> job) { private boolean enqueue(@NonNull Job<I, ?> job) {
cancelTimeout(); cancelTimeout();
return getJobHandler().sendMessage(PooledLambda.obtainMessage( return getJobHandler().post(() -> enqueueJobThread(job));
ServiceConnector.Impl::enqueueJobThread, this, job));
} }
void enqueueJobThread(@NonNull Job<I, ?> job) { void enqueueJobThread(@NonNull Job<I, ?> job) {
@@ -422,7 +417,7 @@ public interface ServiceConnector<I extends IInterface> {
if (DEBUG) { if (DEBUG) {
logTrace(); logTrace();
} }
Handler.getMain().removeCallbacks(mTimeoutDisconnect); mMainHandler.removeCallbacks(mTimeoutDisconnect);
} }
void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) { void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) {
@@ -486,7 +481,7 @@ public interface ServiceConnector<I extends IInterface> {
} }
long timeout = getAutoDisconnectTimeoutMs(); long timeout = getAutoDisconnectTimeoutMs();
if (timeout > 0) { if (timeout > 0) {
Handler.getMain().postDelayed(mTimeoutDisconnect, timeout); mMainHandler.postDelayed(mTimeoutDisconnect, timeout);
} else if (DEBUG) { } else if (DEBUG) {
Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this); Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this);
} }
@@ -502,7 +497,7 @@ public interface ServiceConnector<I extends IInterface> {
logTrace(); logTrace();
} }
mUnbinding = true; mUnbinding = true;
getJobHandler().sendMessage(PooledLambda.obtainMessage(Impl::unbindJobThread, this)); getJobHandler().post(this::unbindJobThread);
} }
void unbindJobThread() { void unbindJobThread() {
@@ -659,10 +654,7 @@ public interface ServiceConnector<I extends IInterface> {
} }
private void logTrace() { private void logTrace() {
Log.i(LOG_TAG, Log.i(LOG_TAG, "See stacktrace", new Throwable());
TextUtils.join(" -> ",
DebugUtils.callersWithin(ServiceConnector.class, /* offset= */ 1))
+ "(" + this + ")");
} }
/** /**