Tencent/QMUI_Android · error · RuntimeException

unsafeAddEffect: start($start) is bigger than end($end)

Error message

unsafeAddEffect: start($start) is bigger than end($end)

What it means

TypeModel.unsafeAddEffect attaches a text effect (typeface, text size, background, color, underline) to a [start, end) character range. It requires start <= end; a start greater than end cannot form a valid element range and throws a RuntimeException immediately, before any element lookup.

Source

Thrown at type/src/main/java/com/qmuiteam/qmui/type/TypeModel.kt:65

    fun addTextColorEffect(start: Int, end: Int, textColor: Int): EffectRemover? {
        val types: MutableList<Int> = ArrayList()
        types.add(TypeEnvironment.TYPE_TEXT_COLOR)
        return unsafeAddEffect(start, end, types) { env -> env.textColor = textColor }
    }

    fun addUnderLineEffect(start: Int, end: Int, underLineColor: Int, underLineHeight: Int): EffectRemover? {
        val types: MutableList<Int> = ArrayList()
        types.add(TypeEnvironment.TYPE_BORDER_BOTTOM_WIDTH)
        types.add(TypeEnvironment.TYPE_BORDER_BOTTOM_COLOR)
        return unsafeAddEffect(
            start, end, types
        ) { env -> env.setBorderBottom(underLineHeight, underLineColor) }
    }

    fun unsafeAddEffect(start: Int, end: Int, types: List<Int>, environmentUpdater: EnvironmentUpdater): EffectRemover? {
        if (start > end) {
            throw RuntimeException("unsafeAddEffect: start($start) is bigger than end($end)")
        }
        val elementStart = getByPos(start)
        val elementEnd = getByPos(end)
        if (elementStart == null || elementEnd == null) {
            return null
        }
        for (type in types) {
            elementStart.addSaveType(type)
            elementEnd.addRestoreType(type)
        }
        elementStart.addEnvironmentUpdater(environmentUpdater)
        firstEffect = if (firstEffect == null) {
            elementStart
        } else {
            elementStart.insertEffectTo(firstEffect!!)
        }
        firstEffect = elementEnd.insertEffectTo(firstEffect!!)
        return DefaultEffectRemove(this, start, end, types, environmentUpdater)

View on GitHub (pinned to 026e7d4866)

Solutions

  1. Validate start <= end before calling; swap or clamp the range if inverted.
  2. Recompute the offsets against the current text content (the offsets refer to positions in the TypeModel, not the original string).
  3. Check the return value as well: if getByPos returns null for either endpoint the call silently no-ops (returns null), so log/verify the effect was applied.

Example fix

// before
typeModel.unsafeAddEffect(end, start, types, updater); // throws when end > start

// after
int s = Math.min(start, end);
int e = Math.max(start, end);
if (s <= e) {
    typeModel.unsafeAddEffect(s, e, types, updater);
}
Defensive patterns

Strategy: validation

Validate before calling

public static void addEffectSafe(TypeModel m, int start, int end, List<Integer> types, EnvironmentUpdater u) {
    if (start > end) throw new IllegalArgumentException("start(" + start + ") > end(" + end + ")");
    m.unsafeAddEffect(start, end, types, u);
}

Try / catch

try {
    typeModel.unsafeAddEffect(start, end, types, updater);
} catch (RuntimeException e) {
    Log.w("TypeModel", "invalid effect range: " + start + ".." + end, e);
}

Prevention

When it happens

Trigger: Calling unsafeAddEffect(start, end, types, updater) — or any wrapper like addTypefaceEffect/addTextSizeEffect/addBgEffect/addTextColorEffect/addUnderLineEffect routed to it — with start > end (e.g. swapped arguments or a range computed against a different string).

Common situations: Computing highlight ranges by searching text after edits, so stale offsets produce inverted ranges; swapping the two positional arguments; applying effects before the text model is updated to match the offsets.

Related errors


AI-assisted analysis of Tencent/QMUI_Android@026e7d4866 (2026-09-06). Data as JSON: /api/errors/392e0fac810780ef. Report an issue: GitHub.