Skip to main content

SonarQube

Overview

SonarQube is an open-source platform for continuously inspecting code quality and security. Through static code analysis, it detects bugs, vulnerabilities, and code smells, visualizing technical debt.

Key Features

1. Code Quality Analysis

  • Bug Detection: Identify code issues that could lead to runtime errors
  • Code Smells: Detect code patterns that reduce maintainability
  • Duplicate Code: Identify duplication within the codebase
  • Complexity Measurement: Calculate metrics such as cyclomatic complexity

2. Security Analysis

  • Vulnerability Detection: Identify security vulnerabilities such as OWASP Top 10
  • Security Hotspots: Point out security concerns that need review
  • CWE/SANS Top 25 Support: Analysis based on common vulnerability patterns

3. Visualization of Technical Debt

  • Quantification of Debt: Estimate time required to fix issues
  • Quality Gate: Determine if the project meets quality standards
  • Trend Analysis: Track changes in code quality over time

Supported Languages

SonarQube supports numerous programming languages:

  • Java, C#, JavaScript, TypeScript
  • Python, PHP, Ruby, Go
  • C, C++, Objective-C, Swift
  • Kotlin, Scala, HTML, CSS
  • SQL, XML, YAML, etc.

Examples of Detected Issues (.NET)

Here are typical code quality and security issues detected by SonarQube, with C# code examples.

1. Null Reference Issues

Problematic Code:

public class UserService
{
public string GetUserName(User user)
{
// S2259: user might be null but is not checked
return user.Name.ToUpper();
}
}

Fixed:

public class UserService
{
public string GetUserName(User user)
{
if (user == null)
throw new ArgumentNullException(nameof(user));

return user.Name?.ToUpper() ?? string.Empty;
}
}

2. Exception Handling Issues

Problematic Code:

public class DataProcessor
{
public void ProcessData()
{
try
{
// Processing
}
catch (Exception ex)
{
// S2737: Empty catch block - exception is ignored
}
}

public void AnotherMethod()
{
try
{
// Processing
}
catch (Exception ex)
{
// S1181: Should not catch generic Exception
Console.WriteLine(ex.Message);
}
}
}

Fixed:

public class DataProcessor
{
private readonly ILogger<DataProcessor> _logger;

public void ProcessData()
{
try
{
// Processing
}
catch (InvalidOperationException ex)
{
// Catch specific exception and handle appropriately
_logger.LogError(ex, "Error occurred during data processing");
throw; // Rethrow if necessary
}
}
}

3. Resource Leaks

Problematic Code:

public class FileHandler
{
public string ReadFile(string path)
{
// S2930: Not using using statement
var reader = new StreamReader(path);
return reader.ReadToEnd();
// reader is not disposed
}
}

Fixed:

public class FileHandler
{
public string ReadFile(string path)
{
using (var reader = new StreamReader(path))
{
return reader.ReadToEnd();
}
// Or use using declaration
// using var reader = new StreamReader(path);
// return reader.ReadToEnd();
}
}

4. Security Vulnerability - SQL Injection

Problematic Code:

public class UserRepository
{
private readonly SqlConnection _connection;

public User GetUser(string username)
{
// S3649: SQL Injection vulnerability
var query = $"SELECT * FROM Users WHERE Username = '{username}'";
var command = new SqlCommand(query, _connection);
// Attacker can input "admin' OR '1'='1" in username
return ExecuteQuery(command);
}
}

Fixed:

public class UserRepository
{
private readonly SqlConnection _connection;

public User GetUser(string username)
{
var query = "SELECT * FROM Users WHERE Username = @username";
var command = new SqlCommand(query, _connection);
command.Parameters.AddWithValue("@username", username);
return ExecuteQuery(command);
}
}

5. Security Vulnerability - Hardcoded Credentials

Problematic Code:

public class DatabaseConnection
{
// S2068: Hardcoded password
private const string ConnectionString =
"Server=myserver;Database=mydb;User=admin;Password=P@ssw0rd123;";

public SqlConnection GetConnection()
{
return new SqlConnection(ConnectionString);
}
}

Fixed:

public class DatabaseConnection
{
private readonly IConfiguration _configuration;

public DatabaseConnection(IConfiguration configuration)
{
_configuration = configuration;
}

public SqlConnection GetConnection()
{
var connectionString = _configuration.GetConnectionString("DefaultConnection");
return new SqlConnection(connectionString);
}
}

