Skip to content

Folded literal factored with its case-sensitive twin: (?i:Z)x|Z matches "z" #211

Description

@jemmix

A case-insensitive literal in one alternation arm leaks its fold into the equivalent case-sensitive arm, which is enough for the latter to match lowercase input:

Pattern.compile("(?i:Z)x|Z").matcher("z").find()      // true — no arm should match "z"
                                                       // arm 1 needs an x; arm 2 is case-sensitive Z
Pattern.compile("(?i:[Z])x|[Z]").matcher("z").find()  // true — same via CHAR_CLASS
Pattern.compile("Zx|Z").matcher("z").find()           // false — correct

java.util.regex returns false for the first two.

Cause: alternation factoring (Parser.factor, round 1) merges arms by Regexp.equals(), which for LITERAL/CHAR_CLASS compares only the runes — not FoldCase. A folded literal therefore factors with its case-sensitive twin and the factored prefix carries the fold into arms that never asked for it (in the repro the case-sensitive arm is effectively deleted). Go's regexp/syntax compares Flags&FoldCase in its equality; the port dropped it.

Fix: compare FoldCase in Regexp.equals()/hashCode() for LITERAL/CHAR_CLASS (hash must change too, since factor groups via hashCode first).

Disclosure: found while fuzzing another library (https://github.com/jemmix/tdfa-jvm) against re2j; analysis and patch composed agentically with GLM 5.3 assistance.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions