iBotPeaches/Apktool · error · IOException

Mark not set

Error message

Mark not set

What it means

BinaryDataInputStream.reset() throws when reset() is called without a preceding mark(): the internal mark field still holds its -1 sentinel. The underlying stream may support marks, but this wrapper has no recorded position to rewind to.

Source

Thrown at brut.j.util/src/main/java/brut/util/BinaryDataInputStream.java:345

    @Override
    public int available() throws IOException {
        return (int) Math.min(in.available(), remaining());
    }

    @Override
    public synchronized void mark(int readlimit) {
        // We can't throw an exception here, so mark even if mark isn't supported, since reset won't work anyway.
        in.mark(readlimit);
        mMark = mPosition;
    }

    @Override
    public synchronized void reset() throws IOException {
        if (!markSupported()) {
            throw new IOException("Mark not supported");
        }
        if (mMark == -1) {
            throw new IOException("Mark not set");
        }
        in.reset();
        mPosition = mMark;
    }
}

View on GitHub (pinned to 79b63384d7)

Solutions

  1. Guarantee mark() is called on every path that can later reach reset() (mark at loop/function entry)
  2. Track a boolean or check the mark state before reset()
  3. Restructure so mark and reset stay in the same scope/try block
  4. Add a unit test covering the branch that previously skipped mark()

Example fix

// before
if (needsLookahead) { in.mark(64); peek(); }
in.reset(); // Mark not set when needsLookahead was false

// after
in.mark(64);
if (needsLookahead) { peek(); in.reset(); } else { /* no rewind needed */ }
Defensive patterns

Strategy: validation

Validate before calling

// Pair every reachable reset() with a guaranteed mark()
final boolean[] marked = { false };
Runnable mark = () -> { in.mark(BUF); marked[0] = true; };
// before reset:
if (!marked[0]) throw new IllegalStateException("reset() without mark() on this path");
in.reset();

Try / catch

try {
    in.reset();
} catch (IOException e) {
    if ("Mark not set".equals(e.getMessage())) {
        // control-flow bug: mark was skipped on this path — fix the caller, do not retry
        throw new IllegalStateException("Parser bug: reset reached without mark", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling reset() before any mark() on the same stream instance, or after logic paths where mark() was skipped (conditional parsing branches that only mark sometimes).

Common situations: Parser control flow where one branch marks and rewinds while another reaches the same reset() call without marking; refactors that move mark() into a guard block; copy-paste of reset() into a second parse loop sharing the stream.

Related errors


AI-assisted analysis of iBotPeaches/Apktool@79b63384d7 (2026-08-14). Data as JSON: /api/errors/2aa2ba79a9fec803. Report an issue: GitHub.