Introduction to SonarJava
SonarJava is a static code analyzer for Java that detects bugs, vulnerabilities, code smells, and security issues. It's part of the SonarQube platform and provides extensive rules covering various aspects of code quality.
Key Features
- 500+ Rules: Comprehensive rule set for Java code analysis
- Security Vulnerability Detection: OWASP Top 10, CWE vulnerabilities
- Code Smell Detection: Maintainability issues and anti-patterns
- Bug Detection: Potential runtime errors and logical bugs
- Custom Rules: Extensible with custom rule development
- Integration: Works with SonarQube, SonarCloud, and CI/CD pipelines
Implementation Guide
Dependencies
Add to your pom.xml:
<properties>
<sonar.java.version>7.21.0.38172</sonar.java.version>
<sonar.plugin.api.version>9.18.0.805</sonar.plugin.api.version>
</properties>
<dependencies>
<!-- SonarJava API for custom rules -->
<dependency>
<groupId>org.sonarsource.java</groupId>
<artifactId>sonar-java</artifactId>
<version>${sonar.java.version}</version>
<scope>provided</scope>
</dependency>
<!-- Sonar Plugin API -->
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
<version>${sonar.plugin.api.version}</version>
<scope>provided</scope>
</dependency>
<!-- For AST parsing -->
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.37.0</version>
</dependency>
<!-- For testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- SonarQube Scanner -->
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.10.0.2594</version>
</plugin>
<!-- Custom rule packaging -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
</plugin>
</plugins>
</build>
SonarQube Configuration
sonar-project.properties
# Project identification sonar.projectKey=my-java-project sonar.projectName=My Java Project sonar.projectVersion=1.0 # Source code configuration sonar.sources=src/main/java sonar.tests=src/test/java sonar.java.binaries=target/classes sonar.java.libraries=target/dependency/*.jar # Quality Gate sonar.qualitygate.wait=true # Java-specific settings sonar.java.source=17 sonar.java.target=17 sonar.java.coveragePlugin=jacoco # Exclusions sonar.exclusions=**/generated/**,**/test/** sonar.coverage.exclusions=**/test/**,**/model/**,**/dto/** # Test coverage sonar.junit.reportPaths=target/surefire-reports sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml # Custom rules sonar.java.customRules=com.example.sonar.rules.MyCustomRules
Maven Configuration
<!-- pom.xml SonarQube configuration -->
<profiles>
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<sonar.host.url>http://localhost:9000</sonar.host.url>
<sonar.login>${env.SONAR_TOKEN}</sonar.login>
<sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.junit.reportPaths>${project.build.directory}/surefire-reports</sonar.junit.reportPaths>
<sonar.java.binaries>${project.build.directory}/classes</sonar.java.binaries>
<sonar.java.libraries>${project.build.directory}/dependency/*.jar</sonar.java.libraries>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Custom Rule Development
Base Custom Rule Structure
package com.example.sonar.rules;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaCheck;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.Tree;
@Rule(
key = "CustomRuleTemplate",
name = "Custom Rule Template",
description = "Template for creating custom SonarJava rules",
tags = {"custom", "bug"},
priority = "MAJOR"
)
public class CustomRuleTemplate extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
protected void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
protected JavaFileScannerContext getContext() {
return context;
}
}
Security Vulnerability Rules
package com.example.sonar.rules.security;
import org.sonar.check.Rule;
import org.sonar.check.Priority;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.Tree;
import java.util.regex.Pattern;
@Rule(
key = "SQLInjectionVulnerability",
name = "SQL Injection Vulnerability",
description = "Detects potential SQL injection vulnerabilities",
tags = {"security", "sql", "owasp", "cwe-89"},
priority = "BLOCKER"
)
public class SQLInjectionRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
private static final Pattern SQL_METHODS = Pattern.compile(
"(?i).*executeQuery|executeUpdate|prepareStatement|createStatement.*"
);
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
String methodName = tree.symbol().name();
// Check if it's a SQL execution method
if (SQL_METHODS.matcher(methodName).matches()) {
// Check if arguments contain string concatenation
tree.arguments().forEach(arg -> {
if (containsStringConcatenation(arg)) {
reportIssue(tree,
"Potential SQL injection vulnerability. " +
"Use prepared statements instead of string concatenation.");
}
});
}
super.visitMethodInvocation(tree);
}
private boolean containsStringConcatenation(Tree tree) {
// This is a simplified check - real implementation would be more complex
return tree.toString().contains("+");
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
@Rule(
key = "HardCodedPassword",
name = "Hard-coded Password",
description = "Detects hard-coded passwords in source code",
tags = {"security", "owasp", "cwe-259"},
priority = "CRITICAL"
)
public class HardCodedPasswordRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
private static final Pattern PASSWORD_PATTERNS = Pattern.compile(
"(?i).*password|pwd|pass.*"
);
private static final Pattern PASSWORD_VALUES = Pattern.compile(
"(?i).*[a-zA-Z0-9]{8,}.*" // Simple pattern for password-like values
);
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitVariable(Tree tree) {
String variableName = tree.toString().toLowerCase();
if (PASSWORD_PATTERNS.matcher(variableName).matches()) {
// Check if variable is assigned a string literal that looks like a password
if (containsHardCodedPassword(tree)) {
reportIssue(tree,
"Hard-coded password detected. " +
"Store passwords in secure configuration instead.");
}
}
super.visitVariable(tree);
}
private boolean containsHardCodedPassword(Tree tree) {
String treeString = tree.toString();
return PASSWORD_VALUES.matcher(treeString).matches() &&
treeString.contains("\"");
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
Code Quality Rules
package com.example.sonar.rules.quality;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
import java.util.List;
@Rule(
key = "LongMethod",
name = "Long Method",
description = "Methods should not have too many lines of code",
tags = {"code-smell", "brain-overload"},
priority = "MAJOR"
)
public class LongMethodRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
private static final int MAX_METHOD_LINES = 20;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitMethod(MethodTree tree) {
int lineCount = countMethodLines(tree);
if (lineCount > MAX_METHOD_LINES) {
reportIssue(tree,
String.format("Method has %d lines which exceeds maximum of %d. " +
"Consider breaking it into smaller methods.",
lineCount, MAX_METHOD_LINES));
}
super.visitMethod(tree);
}
private int countMethodLines(MethodTree tree) {
if (tree.block() == null) return 0;
List<StatementTree> statements = tree.block().body();
if (statements.isEmpty()) return 0;
int firstLine = context.getTreeLines(tree).start().line();
int lastLine = context.getTreeLines(tree).end().line();
return lastLine - firstLine + 1;
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
@Rule(
key = "TooManyParameters",
name = "Too Many Method Parameters",
description = "Methods should not have too many parameters",
tags = {"code-smell", "design"},
priority = "MAJOR"
)
public class TooManyParametersRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
private static final int MAX_PARAMETERS = 5;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitMethod(MethodTree tree) {
int parameterCount = tree.parameters().size();
if (parameterCount > MAX_PARAMETERS) {
reportIssue(tree,
String.format("Method has %d parameters which exceeds maximum of %d. " +
"Consider using a parameter object.",
parameterCount, MAX_PARAMETERS));
}
super.visitMethod(tree);
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
@Rule(
key = "MagicNumber",
name = "Magic Number",
description = "Numeric literals should not be used directly in code",
tags = {"code-smell", "readability"},
priority = "MINOR"
)
public class MagicNumberRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitLiteral(LiteralTree tree) {
if (tree.is(Tree.Kind.INT_LITERAL, Tree.Kind.LONG_LITERAL,
Tree.Kind.FLOAT_LITERAL, Tree.Kind.DOUBLE_LITERAL)) {
String value = tree.value();
// Common allowed numbers (0, 1, -1, 100 for percentages, etc.)
if (!isAllowedNumber(value)) {
reportIssue(tree,
"Magic number detected: " + value + ". " +
"Define it as a constant with a meaningful name.");
}
}
super.visitLiteral(tree);
}
private boolean isAllowedNumber(String value) {
return value.equals("0") || value.equals("1") || value.equals("-1") ||
value.equals("100") || value.equals("1000") || value.equals("60") ||
value.equals("24") || value.equals("3600");
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
Performance Rules
package com.example.sonar.rules.performance;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
@Rule(
key = "StringConcatenationInLoop",
name = "String Concatenation in Loop",
description = "String concatenation in loops should be avoided",
tags = {"performance", "bug"},
priority = "MAJOR"
)
public class StringConcatenationInLoopRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
private boolean inLoop = false;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitForStatement(ForStatementTree tree) {
boolean previousInLoop = inLoop;
inLoop = true;
super.visitForStatement(tree);
inLoop = previousInLoop;
}
@Override
public void visitWhileStatement(WhileStatementTree tree) {
boolean previousInLoop = inLoop;
inLoop = true;
super.visitWhileStatement(tree);
inLoop = previousInLoop;
}
@Override
public void visitDoWhileStatement(DoWhileStatementTree tree) {
boolean previousInLoop = inLoop;
inLoop = true;
super.visitDoWhileStatement(tree);
inLoop = previousInLoop;
}
@Override
public void visitForEachStatement(ForEachStatementTree tree) {
boolean previousInLoop = inLoop;
inLoop = true;
super.visitForEachStatement(tree);
inLoop = previousInLoop;
}
@Override
public void visitBinaryExpression(BinaryExpressionTree tree) {
if (inLoop && tree.is(Tree.Kind.PLUS)) {
// Check if it's string concatenation
if (isStringConcatenation(tree)) {
reportIssue(tree,
"String concatenation in loop detected. " +
"Use StringBuilder instead for better performance.");
}
}
super.visitBinaryExpression(tree);
}
private boolean isStringConcatenation(BinaryExpressionTree tree) {
// Simplified check - real implementation would be more sophisticated
return tree.leftOperand().symbolType().is("java.lang.String") ||
tree.rightOperand().symbolType().is("java.lang.String");
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
Custom Rules Registry
package com.example.sonar.rules;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import java.util.Collections;
import java.util.List;
public class MyCustomRulesRegistrar implements CheckRegistrar {
@Override
public void register(RegistrarContext registrarContext) {
registrarContext.registerClassesForRepository(
MyCustomRulesDefinition.REPOSITORY_KEY,
checkClasses(),
testCheckClasses()
);
}
private static List<Class<? extends JavaCheck>> checkClasses() {
return List.of(
// Security rules
com.example.sonar.rules.security.SQLInjectionRule.class,
com.example.sonar.rules.security.HardCodedPasswordRule.class,
// Quality rules
com.example.sonar.rules.quality.LongMethodRule.class,
com.example.sonar.rules.quality.TooManyParametersRule.class,
com.example.sonar.rules.quality.MagicNumberRule.class,
// Performance rules
com.example.sonar.rules.performance.StringConcatenationInLoopRule.class
);
}
private static List<Class<? extends JavaCheck>> testCheckClasses() {
return Collections.emptyList();
}
}
Rules Definition
package com.example.sonar.rules;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.plugins.java.Java;
import org.sonarsource.analyzer.commons.RuleMetadataLoader;
import java.util.Collections;
import java.util.List;
public class MyCustomRulesDefinition implements RulesDefinition {
public static final String REPOSITORY_KEY = "my-custom-rules";
public static final String REPOSITORY_NAME = "My Custom Rules";
private static final List<String> RULE_PATHS = List.of(
"org/sonar/l10n/java/rules/custom"
);
@Override
public void define(Context context) {
NewRepository repository = context.createRepository(REPOSITORY_KEY, Java.KEY)
.setName(REPOSITORY_NAME);
RuleMetadataLoader ruleMetadataLoader = new RuleMetadataLoader(
"org/sonar/l10n/java",
RULE_PATHS
);
ruleMetadataLoader.addRulesByAnnotatedClass(repository, checkClasses());
repository.done();
}
private static List<Class<?>> checkClasses() {
return List.of(
com.example.sonar.rules.security.SQLInjectionRule.class,
com.example.sonar.rules.security.HardCodedPasswordRule.class,
com.example.sonar.rules.quality.LongMethodRule.class,
com.example.sonar.rules.quality.TooManyParametersRule.class,
com.example.sonar.rules.quality.MagicNumberRule.class,
com.example.sonar.rules.performance.StringConcatenationInLoopRule.class
);
}
}
Plugin Definition
package com.example.sonar.plugin;
import org.sonar.api.Plugin;
import com.example.sonar.rules.MyCustomRulesDefinition;
import com.example.sonar.rules.MyCustomRulesRegistrar;
public class MyJavaPlugin implements Plugin {
@Override
public void define(Context context) {
context.addExtensions(
MyCustomRulesDefinition.class,
MyCustomRulesRegistrar.class
);
}
}
Quality Gate Configuration
Quality Profile Definition
package com.example.sonar.quality;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
public class MyQualityProfile implements BuiltInQualityProfilesDefinition {
@Override
public void define(Context context) {
NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(
"My Java Profile",
"java"
);
// Activate custom rules
profile.activateRule("my-custom-rules", "SQLInjectionVulnerability");
profile.activateRule("my-custom-rules", "HardCodedPassword");
profile.activateRule("my-custom-rules", "LongMethod");
profile.activateRule("my-custom-rules", "TooManyParameters");
profile.activateRule("my-custom-rules", "MagicNumber");
profile.activateRule("my-custom-rules", "StringConcatenationInLoop");
// Activate important built-in rules
profile.activateRule("squid", "S00112"); // String literals should not be duplicated
profile.activateRule("squid", "S106"); // Standard outputs should not be used
profile.activateRule("squid", "S1166"); // Exception handlers should preserve original exceptions
profile.done();
}
}
Testing Custom Rules
Rule Testing Framework
package com.example.sonar.test;
import org.junit.Test;
import org.sonar.java.checks.verifier.CheckVerifier;
import com.example.sonar.rules.quality.MagicNumberRule;
import com.example.sonar.rules.security.SQLInjectionRule;
public class CustomRulesTest {
@Test
public void testMagicNumberRule() {
CheckVerifier.newVerifier()
.onFile("src/test/files/MagicNumber.java")
.withCheck(new MagicNumberRule())
.verifyIssues();
}
@Test
public void testSQLInjectionRule() {
CheckVerifier.newVerifier()
.onFile("src/test/files/SQLInjection.java")
.withCheck(new SQLInjectionRule())
.verifyIssues();
}
@Test
public void testLongMethodRule() {
CheckVerifier.newVerifier()
.onFile("src/test/files/LongMethod.java")
.withCheck(new com.example.sonar.rules.quality.LongMethodRule())
.verifyIssues();
}
}
Test Files
src/test/files/MagicNumber.java
public class MagicNumberExample {
public void calculate() {
int result = 42 * 100; // Noncompliant - 42 is magic number
double percentage = value * 0.15; // Noncompliant - 0.15 is magic number
// Compliant examples
int hoursInDay = 24;
int maxRetries = 3;
}
private static final int ANSWER_TO_EVERYTHING = 42;
private static final double DISCOUNT_RATE = 0.15;
}
src/test/files/SQLInjection.java
import java.sql.*;
public class SQLInjectionExample {
public void vulnerableMethod(String userInput) throws SQLException {
Connection conn = DriverManager.getConnection("url");
Statement stmt = conn.createStatement();
// Noncompliant - SQL injection vulnerability
String query = "SELECT * FROM users WHERE name = '" + userInput + "'";
ResultSet rs = stmt.executeQuery(query);
}
public void safeMethod(String userInput) throws SQLException {
Connection conn = DriverManager.getConnection("url");
// Compliant - using prepared statement
String query = "SELECT * FROM users WHERE name = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, userInput);
ResultSet rs = stmt.executeQuery();
}
}
src/test/files/LongMethod.java
public class LongMethodExample {
// Noncompliant - method is too long
public void veryLongMethod() {
// Line 1
System.out.println("Line 1");
// Line 2
System.out.println("Line 2");
// ... imagine 20+ more lines ...
// Line 21
System.out.println("Line 21");
}
// Compliant - method is short
public void shortMethod() {
System.out.println("Short method");
}
}
Integration with CI/CD
GitHub Actions Workflow
name: SonarQube Analysis
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache SonarQube packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build and analyze
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: |
mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.projectKey=my-java-project \
-Dsonar.java.coveragePlugin=jacoco \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
Jenkins Pipeline
pipeline {
agent any
tools {
jdk 'jdk17'
maven 'maven-3.8'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build & Test') {
steps {
sh 'mvn clean compile test jacoco:report'
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('sonarqube-server') {
sh 'mvn sonar:sonar \
-Dsonar.projectKey=my-java-project \
-Dsonar.java.coveragePlugin=jacoco \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml'
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 10, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
post {
always {
junit 'target/surefire-reports/*.xml'
jacoco execPattern: 'target/jacoco.exec'
}
}
}
Advanced Rule Development
Complex Rule with Symbol Resolution
package com.example.sonar.rules.advanced;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.*;
@Rule(
key = "UnusedPrivateMethod",
name = "Unused Private Method",
description = "Private methods that are not used should be removed",
tags = {"code-smell", "unused"},
priority = "MAJOR"
)
public class UnusedPrivateMethodRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitMethod(MethodTree tree) {
Symbol.MethodSymbol methodSymbol = tree.symbol();
if (isPrivateUnusedMethod(methodSymbol, tree)) {
reportIssue(tree,
"Private method '" + methodSymbol.name() + "' is never used. " +
"Consider removing it.");
}
super.visitMethod(tree);
}
private boolean isPrivateUnusedMethod(Symbol.MethodSymbol methodSymbol, MethodTree tree) {
return methodSymbol.isPrivate() &&
!methodSymbol.name().equals("<init>") && // Exclude constructors
!isOverride(methodSymbol) &&
!isTestMethod(tree) &&
!isUsed(methodSymbol);
}
private boolean isOverride(Symbol.MethodSymbol methodSymbol) {
return methodSymbol.overriddenSymbol() != null;
}
private boolean isTestMethod(MethodTree tree) {
// Check if method has @Test annotation
return tree.modifiers().annotations().stream()
.anyMatch(annotation ->
annotation.annotationType().symbolType().fullyQualifiedName().equals("org.junit.Test"));
}
private boolean isUsed(Symbol.MethodSymbol methodSymbol) {
// This is a simplified check
// Real implementation would need to track method usage across the file
return methodSymbol.usages().size() > 1; // +1 for the declaration itself
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
Data Flow Analysis Rule
package com.example.sonar.rules.advanced;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.plugins.java.api.tree.Tree.Kind;
@Rule(
key = "NullPointerDereference",
name = "Potential Null Pointer Dereference",
description = "Detects potential null pointer dereferences",
tags = {"bug", "null-pointer"},
priority = "CRITICAL"
)
public class NullPointerDereferenceRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
ExpressionTree methodSelect = tree.methodSelect();
if (methodSelect.is(Kind.MEMBER_SELECT)) {
MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) methodSelect;
ExpressionTree expression = memberSelect.expression();
if (expression.symbolType().isSubtypeOf("java.lang.Object") &&
!isCheckedForNull(expression)) {
reportIssue(tree,
"Potential null pointer dereference. " +
"The expression '" + expression + "' might be null.");
}
}
super.visitMethodInvocation(tree);
}
private boolean isCheckedForNull(ExpressionTree expression) {
// Simplified null check detection
// Real implementation would use data flow analysis
Tree parent = expression.parent();
while (parent != null) {
if (parent.is(Kind.IF_STATEMENT)) {
IfStatementTree ifStatement = (IfStatementTree) parent;
if (containsNullCheck(ifStatement.condition(), expression)) {
return true;
}
}
parent = parent.parent();
}
return false;
}
private boolean containsNullCheck(ExpressionTree condition, ExpressionTree target) {
return condition.toString().contains(target + " != null") ||
condition.toString().contains(target + " == null");
}
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
}
Best Practices for Rule Development
Rule Implementation Guidelines
package com.example.sonar.rules.bestpractices;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
@Rule(
key = "BestPracticeTemplate",
name = "Best Practice Rule Template",
description = "Template showing best practices for rule development",
tags = {"best-practice"},
priority = "MAJOR"
)
public class BestPracticeRule extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
// BEST PRACTICE 1: Use specific tree kinds for better performance
@Override
public void visitMethod(MethodTree tree) {
// Only process method trees
checkMethod(tree);
super.visitMethod(tree);
}
// BEST PRACTICE 2: Extract complex logic into separate methods
private void checkMethod(MethodTree tree) {
if (hasIssues(tree)) {
reportIssue(tree, createMessage(tree));
}
}
// BEST PRACTICE 3: Use semantic model when possible
private boolean hasIssues(MethodTree tree) {
return tree.symbol().isPublic() &&
tree.parameters().size() > 5;
}
// BEST PRACTICE 4: Provide clear, actionable messages
private String createMessage(MethodTree tree) {
return String.format(
"Public method '%s' has %d parameters. " +
"Consider reducing the number of parameters or using a parameter object.",
tree.symbol().name(),
tree.parameters().size()
);
}
// BEST PRACTICE 5: Report issues at the most specific location
private void reportIssue(Tree tree, String message) {
context.reportIssue(this, tree, message);
}
// BEST PRACTICE 6: Handle edge cases gracefully
@Override
public void visitClass(ClassTree tree) {
if (tree.symbol().isInterface()) {
// Skip interfaces
return;
}
super.visitClass(tree);
}
}
Conclusion
This comprehensive SonarJava analyzer implementation provides:
- Custom rule development for security, quality, and performance
- Integration with SonarQube and CI/CD pipelines
- Testing framework for rule validation
- Best practices for effective static analysis
- Advanced rule patterns using semantic analysis
Key benefits include:
- Enhanced Code Quality through automated code review
- Security Vulnerability Detection for OWASP Top 10 issues
- Performance Optimization through code pattern analysis
- Maintainability Improvement by detecting code smells
- Customizable Rule Set tailored to your organization's needs
The implementation follows SonarSource best practices and provides a solid foundation for building comprehensive code quality analysis for Java projects.
Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection
Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.
Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.
Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.
Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.
Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.
Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.
Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.
JAVA CODE COMPILER