{"record":{"id":"5be908e022353113","repo":"antlr/antlr4","slug":"invalid-interval","errorCode":null,"errorMessage":"invalid interval","messagePattern":"invalid interval","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"runtime/Java/src/org/antlr/v4/runtime/UnbufferedCharStream.java","lineNumber":325,"sourceCode":"\n    @Override\n    public int size() {\n        throw new UnsupportedOperationException(\"Unbuffered stream cannot know its size\");\n    }\n\n    @Override\n    public String getSourceName() {\n\t\tif (name == null || name.isEmpty()) {\n\t\t\treturn UNKNOWN_SOURCE_NAME;\n\t\t}\n\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String getText(Interval interval) {\n\t\tif (interval.a < 0 || interval.b < interval.a - 1) {\n\t\t\tthrow new IllegalArgumentException(\"invalid interval\");\n\t\t}\n\n\t\tint bufferStartIndex = getBufferStartIndex();\n\t\tif (n > 0 && data[n - 1] == Character.MAX_VALUE) {\n\t\t\tif (interval.a + interval.length() > bufferStartIndex + n) {\n\t\t\t\tthrow new IllegalArgumentException(\"the interval extends past the end of the stream\");\n\t\t\t}\n\t\t}\n\n\t\tif (interval.a < bufferStartIndex || interval.b >= bufferStartIndex + n) {\n\t\t\tthrow new UnsupportedOperationException(\"interval \"+interval+\" outside buffer: \"+\n\t\t\t                    bufferStartIndex+\"..\"+(bufferStartIndex+n-1));\n\t\t}\n\t\t// convert from absolute to local index\n\t\tint i = interval.a - bufferStartIndex;\n\t\treturn new String(data, i, interval.length());\n\t}\n","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/antlr/antlr4/blob/7d5770395bb7b02eb56e7c62662cb1d7c08f42a3/runtime/Java/src/org/antlr/v4/runtime/UnbufferedCharStream.java#L307-L343","documentation":"UnbufferedCharStream.getText(Interval) first validates the interval itself: interval.a must be >= 0 and interval.b must be at least a-1 (an empty interval starting at a is legal). Any negative start or end more than one before the start is rejected as 'invalid interval' with IllegalArgumentException.","triggerScenarios":"getText(Interval.of(-1, -1)) or Interval.of(5, 2); intervals built from sentinel tokens whose getStartIndex()/getStopIndex() are -1 (EOF tokens); computing b as startIndex + length - 1 with a length that underflows.","commonSituations":"Extracting text for a rule context that matched only EOF; error listeners building intervals from invalid tokens; off-by-one arithmetic when constructing intervals from token positions.","solutions":["Normalize the interval before calling: a >= 0 and b = Math.max(b, a - 1)","Skip text extraction for contexts whose start/stop tokens are EOF or have negative indexes","Build intervals from real matched tokens (ctx.start, ctx.stop) and null-check them"],"exampleFix":"// before\nString t = stream.getText(Interval.of(ctx.start.getStartIndex(), ctx.stop.getStartIndex())); // EOF: -1,-1\n\n// after\nif (ctx.start == null || ctx.stop == null\n        || ctx.start.getStartIndex() < 0 || ctx.stop.getStartIndex() < ctx.start.getStartIndex() - 1) {\n    return \"\";\n}\nString t = stream.getText(Interval.of(ctx.start.getStartIndex(), ctx.stop.getStartIndex()));","handlingStrategy":"validation","validationCode":"static Interval normalize(int a, int b) {\n    if (a < 0) return null; // invalid\n    return Interval.of(a, Math.max(b, a - 1));\n}\nInterval iv = normalize(start, stop);\nif (iv != null) text = stream.getText(iv);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reject EOF/sentinel tokens (negative indexes) before extracting text","Centralize interval construction in one validated helper","Treat empty intervals as empty text, not errors"],"tags":["antlr","char-stream","interval","validation","gettext"],"backgroundTag":null,"analyzedSha":"7d5770395bb7b02eb56e7c62662cb1d7c08f42a3","analyzedAt":"2026-08-14T14:47:56.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}