I’m seeing a NullPointerException in the sonar-java plugin when scanning a non-final class that violates the synchronizing on getClass() in a non-final class rule (https://rules.sonarsource.com/java/tag/multi-threading/RSPEC-3067). The synchronized(getClass()) call is in an initializer. The reason appears to be the scanner is tracing up from the offensive code, it only considered that it would be in a constructor or other method, so it traced up the expression tree via parent() calls until it hit null.
- Versions
- Sonar-Java scanner 5.13.1.18282
- SonarQube server 7.9 (on RHEL).
- building with sonar-gradle-plugin 2.7.1 (probably not relevant)
- Error Observed
Caused by: java.lang.NullPointerException
at org.sonar.java.checks.synchronization.SynchronizationOnGetClassCheck.isEnclosingClassFinal(SynchronizationOnGetClassCheck.java:68)
at org.sonar.java.checks.synchronization.SynchronizationOnGetClassCheck.visitNode(SynchronizationOnGetClassCheck.java:59)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.lambda$visit$7(VisitorsBridge.java:295)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visit(VisitorsBridge.java:298)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visitChildren(VisitorsBridge.java:280)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visit(VisitorsBridge.java:302)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visitChildren(VisitorsBridge.java:280)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visit(VisitorsBridge.java:302)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visitChildren(VisitorsBridge.java:280)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.visit(VisitorsBridge.java:302)
at org.sonar.java.model.VisitorsBridge$ScannerRunner.run(VisitorsBridge.java:271)
at org.sonar.java.model.VisitorsBridge.visitFile(VisitorsBridge.java:141)
at org.sonar.java.ast.JavaAstScanner.simpleScan(JavaAstScanner.java:90)
... 117 more
- Steps to Reproduce
This was reproduced inside a rather huge gradle build project with many subprojects. But simplified, it appears only to be a basic static analysis for the following class.
package bugcode;
public class GetClassInNonFinalClassInInitializerKillsSonarJava {
private static boolean flag;
{
synchronized (getClass()) {
if (!flag) {
flag = true;
}
}
}
}
- Potential Workaround
Other than just skipping the offending class, I’ve been able to patch the problem in the sonar-scanner source code, rebuild, and replace the sonar-java plugin jar file to get the proper reporting for the rule violation. I’m not confident that my code is correct. Essentially, I modify org.sonar.java.checks.synchronization.SynchronizationOnGetClassCheck$isEnclosingClassFinal
On line 63, I add INITIALIZER to the while loop:
while (!parent.is(METHOD, CONSTRUCTOR, INITIALIZER)) {
But after the while loop, the parent isn’t going to be a MethodTree, so the early return fails in the cast. I could check if the class was final by modifying that early return to be:
if (parent instanceof MethodTree) {
return ((MethodTree) parent).symbol().owner().isFinal();
} else {
// hypothesis: the parent is type class for initializers
return ((ClassTree) parent.parent()).symbol().isFinal();
}