Package: groovy.util.regex

## [Java] Class RegexGuard

- groovy.util.regex.RegexGuard

-     @Incubating
      public final class RegexGuard
      extends Object

  Deadline-guarded regular expression evaluation, protecting against Regular Expression Denial of Service (ReDoS) when patterns or inputs come from untrusted sources. Java's regex engine has no native timeout, so catastrophic backtracking can hang a thread indefinitely.

  The guard works by wrapping the input `CharSequence` so that `charAt` periodically checks a deadline and throws RegexTimeoutException once it has passed. Backtracking repeatedly re-reads input characters, so a runaway match is cut off shortly after the deadline without needing watchdog threads or thread interruption.

       import groovy.util.regex.RegexGuard
       import groovy.util.regex.RegexTimeoutException
       import java.time.Duration

       assert RegexGuard.matches(/gro+vy/, 'groovy', 200)
       def m = RegexGuard.matcher(/(\d+)/, 'abc 123', Duration.ofMillis(200))
       assert m.find() && m.group(1) == '123'

       try {
           RegexGuard.matches(/(.*,){16}X/, '1,' * 40, 200) // catastrophic backtracking
           assert false, 'should have timed out'
       } catch (RegexTimeoutException expected) {
       }

  Notes and limitations:
  - The deadline starts when the guarded matcher is created and covers all subsequent use of it, e.g. repeated `find()` calls.
  - The clock is only consulted while the engine reads input characters, every 512 reads; evaluations finishing in fewer reads never pay for a clock call. Pathological patterns perform millions of reads per second, so overshoot past the deadline is negligible in practice.
  - `Pattern.compile` itself is not guarded; pattern compilation is not subject to backtracking.
  See Also:
    SafeRegex
  Since:
    6.0.0

