{"record":{"id":"063d7fb9c42915e1","repo":"oracle/graal","slug":"too-much-additional-capture-group-tracking-overhea","errorCode":null,"errorMessage":"Too much additional capture group tracking overhead","messagePattern":"Too much additional capture group tracking overhead","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/TRegexCompiler.java","lineNumber":97,"sourceCode":"        } catch (UnsupportedRegexException bailout) {\n            logCompilationTime(source, timer, null);\n            Loggers.LOG_BAILOUT_MESSAGES.fine(() -> bailout.getReason() + \": \" + source);\n            throw bailout;\n        }\n    }\n\n    @TruffleBoundary\n    private static RegexObject doCompile(RegexLanguage language, RegexSource source) throws RegexSyntaxException {\n        TRegexCompilationRequest compReq = new TRegexCompilationRequest(language, source);\n        RegexExecNode execNode = compReq.compile();\n        return new RegexObject(language, source, execNode, compReq.getAst().getFlavorSpecificFlags(), compReq.getAst().getNumberOfCaptureGroups(), compReq.getNamedCaptureGroups());\n    }\n\n    @TruffleBoundary\n    public static TRegexDFAExecutorNode compileEagerDFAExecutor(RegexLanguage language, RegexSource source) {\n        TRegexDFAExecutorNode executor = new TRegexCompilationRequest(language, source).compileEagerDFAExecutor();\n        if (executor.getCGTrackingCost() > TRegexOptions.TRegexMaxEagerCGDFACost) {\n            throw new UnsupportedRegexException(\"Too much additional capture group tracking overhead\");\n        }\n        return executor;\n    }\n\n    @TruffleBoundary\n    public static LazyCaptureGroupRegexSearchNode compileLazyDFAExecutor(RegexLanguage language, RegexSource source, NFA nfa, RegexProfile rootNodeProfile, boolean allowSimpleCG) {\n        assert nfa == null || nfa.getAst().getSource() == source;\n        if (nfa == null) {\n            return new TRegexCompilationRequest(language, source).compileLazyDFAExecutorFromSource(rootNodeProfile, allowSimpleCG);\n        } else {\n            return new TRegexCompilationRequest(language, nfa).compileLazyDFAExecutor(rootNodeProfile, allowSimpleCG);\n        }\n    }\n\n    @TruffleBoundary\n    public static TRegexBacktrackerSubExecutorNode compileBacktrackingExecutor(RegexLanguage language, NFA nfa) {\n        return new TRegexCompilationRequest(language, nfa).compileBacktrackingExecutor();\n    }","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/TRegexCompiler.java#L79-L115","documentation":"Thrown by TRegexCompiler.compileEagerDFAExecutor when the eager (pre-compiled) DFA's capture-group tracking cost exceeds TRegexMaxEagerCGDFACost (default 3000). Capture groups force the DFA to carry per-group position tracking through every transition; if that machinery would dominate the compiled executor, compilation is aborted with UnsupportedRegexException instead of producing a bloated matcher.","triggerScenarios":"Calling compileEagerDFAExecutor (eager DFA compilation, e.g. for a RegexExecNode with upfront-compiled automaton) on a pattern with many capture groups, nested quantified groups, or group-heavy alternations such that executor.getCGTrackingCost() > 3000.","commonSituations":"Embeddings that pin compilation to the eager DFA strategy for predictable first-match latency; patterns auto-generated by tools that wrap every alternation branch in (...) instead of (?:...); large log-matching patterns with dozens of labeled groups.","solutions":["Convert capture groups that are not inspected later into non-capturing groups '(?:...)' — often the single biggest cost reduction","Split the pattern into several simpler regexes and combine results in host code instead of one group-heavy pattern","Use the lazy DFA executor (compileLazyDFAExecutor) or the default compilation path, which compiles states on demand and does not pay eager CG cost","Raise TRegexMaxEagerCGDFACost in TRegexOptions if the cost is acceptable for your workload (rebuild-time decision)"],"exampleFix":"// before\nString p = \"(a+)(b+)(c+)(d+)(e+)(f+)(g+)(h+)\"; // 8 tracked groups\n\n// after\nString p = \"(?:a+)(?:b+)(?:c+)(?:d+)(?:e+)(?:f+)(?:g+)(h+)\"; // track only what you use","handlingStrategy":"fallback","validationCode":"int captureGroupCount(String pattern) {\n    int n = 0, depth = 0;\n    for (int i = 0; i < pattern.length(); i++) {\n        char c = pattern.charAt(i);\n        if (c == '\\\\') { i++; continue; }\n        if (c == '(' && (i + 1 >= pattern.length() || pattern.charAt(i + 1) != '?')) n++;\n    }\n    return n; // heuristic: eager CG DFA cost grows with group count and quantifier nesting\n}","typeGuard":null,"tryCatchPattern":"try {\n    return TRegexCompiler.compileEagerDFAExecutor(language, source);\n} catch (UnsupportedRegexException e) {\n    // capture-tracking cost too high: use the lazy DFA / default compile path instead\n    return TRegexCompiler.compile(language, source); // falls back internally\n}","preventionTips":["Default to the standard compile path; use eager DFA only when profiling justifies it","Prefer non-capturing groups; keep capture groups only for positions you actually read"],"tags":["regex","tregex","dfa","capture-groups","compilation-cost"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}