antlr/antlr4 · error · InvalidOperationException
release() called with an invalid marker.
Error message
release() called with an invalid marker.
What it means
UnbufferedTokenStream implements Mark()/Release() as a strictly LIFO stack: Mark() returns -numMarkers-1 (so marks are -1, -2, ...) and Release(marker) requires marker == -numMarkers. The thrown InvalidOperationException means the marker passed to Release() is not the most recently acquired, still-open mark. The stream cannot release out of order because each mark pins a buffer window.
Source
Thrown at runtime/CSharp/src/UnbufferedTokenStream.cs:298
/// is called in the wrong order.</p>
/// </remarks>
public virtual int Mark()
{
if (numMarkers == 0)
{
lastTokenBufferStart = lastToken;
}
int mark = -numMarkers - 1;
numMarkers++;
return mark;
}
public virtual void Release(int marker)
{
int expectedMark = -numMarkers;
if (marker != expectedMark)
{
throw new InvalidOperationException("release() called with an invalid marker.");
}
numMarkers--;
if (numMarkers == 0)
{
// can we release buffer?
if (p > 0)
{
// Copy tokens[p]..tokens[n-1] to tokens[0]..tokens[(n-1)-p], reset ptrs
// p is last valid token; move nothing if p==n as we have no valid char
System.Array.Copy(tokens, p, tokens, 0, n - p);
// shift n-p tokens from p to 0
n = n - p;
p = 0;
}
lastTokenBufferStart = lastToken;
}
}
View on GitHub (pinned to 7d5770395b)
Solutions
- Release markers in strict reverse order of acquisition (last Mark() released first) and never release the same marker twice
- Wrap usage in try/finally so the matching Release() always runs even when parsing throws
- Check the invariant before calling: marker must equal the value returned by the most recent, unreleased Mark() (i.e. -(numMarkers))
- If you only need a single checkpoint, keep exactly one mark at a time: Mark() once, Release() once, repeat
Example fix
// before
int m = stream.Mark();
Parse();
stream.Release(m);
stream.Release(m); // second release throws
// after
int m = stream.Mark();
try {
Parse();
}
finally {
stream.Release(m); // exactly once, in LIFO order
} Defensive patterns
Strategy: validation
Validate before calling
// C#: release only the top-of-stack mark; marks are -1, -2, ... in LIFO order
// track open marks yourself when nesting
var openMarks = new Stack<int>();
int m = stream.Mark();
openMarks.Push(m);
if (openMarks.Peek() == m) { stream.Release(openMarks.Pop()); } // valid only when m == last Mark() unreleased Try / catch
try { int m = stream.Mark(); try { Parse(stream); } finally { stream.Release(m); } } catch (InvalidOperationException ex) when (ex.Message.Contains("invalid marker")) { // mark stack corrupted earlier: reset the stream and restart parse from a fresh UnbufferedTokenStream } Prevention
- Always pair Mark/Release with try/finally so no release is skipped or duplicated
- Never assume marker values: use only what Mark() returned, and release in reverse acquisition order
- Keep exactly one open mark at a time unless your nesting logic is explicitly LIFO-tested
When it happens
Trigger: Calling Release(m) twice for the same m; calling Release(0) (marks are negative, never 0); releasing marks in a non-LIFO order (release mark -2 while -1 is still open); releasing a marker obtained from a different token stream or after an exception path already released it; calling Release() when numMarkers==0.
Common situations: Manually managing mark/release around backtracking without try/finally, so an early exception or return skips one release and shifts the stack; mixing markers between a CommonTokenStream and an UnbufferedTokenStream; porting code that assumed Java's index-style markers (0,1,2...) instead of ANTLR 4's negative stack markers.
Related errors
- release() called with an invalid marker.
- tokenSource cannot be null
- cannot consume EOF
- token index {i} out of range 0..{tokens.Count - 1}
- start {start} or stop {stop} not in 0..{tokens.Count - 1}
AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14).
Data as JSON: /api/errors/d5a30629f7f85bd8.
Report an issue: GitHub.