antlr/antlr4 · error · IllegalStateException

This set is readonly

Error message

This set is readonly

What it means

ATNConfigSet is sealed with readonly=true before it is stored as the configuration set of a DFA state; DFA caching assumes those configs never change. add()/addAll() are mutators and throw IllegalStateException on a sealed set.

Source

Thrown at runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java:139

	public boolean add(ATNConfig config) {
		return add(config, null);
	}

	/**
	 * Adding a new config means merging contexts with existing configs for
	 * {@code (s, i, pi, _)}, where {@code s} is the
	 * {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
	 * {@code pi} is the {@link ATNConfig#semanticContext}. We use
	 * {@code (s,i,pi)} as key.
	 *
	 * <p>This method updates {@link #dipsIntoOuterContext} and
	 * {@link #hasSemanticContext} when necessary.</p>
	 */
	public boolean add(
		ATNConfig config,
		DoubleKeyMap<PredictionContext,PredictionContext,PredictionContext> mergeCache)
	{
		if ( readonly ) throw new IllegalStateException("This set is readonly");
		if ( config.semanticContext != SemanticContext.Empty.Instance ) {
			hasSemanticContext = true;
		}
		if (config.getOuterContextDepth() > 0) {
			dipsIntoOuterContext = true;
		}
		ATNConfig existing = configLookup.getOrAdd(config);
		if ( existing==config ) { // we added this new one
			cachedHashCode = -1;
			configs.add(config);  // track order here
			return true;
		}
		// a previous (s,i,pi,_), merge with it and save result
		boolean rootIsWildcard = !fullCtx;
		PredictionContext merged =
			PredictionContext.merge(existing.context, config.context, rootIsWildcard, mergeCache);
		// no need to check for existing.context, config.context in cache
		// since only way to create new graphs is "call rule" and here. We

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Check !configs.isReadonly() before mutating.
  2. Create a new ATNConfigSet or use new ATNConfigSet(existingConfigSet) for modifications.
  3. Never mutate configuration sets reachable from DFAState objects.
  4. Build and optimize a set before it is installed in a DFA cache.

Example fix

// before
DFAState dfaState = ...;
dfaState.configs.add(newConfig); // readonly after DFA caching

// after
ATNConfigSet mutable = new ATNConfigSet(dfaState.configs);
mutable.add(newConfig);
Defensive patterns

Strategy: validation

Validate before calling

if (configs.isReadonly()) {
    configs = new ATNConfigSet(configs);
}
configs.add(newConfig);

Try / catch

try {
    configs.add(config);
} catch (IllegalStateException e) {
    if (configs.isReadonly()) {
        configs = new ATNConfigSet(configs);
        configs.add(config);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling add or addAll on DFAState.configs obtained from ParserATNSimulator/LexerATNSimulator; reusing a set after it was passed to code that calls setReadonly(true); or retaining a set and later trying to append configurations.

Common situations: Custom ATN simulators, debug tools that inspect and then modify DFA states, and code that copies references rather than creating a fresh ATNConfigSet.

Related errors


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