False negative java:S2259: nothing analysed after a try-finally that ends with another try-finally

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

Language: Java

Rule: java:S2259

Issue:

S2259 stops reporting after a try/finally whose try block ends with another try/finally. Both files below set s0 = null and then call s0.length(), and both throw an NPE when run. The only difference is where sink++; sits in the outer try block: after the inner try (seed, S2259 is reported) or before it (variant, nothing is reported).

In the variant, nothing after the outer try gets explored. CFG.buildTryStatement builds the finally block first and uses it as the exit block while it builds the try body, so the inner finally gets the outer finally as its exit successor. When the inner try is the last statement of the outer try block, that’s also its only normal successor. ExplodedGraphWalker.handleBlockExit (sonar-java-symbolic-execution 8.16.4.1912, line 503) only enqueues a successor of a finally block if isDirectFlowSuccessorOf holds (line 521), i.e. the successor isn’t the exit block, or it’s the method exit. Neither is true here, so no state leaves the inner finally and the rest of the method is skipped. Those lines are the same on master.

It doesn’t happen if the outer try has only catch clauses, if the inner try/finally ends a loop or if body, or if the inner finally is empty.

It isn’t specific to S2259 either: S3518 on 10 / z with z = 0 after the same statement is also silent, and comes back once sink++; follows the inner try.

Reproducer:

seed/Tail.java:

public class Tail {
    static int sink = 0;
    public static void main(String[] args) {
        String s0 = "s0";
        try {
            try {
            } finally {
                sink++;
            }
            sink++;                      // a statement follows the inner try statement
        } finally {
            sink++;
        }
        s0 = null;
        sink += s0.length();             // NullPointerException at run time; S2259 reported
        System.out.println(sink);
    }
}

variant/Tail.java:

public class Tail {
    static int sink = 0;
    public static void main(String[] args) {
        String s0 = "s0";
        try {
            sink++;                      // the same statement, before the inner try statement
            try {
            } finally {
                sink++;
            }
        } finally {
            sink++;
        }
        s0 = null;
        sink += s0.length();             // NullPointerException at run time; nothing reported
        System.out.println(sink);
    }
}

Expected behavior:

S2259 at sink += s0.length(); (line 15) in variant/Tail.java, as in seed/Tail.java.

How to reproduce:

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

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: S2259

--- SEED -- expected: reported (seed/)
    java:S1220             line -    Move this file to a named package.
    java:S1854             line 4    Remove this useless assignment to local variable "s0".
    java:S108              line 6    Remove this block of code, fill it in, or add a comment expl
    java:S2259             line 15   A "NullPointerException" could be thrown; "s0" is nullable h  <== S2259
    java:S106              line 16   Replace this use of System.out by a logger.
    [S2259: 1]

--- VARIANT -- expected: reported; actual: not reported (variant/)
    java:S1220             line -    Move this file to a named package.
    java:S1854             line 4    Remove this useless assignment to local variable "s0".
    java:S108              line 7    Remove this block of code, fill it in, or add a comment expl
    java:S106              line 16   Replace this use of System.out by a logger.
    [S2259: 0]

Hello @MarkLee131

The issue you reported affects our old advanced analysis engine which is no longer maintained by Sonar, see: Changes in community build regarding Rust, Ruby, Scala and advanced bugs in Java, Python .

I’ve tested your reproducer on our new engine and the issue is raised correctly. You can find this new engine on SonarQube Cloud’s free plan or SonarQube Server enterprise.