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

Commit 6ef8030a authored by Kurt Nelson's avatar Kurt Nelson Committed by Android (Google) Code Review
Browse files

Merge "Extensible StrictMode"

parents ce19db9e e6e0ebf1
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() */