- - ### Methods Summary

    Methods
    Type Params | Return Type | Name and description
    --- | --- | ---
    `` | `public static CharSequence` | `ambientGuard(CharSequence input)` Runtime hook used by Groovy's regex operators and extension methods: wraps the input so that reads are guarded when an ambient guard (see guard(long, Closure)) is active on the current thread, and returns the input unchanged otherwise.
    `` | `public static Matcher` | `findRegex(Object input, Object pattern, long millis)` Runtime hook used by the SafeRegex transform: equivalent to matcher(Object, Object, long) but with the operands in the `=~` operator's order, so that a rewritten expression still evaluates its operands left to right, exactly as the original did.
    `` | `public static CharSequence` | `guard(CharSequence input, long millis)` Wraps a character sequence so that reads beyond the deadline throw RegexTimeoutException.
    `` | `public static CharSequence` | `guard(CharSequence input, Duration timeout)` Variant of guard(CharSequence, long) taking the timeout as a Duration.
    `<T>` | `public static T` | `guard(long millis, Closure<T> closure)` Runs the closure with an ambient regex timeout active on the current thread and returns the closure's result.
    `<T>` | `public static T` | `guard(Duration timeout, Closure<T> closure)` Variant of guard(long, Closure) taking the timeout as a Duration.
    `` | `public static boolean` | `matchRegex(Object input, Object pattern, long millis)` Runtime hook used by the SafeRegex transform: equivalent to matches(Object, Object, long) but with the operands in the `==~` operator's order, so that a rewritten expression still evaluates its operands left to right, exactly as the original did.
    `` | `public static Matcher` | `matcher(Object pattern, Object input, long millis)` Creates a matcher of the pattern over deadline-guarded input, like the `=~` operator.
    `` | `public static Matcher` | `matcher(Object pattern, Object input, Duration timeout)` Variant of matcher(Object, Object, long) taking the timeout as a Duration.
    `` | `public static boolean` | `matches(Object pattern, Object input, long millis)` Matches the whole input against the pattern like the `==~` operator, giving up once the timeout has elapsed.
    `` | `public static boolean` | `matches(Object pattern, Object input, Duration timeout)` Variant of matches(Object, Object, long) taking the timeout as a Duration.
    `` | `public static CharSequence` | `prepare(CharSequence input)` Runtime hook used by Groovy's regex extension methods: normalizes an input for regex evaluation.

  - ### Inherited Methods Summary

    Inherited Methods
    Methods inherited from class | Name
    --- | ---
    `class Object` | `equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- - ### Method Detail

    - #### public static CharSequence ambientGuard(CharSequence input)

      Runtime hook used by Groovy's regex operators and extension methods: wraps the input so that reads are guarded when an ambient guard (see guard(long, Closure)) is active on the current thread, and returns the input unchanged otherwise.

      Parameters:
        `input` - the text a regex is about to be evaluated against
      Returns:
        the input, guarded if an ambient guard is active

    - #### public static Matcher findRegex(Object input, Object pattern, long millis)

      Runtime hook used by the SafeRegex transform: equivalent to matcher(Object, Object, long) but with the operands in the `=~` operator's order, so that a rewritten expression still evaluates its operands left to right, exactly as the original did.

      Parameters:
        `input` - the text to match (the operator's left operand)
        `pattern` - the pattern (the operator's right operand)
        `millis` - the timeout in milliseconds (must be positive)
      Returns:
        a matcher over the guarded input

    - #### public static CharSequence guard(CharSequence input, long millis)

      Wraps a character sequence so that reads beyond the deadline throw RegexTimeoutException. The deadline is attached to the data: it is a single total budget covering every subsequent regex evaluation against the guarded sequence, wherever and on whatever thread it happens. Groovy's regex-evaluating `CharSequence` extension methods (`find`, `findAll`, `findGroups`, `eachMatch`, `replaceAll`, `switch/case` matching, ...) honour the guard, as does anything that passes the sequence through to `java.util.regex` untouched, e.g. `Pattern.split` or manual matcher creation. This complements guard(long, Closure), which instead guards a region of code with a per-evaluation timeout.

      Note the deadline is only consulted while the regex engine reads input characters, so a well-behaved evaluation over a short input may still succeed after the deadline; runaway evaluations are always cut off. A sequence other than a `String` (e.g. a `GString` or `StringBuilder`) is snapshotted via `toString()` first, so later mutations of the original are not seen by the guarded view.

      Parameters:
        `input` - the sequence to guard (must not be `null`)
        `millis` - the timeout in milliseconds (must be positive)
      Returns:
        a guarded view of the input

    - #### public static CharSequence guard(CharSequence input, Duration timeout)

      Variant of guard(CharSequence, long) taking the timeout as a Duration.

    - #### <T> public static T guard(long millis, Closure<T> closure)

      Runs the closure with an ambient regex timeout active on the current thread and returns the closure's result. While active, every regex evaluation reached through Groovy's regex operators (`==~`, `=~`, and `switch/case`, `grep` and `in` against a `Pattern`) or the `CharSequence` extension methods (`find`, `findAll`, `findGroups`, `findAllGroups`, `eachMatch`, `matches`, `replaceAll`, `replaceFirst`, `splitEachLine`, ...) is guarded: each individual evaluation that exceeds the timeout throws RegexTimeoutException.

           import groovy.util.regex.RegexGuard
           import groovy.util.regex.RegexTimeoutException

           def groups = RegexGuard.guard(200) {
               '6.0.0-beta-2'.findGroups(/(\d+)\.(\d+)\.(\d+)(?:-(.+))?/)
           }
           assert groups == ['6.0.0-beta-2', '6', '0', '0', 'beta-2']

           try {
               RegexGuard.guard(200) {
                   ('1,' * 40).findGroups(/(.*,){16}X/) // catastrophic backtracking
               }
               assert false, 'should have timed out'
           } catch (RegexTimeoutException expected) {
           }

      Semantics:
      - The timeout applies per evaluation, not to the block as a whole; any number of well-behaved evaluations may run within the scope.
      - The scope is dynamic: regex operations in methods called from the closure are guarded too, as long as they run on the same thread. Threads spawned within the closure are not covered; on JDK 25+, however, the guard does propagate into `StructuredTaskScope` forks, which by construction run within the enclosing scope.
      - Nested guards only tighten: the smaller per-evaluation timeout wins.
      - An explicit timeout, e.g. from matches(Object, Object, long) or a SafeRegex scope, is likewise capped by the ambient timeout.
      - A matcher created within the scope (e.g. via `=~`) keeps its deadline, measured from creation, when used after the block exits.
      - Direct `java.util.regex` calls, e.g. a hand-written `pattern.matcher(input)`, bypass the ambient guard; combine them with guard(CharSequence, long) instead. The same applies to the JDK's own `String` methods: on a `String` receiver, `matches(String)`, `replaceAll(String, String)`, `replaceFirst(String, String)` and `split(String)` dispatch to `java.lang.String` and never enter Groovy's runtime. Their `Pattern`-argument variants, e.g. `str.replaceAll(~/expensive/, 'x')`, are extension methods and are covered.
      Parameters:
        `millis` - the per-evaluation timeout in milliseconds (must be positive)
        `closure` - the code to run under the ambient guard
      Returns:
        the closure's result

    - #### <T> public static T guard(Duration timeout, Closure<T> closure)

      Variant of guard(long, Closure) taking the timeout as a Duration.

    - #### public static boolean matchRegex(Object input, Object pattern, long millis)

      Runtime hook used by the SafeRegex transform: equivalent to matches(Object, Object, long) but with the operands in the `==~` operator's order, so that a rewritten expression still evaluates its operands left to right, exactly as the original did.

      throws:
        RegexTimeoutException if evaluation exceeds the timeout
      Parameters:
        `input` - the text to match (the operator's left operand)
        `pattern` - the pattern (the operator's right operand)
        `millis` - the timeout in milliseconds (must be positive)
      Returns:
        `true` if the whole input matches the pattern

    - #### public static Matcher matcher(Object pattern, Object input, long millis)

      Creates a matcher of the pattern over deadline-guarded input, like the `=~` operator. Matcher operations that read the input, e.g. `find()` or `matches()`, throw RegexTimeoutException once the deadline, measured from this call, has passed.

      Parameters:
        `pattern` - the pattern, a Pattern or an object whose string representation is the regex
        `input` - the text to match
        `millis` - the timeout in milliseconds (must be positive)
      Returns:
        a matcher over the guarded input

    - #### public static Matcher matcher(Object pattern, Object input, Duration timeout)

      Variant of matcher(Object, Object, long) taking the timeout as a Duration.

    - #### public static boolean matches(Object pattern, Object input, long millis)

      Matches the whole input against the pattern like the `==~` operator, giving up once the timeout has elapsed. Follows the operator's conventions: a `null` pattern or input yields `false`, non-`Pattern` patterns and non-`String` inputs are converted via their default string representation, and the matcher is stored for `Matcher.lastMatcher`.

      throws:
        RegexTimeoutException if evaluation exceeds the timeout
      Parameters:
        `pattern` - the pattern, a Pattern or an object whose string representation is the regex
        `input` - the text to match
        `millis` - the timeout in milliseconds (must be positive)
      Returns:
        `true` if the whole input matches the pattern

    - #### public static boolean matches(Object pattern, Object input, Duration timeout)

      Variant of matches(Object, Object, long) taking the timeout as a Duration.

    - #### public static CharSequence prepare(CharSequence input)

      Runtime hook used by Groovy's regex extension methods: normalizes an input for regex evaluation. A `String`, or a sequence guarded via guard(CharSequence, long), is passed through, so an attached deadline is honoured by the evaluation; any other sequence (e.g. a `GString` or `StringBuilder`) is snapshotted via `toString()`, as the extension methods have always done. An ambient guard (see guard(long, Closure)), if active, is then applied on top.

      Parameters:
        `input` - the text a regex is about to be evaluated against
      Returns:
        the normalized, possibly guarded, input