antlr/antlr4 · error · IllegalArgumentException

cannot seek to negative index {index}

Error message

cannot seek to negative index {index}

What it means

UnbufferedTokenStream.seek(index) accepts an absolute index only inside its retained moving window. It computes i = index - bufferStartIndex; i < 0 means the requested token was already flushed. Despite the wording, the printed index can be non-negative: seek(0) after the buffer start advanced also produces this exception.

Source

Thrown at runtime/Java/src/org/antlr/v4/runtime/UnbufferedTokenStream.java:256

	public int index() {
		return currentTokenIndex;
	}

	@Override
	public void seek(int index) { // seek to absolute index
		if (index == currentTokenIndex) {
			return;
		}

		if (index > currentTokenIndex) {
			sync(index - currentTokenIndex);
			index = Math.min(index, getBufferStartIndex() + n - 1);
		}

		int bufferStartIndex = getBufferStartIndex();
		int i = index - bufferStartIndex;
		if ( i < 0 ) {
			throw new IllegalArgumentException("cannot seek to negative index " + index);
		}
		else if (i >= n) {
			throw new UnsupportedOperationException("seek to index outside buffer: "+
													index+" not in "+ bufferStartIndex +".."+(bufferStartIndex +n));
		}

		p = i;
		currentTokenIndex = index;
		if (p == 0) {
			lastToken = lastTokenBufferStart;
		}
		else {
			lastToken = tokens[p-1];
		}
	}

	@Override
	public int size() {

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Call mark() before consuming tokens that you may need to seek back to; release it only after backtracking is complete.
  2. For random access or seek(0), use BufferedTokenStream/CommonTokenStream instead.
  3. Validate index >= 0 and never seek before the point where the stream was constructed/marked.
  4. Recreate the token source and stream when old tokens must be replayed and were not retained.

Example fix

// before
while (tokens.LA(1) != Token.EOF) tokens.consume();
tokens.seek(0); // old tokens were flushed

// after
int marker = tokens.mark(); // mark before consuming
try {
    while (tokens.LA(1) != Token.EOF) tokens.consume();
    tokens.seek(0);
} finally {
    tokens.release(marker);
}
Defensive patterns

Strategy: validation

Validate before calling

static boolean canSeek(UnbufferedTokenStream<Token> tokens, int index, boolean haveProtectingMark) {
    if (index < 0) return false;
    return index >= tokens.index() || haveProtectingMark;
}

Type guard

static boolean supportsArbitraryBackwardSeek(TokenStream tokens) {
    return tokens instanceof BufferedTokenStream;
}

Try / catch

try {
    tokens.seek(index);
} catch (IllegalArgumentException e) {
    // token is outside the retained unbuffered window; replay from a new stream
}

Prevention

When it happens

Trigger: Calling seek(0) after consuming tokens without an active marker; seeking backward to any index before getBufferStartIndex(); passing a negative index; or calling a reset/backtracking routine after the unbuffered stream has flushed old tokens.

Common situations: Porting random-access code from BufferedTokenStream to UnbufferedTokenStream, replaying a token range after parsing, and custom backtracking that forgot to mark the region before consuming.

Related errors


AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14). Data as JSON: /api/errors/98ad624e4776b22e. Report an issue: GitHub.