{"record":{"id":"ed0e8b428327293b","repo":"languagetool-org/languagetool","slug":"rulefilter-index-out-of-bounds-in-match-getrule","errorCode":null,"errorMessage":"RuleFilter: Index out of bounds in ${match.getRule().getFullId()}, value: ${fromStr}","messagePattern":"RuleFilter: Index out of bounds in (.+?), value: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/rules/patterns/RuleFilter.java","lineNumber":103,"sourceCode":"  }\n\n  protected int getPosition(String fromStr, AnalyzedTokenReadings[] patternTokens, RuleMatch match) {\n    int i;\n    if (fromStr.startsWith(\"marker\")) {\n      i = 0;\n      while (i < patternTokens.length && patternTokens[i].getStartPos() < match.getFromPos()\n        || patternTokens[i].isSentenceStart()) {\n        i++;\n      }\n      i++;\n      if (fromStr.length()>6) {\n        i += Integer.parseInt(fromStr.replace(\"marker\", \"\"));\n      }\n    } else {\n      i = Integer.parseInt(fromStr);\n    }\n    if (i < 1 || i > patternTokens.length) {\n      throw new IllegalArgumentException(\"RuleFilter: Index out of bounds in \"\n        + match.getRule().getFullId() + \", value: \" + fromStr);\n    }\n    return i - 1;\n  }\n\n  protected boolean isMatchAtSentenceStart(AnalyzedTokenReadings[] tokens, RuleMatch match) {\n    int i = 0;\n    while (i < tokens.length && tokens[i].getStartPos() < match.getFromPos()) {\n      i++;\n    }\n    while (i > 0 && StringTools.isPunctuationMark(tokens[i].getToken())) {\n      i--;\n    }\n    return i == 0;\n  }\n\n  // when there's a 'skip', we need to adapt the reference number\n  protected int getSkipCorrectedReference(List<Integer> tokenPositions, int refNumber) {","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/rules/patterns/RuleFilter.java#L85-L121","documentation":"RuleFilter.getPosition() converts a position value like '1', '2marker' or 'rel:n' from the rule XML into a zero-based token index for the matched pattern. It throws IllegalArgumentException when the computed index is less than 1 or greater than the number of pattern tokens, i.e. the referenced position does not exist in the rule pattern.","triggerScenarios":"An XML attribute such as tokenFilter pos='5' (or fromStr from marker/rel syntax) points beyond the number of tokens in the <pattern> of the rule, or evaluates to 0/negative after the 'marker'/rel offset arithmetic.","commonSituations":"Editing a rule and deleting or adding tokens to the pattern without updating the filter position; off-by-one confusion between the 1-based XML numbering and the returned 0-based index; using marker-based offsets in a rule without a <marker> element.","solutions":["Count the tokens in the rule's <pattern> and set the position attribute to a value between 1 and that count","If you recently changed the pattern, recompute all position attributes in the filter/tokenFilter elements","Check marker/rel offset arithmetic in the filter source; ensure the referenced marker token exists","Remember getPosition returns i-1; verify you are not double-adjusting an already 0-based value"],"exampleFix":"// before (XML)\n<pattern>\n  <token regexp=\"yes\">be</token>\n</pattern>\n<filter class=\"MyFilter\" args=\"position:3\"/>\n// after (XML)\n<pattern>\n  <token regexp=\"yes\">be</token>\n</pattern>\n<filter class=\"MyFilter\" args=\"position:1\"/>","handlingStrategy":"validation","validationCode":"int patternTokenCount = countPatternTokens(ruleXmlPattern);\nint pos = Integer.parseInt(args.get(\"position\").replace(\"marker\", \"\"));\nif (pos < 1 || pos > patternTokenCount) throw new IllegalStateException(\"position \" + pos + \" outside pattern of size \" + patternTokenCount);","typeGuard":"boolean isValidPosition(String s, int n) { try { int i = Integer.parseInt(s); return i >= 1 && i <= n; } catch (NumberFormatException e) { return false; } }","tryCatchPattern":"try {\n  int idx = getPosition(match, patternTokens, fromStr);\n} catch (IllegalArgumentException e) {\n  LOG.error(\"Filter position invalid for {}: {}\", match.getRule().getFullId(), e.getMessage());\n  return match; // or rethrow in strict mode\n}","preventionTips":["After any change to a rule's <pattern>, re-check every position attribute in filters","Remember positions in XML are 1-based; getPosition returns a 0-based index","Unit-test each pattern rule with its filter so out-of-range positions are caught early","Avoid marker-based offsets unless the <marker> element is present"],"tags":["java","languagetool","rule-filter","index-out-of-bounds"],"backgroundTag":"index-out-of-bounds","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}