antlr/antlr4 · error · InvalidOperationException

release() called with an invalid marker.

Error message

release() called with an invalid marker.

What it means

UnbufferedCharStream markers form a stack: Mark() returns -numMarkers after incrementing it, and Release(marker) accepts only the currently innermost marker. Any other value, duplicate release, or foreign marker is rejected. This preserves the exact buffer window that still-active markers require.

Source

Thrown at runtime/CSharp/src/UnbufferedCharStream.cs:334

        {
            if (numMarkers == 0)
            {
                lastCharBufferStart = lastChar;
            }
            int mark = -numMarkers - 1;
            numMarkers++;
            return mark;
        }

        /// <summary>Decrement number of markers, resetting buffer if we hit 0.</summary>
        /// <remarks>Decrement number of markers, resetting buffer if we hit 0.</remarks>
        /// <param name="marker"/>
        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 && 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.Array.Copy(data, p, data, 0, n - p);
                // shift n-p char from p to 0
                n = n - p;
                p = 0;
                lastCharBufferStart = lastChar;
            }
        }

        public virtual int Index
        {
            get

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Store the value returned by Mark() and pass exactly that value to Release().
  2. Release markers in reverse acquisition order.
  3. Wrap marked regions in try/finally so every marker is released exactly once.
  4. Do not share or reuse markers across streams.

Example fix

// before
int m = stream.Mark();
stream.Release(m);
stream.Release(m);

// after
int m = stream.Mark();
try { /* work */ }
finally { stream.Release(m); }
Defensive patterns

Strategy: validation

Validate before calling

var markerStack = new Stack<int>();
markerStack.Push(stream.Mark());
// before releasing:
if (markerStack.Peek() == marker) { stream.Release(markerStack.Pop()); }

Try / catch

try { stream.Release(marker); }
catch (InvalidOperationException ex) when (ex.Message.Contains("invalid marker")) { /* release remaining markers in LIFO order */ }

Prevention

When it happens

Trigger: Calling Release() twice with the same marker; releasing markers out of LIFO order; passing a marker from a different stream; or passing 0/1 instead of the value returned by Mark().

Common situations: Custom parser harnesses that cache markers; exception paths that skip a release and later release the wrong marker; or code ported from an API where markers are opaque IDs that can be released in any order.

Related errors


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