antlr/antlr4 · error · IllegalStateException
release() called with an invalid marker.
Error message
release() called with an invalid marker.
What it means
UnbufferedCharStream.release(marker) requires markers to be released in strict last-in-first-out order: the only valid argument is the most recent mark() result. Releasing an older marker, releasing the same marker twice, or releasing out of order corrupts the buffer-window bookkeeping, so it throws IllegalStateException.
Source
Thrown at runtime/Java/src/org/antlr/v4/runtime/UnbufferedCharStream.java:255
@Override
public int mark() {
if (numMarkers == 0) {
lastCharBufferStart = lastChar;
}
int mark = -numMarkers - 1;
numMarkers++;
return mark;
}
/** Decrement number of markers, resetting buffer if we hit 0.
* @param marker
*/
@Override
public void release(int marker) {
int expectedMark = -numMarkers;
if ( marker!=expectedMark ) {
throw new IllegalStateException("release() called with an invalid marker.");
}
numMarkers--;
if ( numMarkers==0 && p > 0 ) { // release buffer when we can, but don't do unnecessary work
// Copy data[p]..data[n-1] to data[0]..data[(n-1)-p], reset ptrs
// p is last valid char; move nothing if p==n as we have no valid char
System.arraycopy(data, p, data, 0, n - p); // shift n-p char from p to 0
n = n - p;
p = 0;
lastCharBufferStart = lastChar;
}
}
@Override
public int index() {
return currentCharIndex;
}
View on GitHub (pinned to 7d5770395b)
Solutions
- Use a Deque<Integer> of marks and always release from the top (LIFO)
- Wrap mark/release in try/finally so early exits cannot skip a release and break the ordering
- Give each consumer its own stream instance instead of sharing one UnbufferedCharStream
Example fix
// before int m1 = stream.mark(); int m2 = stream.mark(); stream.release(m1); // throws: expected m2 // after Deque<Integer> marks = new ArrayDeque<>(); marks.push(stream.mark()); marks.push(stream.mark()); stream.release(marks.pop()); // m2 stream.release(marks.pop()); // m1
Defensive patterns
Strategy: validation
Validate before calling
Deque<Integer> marks = new ArrayDeque<>();
marks.push(stream.mark());
// ... work ...
if (!marks.isEmpty() && marker == marks.peek()) {
stream.release(marks.pop()); // only the top marker is ever valid
} else {
throw new IllegalStateException("marker released out of order");
} Prevention
- Always release marks in reverse order of acquisition
- Pair every mark() with release() in try/finally
- One stream per consumer; never share an unbuffered stream across threads
When it happens
Trigger: release(m1) called before release(m2) when m2 was marked after m1; calling release twice on the same marker; storing marks in a collection and releasing them in insertion order instead of reverse.
Common situations: Nested components each marking/unmarking the stream with exception paths that skip a release; asynchronous or interleaved parsing attempts sharing one stream; refactoring that changed mark/unmark nesting.
Related errors
- cannot seek to negative index {index}
- seek to index outside buffer: {index} not in {bufferStartInd
- interval {interval} outside buffer: {bufferStartIndex}..{buf
- cannot consume EOF
- cannot consume EOF
AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14).
Data as JSON: /api/errors/1e303e39d0fd58d3.
Report an issue: GitHub.