java:S6002 false negative: impossible lookahead ignored by Matcher.matches()

Product: SonarQube Community Build 26.5.0.122743
sonar-java version: sonar-java 8.29 (build 43460)
sonar-java SE version: sonar-java-symbolic-execution 8.16.4 (build 1912)
Java source level: 21 (javac 21, source/target 21)

Rule

java:S6002 — Regex lookahead assertions should not be contradictory

Description

S6002 detects regular-expression lookahead assertions that can never succeed under whole-string matching. The rule fires when the regex is passed to String.matches(...) (which anchors the match to the entire input) but does NOT fire when the same regex is compiled via Pattern.compile(...) and applied with matcher(s).matches(), even though Matcher.matches() is semantically identical to String.matches (it also requires the whole input to match). The impossible-lookahead diagnosis depends only on the regex content and the whole-string anchoring; it should not depend on which API the user chose.

Reproducer

public class S6002StringMatchesBefore {
    boolean demo(String s) {
        return s.matches("(?=.*foo-bar)\\w*");   // S6002 fires (impossible lookahead).
    }
}
import java.util.regex.Pattern;

public class S6002PatternMatcherAfter {
    boolean demo(String s) {
        // Same regex (with explicit `$` anchor for clarity), same whole-string match.
        return Pattern.compile("(?=.*foo-bar)\\w*$").matcher(s).matches();
    }
}

Expected behavior

String.matches(regex), Pattern.compile(regex).matcher(s).matches(), and Pattern.matches(regex, s) are all defined to attempt a match against the entire input (JDK Javadoc). S6002’s reasoning — that the lookahead (?=.*foo-bar) requires the rest of the string to contain -, while the body \w* only matches word characters — is independent of the API surface. S6002 should fire on both files.

Actual behavior

  • S6002StringMatchesBefore: java:S6002 (CRITICAL) raised at the regex literal with message
    “Remove or fix this lookahead assertion that can never be true.”
  • S6002PatternMatcherAfter: no S6002 violation, despite the regex containing the same impossible lookahead and being applied with matches() (whole-string match).

The only structural difference is the entry point used to perform a whole-string regex match. Codebases that pre-compile patterns for performance (the recommended practice, per SonarJava’s own S4248) are silently exempted from S6002’s impossible-lookahead detection.