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

Commit 3552a959 authored by Romain Guy's avatar Romain Guy Committed by Steve Kondik
Browse files

Prevent possible memory leak in SpanSet

If SpanSet.init() is called several times in a row with different
values, it is possible to change "numberOfSpans" in a way that
will prevent SpanSet.recycle() from nulling out all the spans.

This can lead to memory leaks of large objects through spans
references. User @piwai reported this leak:

     com.squareup.marketfont.MarketSpan
     `-[1] of array android.text.style.CharacterStyle[]
       `-spans of object android.text.SpanSet
         `-mCharacterStyleSpanSet of object android.text.TextLine
           `-[1] of array android.text.TextLine[]
             `-sCached of class android.text.TextLine

The MarketSpan instance is kept alive through a recycled TextLine
which itself contains a SpanSet.

Change-Id: Idfb2233ca16895dbe735c312662eaf0b4a2ecd65
parent aa9e757d
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.text;

import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then
@@ -54,6 +55,7 @@ public class SpanSet<E> {
            spanFlags = new int[length];
        }

        int prevNumberOfSpans = numberOfSpans;
        numberOfSpans = 0;
        for (int i = 0; i < length; i++) {
            final E span = allSpans[i];
@@ -71,6 +73,12 @@ public class SpanSet<E> {

            numberOfSpans++;
        }

        // cleanup extra spans left over from previous init() call
        if (numberOfSpans < prevNumberOfSpans) {
            // prevNumberofSpans was > 0, therefore spans != null
            Arrays.fill(spans, numberOfSpans, prevNumberOfSpans, null);
        }
    }

    /**
@@ -103,9 +111,8 @@ public class SpanSet<E> {
     * Removes all internal references to the spans to avoid memory leaks.
     */
    public void recycle() {
        // The spans array is guaranteed to be not null when numberOfSpans is > 0
        for (int i = 0; i < numberOfSpans; i++) {
            spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled
        if (spans != null) {
            Arrays.fill(spans, 0, numberOfSpans, null);
        }
    }
}