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

Commit 6985fb39 authored by Jing Ji's avatar Jing Ji
Browse files

Fix memory leak in StrictMode violation throttling

* Add hashCode() to the base Violation class for dup detections
* Discard obsoleted violation fingerprints if possible

Bug: 159128771
Bug: 159626227
Test: run cts -m CtsOsTestCases -t android.os.cts.StrictModeTest
Test: Manual - Loop generation of StrictMode violations
Test: Manual - Check heap dump
Change-Id: I19a0922fe010fad97b6b819e73acb7db08f84930
parent 26dfdc58
Loading
Loading
Loading
Loading
+47 −16
Original line number Original line Diff line number Diff line
@@ -84,6 +84,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Deque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;
@@ -189,6 +191,9 @@ public final class StrictMode {
    // Only show an annoying dialog at most every 30 seconds
    // Only show an annoying dialog at most every 30 seconds
    private static final long MIN_DIALOG_INTERVAL_MS = 30000;
    private static final long MIN_DIALOG_INTERVAL_MS = 30000;


    // Only log a dropbox entry at most every 30 seconds
    private static final long MIN_DROPBOX_INTERVAL_MS = 3000;

    // How many Span tags (e.g. animations) to report.
    // How many Span tags (e.g. animations) to report.
    private static final int MAX_SPAN_TAGS = 20;
    private static final int MAX_SPAN_TAGS = 20;


@@ -1752,16 +1757,20 @@ public final class StrictMode {
            // Not perfect, but fast and good enough for dup suppression.
            // Not perfect, but fast and good enough for dup suppression.
            Integer crashFingerprint = info.hashCode();
            Integer crashFingerprint = info.hashCode();
            long lastViolationTime = 0;
            long lastViolationTime = 0;
            long now = SystemClock.uptimeMillis();
            if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
                if (mLastViolationTime != null) {
                if (mLastViolationTime != null) {
                    Long vtime = mLastViolationTime.get(crashFingerprint);
                    Long vtime = mLastViolationTime.get(crashFingerprint);
                    if (vtime != null) {
                    if (vtime != null) {
                        lastViolationTime = vtime;
                        lastViolationTime = vtime;
                    }
                    }
                    clampViolationTimeMap(mLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS,
                                Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS)));
                } else {
                } else {
                    mLastViolationTime = new ArrayMap<>(1);
                    mLastViolationTime = new ArrayMap<>(1);
                }
                }
            long now = SystemClock.uptimeMillis();
                mLastViolationTime.put(crashFingerprint, now);
                mLastViolationTime.put(crashFingerprint, now);
            }
            long timeSinceLastViolationMillis =
            long timeSinceLastViolationMillis =
                    lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
                    lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);


@@ -1780,7 +1789,8 @@ public final class StrictMode {
                penaltyMask |= PENALTY_DIALOG;
                penaltyMask |= PENALTY_DIALOG;
            }
            }


            if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) {
            if (info.penaltyEnabled(PENALTY_DROPBOX)
                    && timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) {
                penaltyMask |= PENALTY_DROPBOX;
                penaltyMask |= PENALTY_DROPBOX;
            }
            }


@@ -2215,6 +2225,23 @@ public final class StrictMode {
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
    private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();


    /**
     * Clamp the given map by removing elements with timestamp older than the given retainSince.
     */
    private static void clampViolationTimeMap(final @NonNull Map<Integer, Long> violationTime,
            final long retainSince) {
        final Iterator<Map.Entry<Integer, Long>> iterator = violationTime.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, Long> e = iterator.next();
            if (e.getValue() < retainSince) {
                // Remove stale entries
                iterator.remove();
            }
        }
        // Ideally we'd cap the total size of the map, though it'll involve quickselect of topK,
        // seems not worth it (saving some space immediately but they will be obsoleted soon anyway)
    }

    /** @hide */
    /** @hide */
    public static void onVmPolicyViolation(Violation originStack) {
    public static void onVmPolicyViolation(Violation originStack) {
        onVmPolicyViolation(originStack, false);
        onVmPolicyViolation(originStack, false);
@@ -2238,6 +2265,7 @@ public final class StrictMode {
        final long now = SystemClock.uptimeMillis();
        final long now = SystemClock.uptimeMillis();
        long lastViolationTime;
        long lastViolationTime;
        long timeSinceLastViolationMillis = Long.MAX_VALUE;
        long timeSinceLastViolationMillis = Long.MAX_VALUE;
        if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
            synchronized (sLastVmViolationTime) {
            synchronized (sLastVmViolationTime) {
                if (sLastVmViolationTime.containsKey(fingerprint)) {
                if (sLastVmViolationTime.containsKey(fingerprint)) {
                    lastViolationTime = sLastVmViolationTime.get(fingerprint);
                    lastViolationTime = sLastVmViolationTime.get(fingerprint);
@@ -2246,6 +2274,9 @@ public final class StrictMode {
                if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
                if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
                    sLastVmViolationTime.put(fingerprint, now);
                    sLastVmViolationTime.put(fingerprint, now);
                }
                }
                clampViolationTimeMap(sLastVmViolationTime,
                        now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS));
            }
        }
        }
        if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
        if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
            // Rate limit all penalties.
            // Rate limit all penalties.
+51 −0
Original line number Original line Diff line number Diff line
@@ -18,7 +18,58 @@ package android.os.strictmode;


/** Root class for all StrictMode violations. */
/** Root class for all StrictMode violations. */
public abstract class Violation extends Throwable {
public abstract class Violation extends Throwable {
    private int mHashCode;
    private boolean mHashCodeValid;

    Violation(String message) {
    Violation(String message) {
        super(message);
        super(message);
    }
    }

    @Override
    public int hashCode() {
        synchronized (this) {
            if (mHashCodeValid) {
                return mHashCode;
            }
            final String message = getMessage();
            final Throwable cause = getCause();
            int hashCode = message != null ? message.hashCode() : getClass().hashCode();
            hashCode = hashCode * 37 + calcStackTraceHashCode(getStackTrace());
            hashCode = hashCode * 37 + (cause != null ? cause.toString().hashCode() : 0);
            mHashCodeValid = true;
            return mHashCode = hashCode;
        }
    }

    @Override
    public synchronized Throwable initCause(Throwable cause) {
        mHashCodeValid = false;
        return super.initCause(cause);
    }

    @Override
    public void setStackTrace(StackTraceElement[] stackTrace) {
        super.setStackTrace(stackTrace);
        synchronized (this) {
            mHashCodeValid = false;
        }
    }

    @Override
    public synchronized Throwable fillInStackTrace() {
        mHashCodeValid = false;
        return super.fillInStackTrace();
    }

    private static int calcStackTraceHashCode(final StackTraceElement[] stackTrace) {
        int hashCode = 17;
        if (stackTrace != null) {
            for (int i = 0; i < stackTrace.length; i++) {
                if (stackTrace[i] != null) {
                    hashCode = hashCode * 37 + stackTrace[i].hashCode();
                }
            }
        }
        return hashCode;
    }
}
}