6. High Code Complexity

Problematic Code:

public class OrderValidator
{
// S1541: Cognitive complexity is too high (nesting is deep)
public bool ValidateOrder(Order order)
{
if (order != null)
{
if (order.Items != null)
{
if (order.Items.Count > 0)
{
foreach (var item in order.Items)
{
if (item != null)
{
if (item.Quantity > 0)
{
if (item.Price > 0)
{
// Processing
}
else
{
return false;
}
}
else
{
return false;
}
}
}
}
}
}
return true;
}
}

Fixed:

public class OrderValidator
{
public bool ValidateOrder(Order order)
{
if (order == null || order.Items == null || order.Items.Count == 0)
return false;

return order.Items.All(item => IsValidItem(item));
}

private bool IsValidItem(OrderItem item)
{
return item != null && item.Quantity > 0 && item.Price > 0;
}
}

7. Dead Code

Problematic Code:

public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}

// S1144: Unused private method
private int Subtract(int a, int b)
{
return a - b;
}

public int Multiply(int a, int b)
{
int unused = 10; // S1481: Unused local variable
return a * b;
}
}

Fixed:

public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}

public int Multiply(int a, int b)
{
return a * b;
}

// Remove unnecessary code
}

8. Misuse of Async Methods

Problematic Code:

public class ApiClient
{
private readonly HttpClient _httpClient;

public string GetData(string url)
{
// S4462: Waiting synchronously for async method (Risk of deadlock)
var response = _httpClient.GetAsync(url).Result;
return response.Content.ReadAsStringAsync().Result;
}
}

Fixed:

