False negative java:S3824: the Map.get null test is not reported when written `!(value != null)`

Product/version: SonarQube Community Build 26.9.0.129388 (sonar-java 8.41.0.47177); also 26.7.0.124771 (sonar-java 8.34.0.44906), sonar-scanner 8.1.0.6389

Language: Java

Rule: java:S3824

Issue:

S3824 wants a Map.get() null test followed by a put replaced with computeIfAbsent. seed/Cache.java tests value == null and is reported. variant/Cache.java tests !(value != null), which is the same test on the same value, and isn’t reported. !(!(value == null)) isn’t reported either.

MapComputeIfAbsentOrPresentCheck (symbolic-execution plugin 8.16.4.1912, shipped with this release) matches the null test by the syntax of the if condition, == or != against the null literal, and doesn’t look through !. The same spelling also gets past S2168’s double-checked locking check (linked below). PMD’s DoubleCheckedLocking had the same negated-guard gap and fixed it in 7.27.0 (pmd#6900).

Reproducer:

seed/Cache.java:

import java.util.Map;

public class Cache {
    String lookup(Map<String, String> map, String key) {
        String value = map.get(key);
        if (value == null) {                      // S3824 reported
            value = key.toUpperCase();
            map.put(key, value);
        }
        return value;
    }
}

variant/Cache.java:

import java.util.Map;

public class Cache {
    String lookup(Map<String, String> map, String key) {
        String value = map.get(key);
        if (!(value != null)) {                   // the same test; nothing reported
            value = key.toUpperCase();
            map.put(key, value);
        }
        return value;
    }
}

Expected behavior:

S3824 on the if in variant/Cache.java (line 6), as on the one in seed/Cache.java.

How to reproduce:

# one project per file
sonar-scanner -Dsonar.projectKey=<key> -Dsonar.sources=. \
    -Dsonar.java.binaries=. -Dsonar.scm.disabled=true

See also False negative java:S2168: not reported when the outer null check is written `!(helper != null)`.

Output of the commands above:

### SonarQube Community Build 26.9.0.129388 (sonar-java 8.41.0.47177), server reports 26.9.0.129388, default Sonar way profile  rule: S3824

--- SEED -- expected: reported (seed/)
    java:S1220             line -    Move this file to a named package.
    java:S3824             line 5    Replace this "Map.get()" and condition with a call to "Map.c  <== S3824
    [S3824: 1]

--- VARIANT -- expected: reported; actual: not reported (variant/)
    java:S1220             line -    Move this file to a named package.
    java:S1940             line 6    Use the opposite operator ("==") instead.
    [S3824: 0]

Hey @MarkLee131, this rule is in the same situation as the other one you reported here, and the response is the same that my colleague Marco gave you in that thread. We don’t support it anymore so we won’t fix it.