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

Commit e6e0ebf1 authored by Kurt Nelson's avatar Kurt Nelson
Browse files

Extensible StrictMode

go/extensible-strictmode for design doc.

Bug: 63535923
Change-Id: Ic5c48fc06d2167dc99b86e264e114a9af49f12a1
Test: cts-tradefed run commandAndExit cts-dev -m CtsOsTestCases -t
android.os.cts.StrictModeTest
parent 45a9600e
Loading
Loading
Loading
Loading
+226 −81
Original line number Diff line number Diff line
@@ -16,11 +16,11 @@
package android.os;

import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -75,6 +75,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

/**
@@ -163,12 +165,14 @@ public final class StrictMode {
    private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";

    /**
     * Quick feature-flag that can be used to disable the defaults provided by
     * {@link #initThreadDefaults(ApplicationInfo)} and
     * {@link #initVmDefaults(ApplicationInfo)}.
     * Quick feature-flag that can be used to disable the defaults provided by {@link
     * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
     */
    private static final boolean DISABLE = false;

    // Only apply VM penalties for the same violation at this interval.
    private static final long MIN_VM_INTERVAL_MS = 1000;

    // Only log a duplicate stack trace to the logs every second.
    private static final long MIN_LOG_INTERVAL_MS = 1000;

@@ -374,6 +378,32 @@ public final class StrictMode {

    private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;

    private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
            new ThreadLocal<>();
    private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();

    /**
     * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
     * provided executor when a Thread violation occurs.
     *
     * @hide
     */
    public interface OnThreadViolationListener {
        /** Called on a thread policy violation. */
        void onThreadViolation(Violation v);
    }

    /**
     * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
     * provided executor when a VM violation occurs.
     *
     * @hide
     */
    public interface OnVmViolationListener {
        /** Called on a VM policy violation. */
        void onVmViolation(Violation v);
    }

    /** {@hide} */
    @TestApi
    public static void setViolationLogger(ViolationLogger listener) {
@@ -403,12 +433,16 @@ public final class StrictMode {
     */
    public static final class ThreadPolicy {
        /** The default, lax policy which doesn't catch anything. */
        public static final ThreadPolicy LAX = new ThreadPolicy(0);
        public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);

        final int mask;
        final OnThreadViolationListener mListener;
        final Executor mCallbackExecutor;

        private ThreadPolicy(int mask) {
        private ThreadPolicy(int mask, OnThreadViolationListener listener, Executor executor) {
            this.mask = mask;
            mListener = listener;
            mCallbackExecutor = executor;
        }

        @Override
@@ -436,6 +470,8 @@ public final class StrictMode {
         */
        public static final class Builder {
            private int mMask = 0;
            private OnThreadViolationListener mListener;
            private Executor mExecutor;

            /**
             * Create a Builder that detects nothing and has no violations. (but note that {@link
@@ -601,6 +637,22 @@ public final class StrictMode {
                return enable(PENALTY_DROPBOX);
            }

            /**
             * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
             * executor every violation.
             *
             * @hide
             */
            public Builder penaltyListener(
                    @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
                if (executor == null) {
                    throw new NullPointerException("executor must not be null");
                }
                mListener = listener;
                mExecutor = executor;
                return this;
            }

            private Builder enable(int bit) {
                mMask |= bit;
                return this;
@@ -620,7 +672,8 @@ public final class StrictMode {
            public ThreadPolicy build() {
                // If there are detection bits set but no violation bits
                // set, enable simple logging.
                if (mMask != 0
                if (mListener == null
                        && mMask != 0
                        && (mMask
                                        & (PENALTY_DEATH
                                                | PENALTY_LOG
@@ -629,7 +682,7 @@ public final class StrictMode {
                                == 0) {
                    penaltyLog();
                }
                return new ThreadPolicy(mMask);
                return new ThreadPolicy(mMask, mListener, mExecutor);
            }
        }
    }
@@ -641,19 +694,27 @@ public final class StrictMode {
     */
    public static final class VmPolicy {
        /** The default, lax policy which doesn't catch anything. */
        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);

        final int mask;
        final OnVmViolationListener mListener;
        final Executor mCallbackExecutor;

        // Map from class to max number of allowed instances in memory.
        final HashMap<Class, Integer> classInstanceLimit;

        private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
        private VmPolicy(
                int mask,
                HashMap<Class, Integer> classInstanceLimit,
                OnVmViolationListener listener,
                Executor executor) {
            if (classInstanceLimit == null) {
                throw new NullPointerException("classInstanceLimit == null");
            }
            this.mask = mask;
            this.classInstanceLimit = classInstanceLimit;
            mListener = listener;
            mCallbackExecutor = executor;
        }

        @Override
@@ -681,6 +742,8 @@ public final class StrictMode {
         */
        public static final class Builder {
            private int mMask;
            private OnVmViolationListener mListener;
            private Executor mExecutor;

            private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
            private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
@@ -694,6 +757,8 @@ public final class StrictMode {
                mMask = base.mask;
                mClassInstanceLimitNeedCow = true;
                mClassInstanceLimit = base.classInstanceLimit;
                mListener = base.mListener;
                mExecutor = base.mCallbackExecutor;
            }

            /**
@@ -910,6 +975,21 @@ public final class StrictMode {
                return enable(PENALTY_DROPBOX);
            }

            /**
             * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
             *
             * @hide
             */
            public Builder penaltyListener(
                    @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
                if (executor == null) {
                    throw new NullPointerException("executor must not be null");
                }
                mListener = listener;
                mExecutor = executor;
                return this;
            }

            private Builder enable(int bit) {
                mMask |= bit;
                return this;
@@ -929,7 +1009,8 @@ public final class StrictMode {
            public VmPolicy build() {
                // If there are detection bits set but no violation bits
                // set, enable simple logging.
                if (mMask != 0
                if (mListener == null
                        && mMask != 0
                        && (mMask
                                        & (PENALTY_DEATH
                                                | PENALTY_LOG
@@ -940,7 +1021,9 @@ public final class StrictMode {
                }
                return new VmPolicy(
                        mMask,
                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
                        mListener,
                        mExecutor);
            }
        }
    }
@@ -973,6 +1056,8 @@ public final class StrictMode {
     */
    public static void setThreadPolicy(final ThreadPolicy policy) {
        setThreadPolicyMask(policy.mask);
        sThreadViolationListener.set(policy.mListener);
        sThreadViolationExecutor.set(policy.mCallbackExecutor);
    }

    /** @hide */
@@ -1029,7 +1114,10 @@ public final class StrictMode {
        // introduce VmPolicy cleanly) but this isn't particularly
        // optimal for users who might call this method often.  This
        // should be in a thread-local and not allocate on each call.
        return new ThreadPolicy(getThreadPolicyMask());
        return new ThreadPolicy(
                getThreadPolicyMask(),
                sThreadViolationListener.get(),
                sThreadViolationExecutor.get());
    }

    /**
@@ -1042,7 +1130,10 @@ public final class StrictMode {
     *     end of a block
     */
    public static ThreadPolicy allowThreadDiskWrites() {
        return new ThreadPolicy(allowThreadDiskWritesMask());
        return new ThreadPolicy(
                allowThreadDiskWritesMask(),
                sThreadViolationListener.get(),
                sThreadViolationExecutor.get());
    }

    /** @hide */
@@ -1063,7 +1154,10 @@ public final class StrictMode {
     * @return the old policy, to be passed to setThreadPolicy to restore the policy.
     */
    public static ThreadPolicy allowThreadDiskReads() {
        return new ThreadPolicy(allowThreadDiskReadsMask());
        return new ThreadPolicy(
                allowThreadDiskReadsMask(),
                sThreadViolationListener.get(),
                sThreadViolationExecutor.get());
    }

    /** @hide */
@@ -1076,16 +1170,27 @@ public final class StrictMode {
        return oldPolicyMask;
    }

    private static ThreadPolicy allowThreadViolations() {
        ThreadPolicy oldPolicy = getThreadPolicy();
        setThreadPolicyMask(0);
        return oldPolicy;
    }

    private static VmPolicy allowVmViolations() {
        VmPolicy oldPolicy = getVmPolicy();
        sVmPolicy = VmPolicy.LAX;
        return oldPolicy;
    }

    /**
     * Determine if the given app is "bundled" as part of the system image.
     * These bundled apps are developed in lock-step with the OS, and they
     * aren't updated outside of an OTA, so we want to chase any
     * {@link StrictMode} regressions by enabling detection when running on
     * {@link Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
     * <p>
     * Unbundled apps included in the system image are expected to detect and
     * triage their own {@link StrictMode} issues separate from the OS release
     * process, which is why we don't enable them here.
     * Determine if the given app is "bundled" as part of the system image. These bundled apps are
     * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
     * chase any {@link StrictMode} regressions by enabling detection when running on {@link
     * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
     *
     * <p>Unbundled apps included in the system image are expected to detect and triage their own
     * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
     * them here.
     *
     * @hide
     */
@@ -1122,8 +1227,8 @@ public final class StrictMode {
     */
    public static void initThreadDefaults(ApplicationInfo ai) {
        final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
        final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion
                : Build.VERSION_CODES.CUR_DEVELOPMENT;
        final int targetSdkVersion =
                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;

        // Starting in HC, we don't allow network usage on the main thread
        if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
@@ -1162,8 +1267,8 @@ public final class StrictMode {
     */
    public static void initVmDefaults(ApplicationInfo ai) {
        final VmPolicy.Builder builder = new VmPolicy.Builder();
        final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion
                : Build.VERSION_CODES.CUR_DEVELOPMENT;
        final int targetSdkVersion =
                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;

        // Starting in N, we don't allow file:// Uri exposure
        if (targetSdkVersion >= Build.VERSION_CODES.N) {
@@ -1204,7 +1309,9 @@ public final class StrictMode {
                        sVmPolicy.mask
                                | DETECT_VM_FILE_URI_EXPOSURE
                                | PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
                        sVmPolicy.classInstanceLimit);
                        sVmPolicy.classInstanceLimit,
                        sVmPolicy.mListener,
                        sVmPolicy.mCallbackExecutor);
    }

    /**
@@ -1219,7 +1326,9 @@ public final class StrictMode {
                        sVmPolicy.mask
                                & ~(DETECT_VM_FILE_URI_EXPOSURE
                                        | PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
                        sVmPolicy.classInstanceLimit);
                        sVmPolicy.classInstanceLimit,
                        sVmPolicy.mListener,
                        sVmPolicy.mCallbackExecutor);
    }

    /**
@@ -1409,7 +1518,7 @@ public final class StrictMode {
            //       go into this immediate mode?
            if (looper == null || (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
                info.durationMillis = -1; // unknown (redundant, already set)
                handleViolation(info);
                onThreadPolicyViolation(info);
                return;
            }

@@ -1447,8 +1556,7 @@ public final class StrictMode {
            THREAD_HANDLER
                    .get()
                    .postAtFrontOfQueue(
                            new Runnable() {
                                public void run() {
                            () -> {
                                long loopFinishTime = SystemClock.uptimeMillis();

                                // Note: we do this early, before handling the
@@ -1467,10 +1575,9 @@ public final class StrictMode {
                                    v.violationNumThisLoop = n + 1;
                                    v.durationMillis =
                                            (int) (loopFinishTime - v.violationUptimeMillis);
                                        handleViolation(v);
                                    onThreadPolicyViolation(v);
                                }
                                records.clear();
                                }
                            });
        }

@@ -1479,13 +1586,13 @@ public final class StrictMode {
        // violation fired and now (after the violating code ran) due
        // to people who push/pop temporary policy in regions of code,
        // hence the policy being passed around.
        void handleViolation(final ViolationInfo info) {
            if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.mPolicy);
        void onThreadPolicyViolation(final ViolationInfo info) {
            if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; policy=" + info.mPolicy);

            if (info.penaltyEnabled(PENALTY_GATHER)) {
                ArrayList<ViolationInfo> violations = gatheredViolations.get();
                if (violations == null) {
                    violations = new ArrayList<ViolationInfo>(1);
                    violations = new ArrayList<>(1);
                    gatheredViolations.set(violations);
                }
                for (ViolationInfo previous : violations) {
@@ -1519,6 +1626,8 @@ public final class StrictMode {
                sLogger.log(info);
            }

            final Violation violation = info.mViolation;

            // The violationMaskSubset, passed to ActivityManager, is a
            // subset of the original StrictMode policy bitmask, with
            // only the bit violated and penalty bits to be executed
@@ -1552,13 +1661,30 @@ public final class StrictMode {
            }

            if ((info.getPolicyMask() & PENALTY_DEATH) != 0) {
                executeDeathPenalty(info);
                throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
            }

            // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
            // executor finishes before crashing.
            final OnThreadViolationListener listener = sThreadViolationListener.get();
            final Executor executor = sThreadViolationExecutor.get();
            if (listener != null && executor != null) {
                try {
                    executor.execute(
                            () -> {
                                // Lift violated policy to prevent infinite recursion.
                                ThreadPolicy oldPolicy = allowThreadViolations();
                                try {
                                    listener.onThreadViolation(violation);
                                } finally {
                                    setThreadPolicy(oldPolicy);
                                }
                            });
                } catch (RejectedExecutionException e) {
                    Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
                }
            }
        }

    private static void executeDeathPenalty(ViolationInfo info) {
        throw new RuntimeException("StrictMode death penalty", info.mViolation);
    }

    /**
@@ -1736,9 +1862,8 @@ public final class StrictMode {
     * #setThreadPolicy}.
     */
    public static void enableDefaults() {
        StrictMode.setThreadPolicy(
                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
        setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
        setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
    }

    /** @hide */
@@ -1857,11 +1982,11 @@ public final class StrictMode {
    }

    /** @hide */
    public static void onVmPolicyViolation(Violation originStack, boolean forceDeath) {
    public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
        final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
        final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
        final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
        final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
        final ViolationInfo info = new ViolationInfo(violation, sVmPolicy.mask);

        // Erase stuff not relevant for process-wide violations
        info.numAnimationsRunning = 0;
@@ -1870,37 +1995,37 @@ public final class StrictMode {

        final Integer fingerprint = info.hashCode();
        final long now = SystemClock.uptimeMillis();
        long lastViolationTime = 0;
        long lastViolationTime;
        long timeSinceLastViolationMillis = Long.MAX_VALUE;
        synchronized (sLastVmViolationTime) {
            if (sLastVmViolationTime.containsKey(fingerprint)) {
                lastViolationTime = sLastVmViolationTime.get(fingerprint);
                timeSinceLastViolationMillis = now - lastViolationTime;
            }
            if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
            if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
                sLastVmViolationTime.put(fingerprint, now);
            }
        }

        if (penaltyLog && sLogger != null) {
            sLogger.log(info);
        if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
            // Rate limit all penalties.
            return;
        }
        if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {

        if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
            sLogger.log(info);
        }

        int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);

        if (penaltyDropbox && !penaltyDeath) {
        if (penaltyDropbox) {
            if (penaltyDeath) {
                handleApplicationStrictModeViolation(violationMaskSubset, info);
            } else {
                // Common case for userdebug/eng builds.  If no death and
                // just dropboxing, we can do the ActivityManager call
                // asynchronously.
                dropboxViolationAsync(violationMaskSubset, info);
            return;
            }

        if (penaltyDropbox && lastViolationTime == 0) {
            handleApplicationStrictModeViolation(violationMaskSubset, info);
        }

        if (penaltyDeath) {
@@ -1908,6 +2033,26 @@ public final class StrictMode {
            Process.killProcess(Process.myPid());
            System.exit(10);
        }

        // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
        // all executors. penaltyDeath supersedes penaltyCallback.
        if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
            final OnVmViolationListener listener = sVmPolicy.mListener;
            try {
                sVmPolicy.mCallbackExecutor.execute(
                        () -> {
                            // Lift violated policy to prevent infinite recursion.
                            VmPolicy oldPolicy = allowVmViolations();
                            try {
                                listener.onVmViolation(violation);
                            } finally {
                                setVmPolicy(oldPolicy);
                            }
                        });
            } catch (RejectedExecutionException e) {
                Log.e(TAG, "VmPolicy penaltyCallback failed", e);
            }
        }
    }

    /** Called from Parcel.writeNoException() */