public class ApiClient
{
private readonly HttpClient _httpClient;

public async Task<string> GetDataAsync(string url)
{
var response = await _httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
}

9. Duplicate Code

Problematic Code:

public class ReportGenerator
{
public string GeneratePdfReport(Report report)
{
var header = $"Report: {report.Title}\n";
header += $"Date: {DateTime.Now:yyyy-MM-dd}\n";
header += $"Author: {report.Author}\n";
header += "---\n";
// PDF generation processing
return GeneratePdf(header, report.Content);
}

public string GenerateHtmlReport(Report report)
{
var header = $"Report: {report.Title}\n";
header += $"Date: {DateTime.Now:yyyy-MM-dd}\n";
header += $"Author: {report.Author}\n";
header += "---\n";
// HTML generation processing
return GenerateHtml(header, report.Content);
}
}

Fixed:

public class ReportGenerator
{
private string BuildHeader(Report report)
{
return $"Report: {report.Title}\n" +
$"Date: {DateTime.Now:yyyy-MM-dd}\n" +
$"Author: {report.Author}\n" +
"---\n";
}

public string GeneratePdfReport(Report report)
{
var header = BuildHeader(report);
return GeneratePdf(header, report.Content);
}

public string GenerateHtmlReport(Report report)
{
var header = BuildHeader(report);
return GenerateHtml(header, report.Content);
}
}

10. Weak Encryption Implementation

Problematic Code:

public class PasswordHasher
{
// S4790: Using weak hashing algorithm (MD5)
public string HashPassword(string password)
{
using (var md5 = MD5.Create())
{
var bytes = Encoding.UTF8.GetBytes(password);
var hash = md5.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
}

Fixed:

public class PasswordHasher
{
public string HashPassword(string password)
{
// Use bcrypt, PBKDF2, or Argon2
return BCrypt.Net.BCrypt.HashPassword(password);
}

public bool VerifyPassword(string password, string hash)
{
return BCrypt.Net.BCrypt.Verify(password, hash);
}
}

These examples are some of the issues detected by SonarQube. Each issue is assigned a unique rule ID (e.g., S2259, S3649), and detailed descriptions and remediation methods can be checked on the SonarQube dashboard.

Architecture

┌─────────────────┐
│ Developer IDE │
│ (SonarLint) │
└────────┬────────┘


┌─────────────────┐
│ CI/CD Pipe │
│ (SonarScanner) │
└────────┬────────┘


┌─────────────────┐
│ SonarQube Server│
│ ・Analysis Eng │
│ ・Database │
│ ・Web Dash │
└─────────────────┘

Key Components

  1. SonarQube Server

    • Web Server (Dashboard)
    • Search Server (Elasticsearch)
    • Compute Engine (Background processing)
  2. SonarScanner

    • Client tool to execute code scan
    • Integrated into CI/CD pipeline
  3. SonarLint

    • IDE integration plugin
    • Check code quality in real-time
  4. Database

    • Store analysis results and project settings
    • Supports PostgreSQL, Microsoft SQL Server, Oracle

Setup

Quick Start using Docker

# Start SonarQube container
docker run -d --name sonarqube \
-p 9000:9000 \
sonarqube:lts-community

# Access http://localhost:9000 in browser
# Initial credentials: admin / admin

Running Project Scan

For .NET Projects

# Install SonarScanner for .NET
dotnet tool install --global dotnet-sonarscanner

# Start scan
dotnet sonarscanner begin \
/k:"project-key" \
/d:sonar.host.url="http://localhost:9000" \
/d:sonar.login="your-token"

# Execute build
dotnet build

# End scan
dotnet sonarscanner end /d:sonar.login="your-token"

For JavaScript/TypeScript Projects

# Install sonar-scanner
npm install -g sonar-scanner

# Create sonar-project.properties in project root
# sonar.projectKey=my-project
# sonar.sources=src
# sonar.host.url=http://localhost:9000
# sonar.login=your-token

# Execute scan
sonar-scanner

For Java/Maven Projects

# If SonarQube plugin is added to pom.xml
mvn clean verify sonar:sonar \
-Dsonar.projectKey=my-project \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=your-token

CI/CD Integration

GitHub Actions

name: SonarQube Analysis

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch entire history

- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

Azure DevOps

# azure-pipelines.yml
trigger:
- main

pool:
vmImage: 'ubuntu-latest'

steps:
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQubeServiceConnection'
scannerMode: 'MSBuild'
projectKey: 'my-project'

- task: DotNetCoreCLI@2
inputs:
command: 'build'

- task: SonarQubeAnalyze@5

- task: SonarQubePublish@5
inputs:
pollingTimeoutSec: '300'

Quality Gate

Quality Gate is a criterion to determine if a project is releasable.

Default Quality Gate Conditions

  • Coverage on New Code >= 80%
  • Duplicated Lines on New Code < 3%
  • Security Hotspots Reviewed on New Code = 100%
  • Reliability Rating on New Code = A
  • Security Rating on New Code = A
  • Maintainability Rating on New Code = A

Creating Custom Quality Gate

Quality Gates > Create > Add Condition
- Select Metric (e.g., Bugs, Vulnerabilities, Code Smells)
- Select Operator (e.g., is greater than, is less than)
- Set Threshold

Best Practices

1. Continuous Integration

  • Run SonarQube analysis on every pull request
  • Block merge if Quality Gate fails
  • Regularly scan main branch

2. Focus on New Code

  • Adopt "Clean as You Code" approach
  • Prioritize quality of new code over existing issues
  • Improve legacy code incrementally

3. Security First

  • Regularly review Security Hotspots
  • Fix vulnerabilities immediately
  • Leverage security reports

4. Team-wide Usage

  • Integrate SonarLint into IDE and leverage real-time feedback
  • Refer to SonarQube results during code reviews
  • Share quality metrics with the team

5. Custom Rule Configuration

  • Reflect project-specific coding conventions
  • Disable unnecessary rules
  • Adjust severity of important rules

SonarQube vs SonarCloud

FeatureSonarQubeSonarCloud
HostingSelf-hostedCloud (SaaS)
PricingCommunity Edition is freePublic repositories are free
SetupServer installation requiredAvailable immediately
CustomizationFull controlLimited
MaintenanceSelf-managedManaged by SonarSource
TargetFor private codeFor OSS/Cloud Native

Troubleshooting

Common Issues

Q: Scan fails

# Check logs
docker logs sonarqube

# If out of memory
docker run -d --name sonarqube \
-e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \
-p 9000:9000 \
sonarqube:lts-community

Q: Coverage is not displayed

  • Check execution of coverage tool (e.g., dotnet test --collect:"XPlat Code Coverage")
  • Specify report path with sonar.cs.opencover.reportsPaths parameter

Q: Analysis time is long

  • Enable incremental analysis
  • Exclude unnecessary files (sonar.exclusions setting)
  • Increase number of Compute Engine threads

Summary

SonarQube is a powerful tool for continuously improving code quality and security. By integrating it into the CI/CD pipeline, you can maintain the quality of new code and visualize technical debt. It is important to follow the "Clean as You Code" principle and work on improving code quality as a whole team.