{"record":{"id":"30da8ad7c4f5dd2e","repo":"antlr/antlr4","slug":"this-set-is-readonly","errorCode":null,"errorMessage":"This set is readonly","messagePattern":"This set is readonly","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java","lineNumber":139,"sourceCode":"\tpublic boolean add(ATNConfig config) {\n\t\treturn add(config, null);\n\t}\n\n\t/**\n\t * Adding a new config means merging contexts with existing configs for\n\t * {@code (s, i, pi, _)}, where {@code s} is the\n\t * {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and\n\t * {@code pi} is the {@link ATNConfig#semanticContext}. We use\n\t * {@code (s,i,pi)} as key.\n\t *\n\t * <p>This method updates {@link #dipsIntoOuterContext} and\n\t * {@link #hasSemanticContext} when necessary.</p>\n\t */\n\tpublic boolean add(\n\t\tATNConfig config,\n\t\tDoubleKeyMap<PredictionContext,PredictionContext,PredictionContext> mergeCache)\n\t{\n\t\tif ( readonly ) throw new IllegalStateException(\"This set is readonly\");\n\t\tif ( config.semanticContext != SemanticContext.Empty.Instance ) {\n\t\t\thasSemanticContext = true;\n\t\t}\n\t\tif (config.getOuterContextDepth() > 0) {\n\t\t\tdipsIntoOuterContext = true;\n\t\t}\n\t\tATNConfig existing = configLookup.getOrAdd(config);\n\t\tif ( existing==config ) { // we added this new one\n\t\t\tcachedHashCode = -1;\n\t\t\tconfigs.add(config);  // track order here\n\t\t\treturn true;\n\t\t}\n\t\t// a previous (s,i,pi,_), merge with it and save result\n\t\tboolean rootIsWildcard = !fullCtx;\n\t\tPredictionContext merged =\n\t\t\tPredictionContext.merge(existing.context, config.context, rootIsWildcard, mergeCache);\n\t\t// no need to check for existing.context, config.context in cache\n\t\t// since only way to create new graphs is \"call rule\" and here. We","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/antlr/antlr4/blob/7d5770395bb7b02eb56e7c62662cb1d7c08f42a3/runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java#L121-L157","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom ATN simulators, debug tools that inspect and then modify DFA states, and code that copies references rather than creating a fresh ATNConfigSet.","solutions":["Check !configs.isReadonly() before mutating.","Create a new ATNConfigSet or use new ATNConfigSet(existingConfigSet) for modifications.","Never mutate configuration sets reachable from DFAState objects.","Build and optimize a set before it is installed in a DFA cache."],"exampleFix":"// before\nDFAState dfaState = ...;\ndfaState.configs.add(newConfig); // readonly after DFA caching\n\n// after\nATNConfigSet mutable = new ATNConfigSet(dfaState.configs);\nmutable.add(newConfig);","handlingStrategy":"validation","validationCode":"if (configs.isReadonly()) {\n    configs = new ATNConfigSet(configs);\n}\nconfigs.add(newConfig);","typeGuard":null,"tryCatchPattern":"try {\n    configs.add(config);\n} catch (IllegalStateException e) {\n    if (configs.isReadonly()) {\n        configs = new ATNConfigSet(configs);\n        configs.add(config);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Treat DFAState.configs as immutable.","Copy before modifying.","Keep scratch sets separate from cached DFA sets."],"tags":["antlr","java","atn","atn-config-set","readonly","dfa"],"backgroundTag":null,"analyzedSha":"7d5770395bb7b02eb56e7c62662cb1d7c08f42a3","analyzedAt":"2026-08-14T14:47:56.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}