Skip to content

Commit

Permalink
Merge pull request #2806 from guwirth/unknown-rules-warning
Browse files Browse the repository at this point in the history
New feature to report unknown rules / warnings
  • Loading branch information
guwirth authored Nov 14, 2024
2 parents 666c53b + fb4e5ec commit f155c37
Show file tree
Hide file tree
Showing 39 changed files with 671 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public class CxxCompilerGccSensor extends CxxCompilerSensor {
*/
public static final String DEFAULT_ID = "default";
public static final String DEFAULT_REGEX_DEF
= "(?<file>[^:]*+):(?<line>\\d{1,5}):\\d{1,5}:\\x20warning:\\x20(?<message>.*?)(\\x20\\[(?<id>.*)\\])?\\s*$";
= "(?<file>[^:]*+):(?<line>\\d{1,5}):\\d{1,5}:\\x20warning:\\x20"
+ "(?<message>.*?)(\\x20\\[(?<id>[^\\[]*)\\])?\\s*$";

public static List<PropertyDefinition> properties() {
var subcateg = "GCC";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
package org.sonar.cxx.sensors.utils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -43,9 +44,14 @@ public abstract class CxxIssuesReportSensor extends CxxReportSensor {

private static final Logger LOG = LoggerFactory.getLogger(CxxIssuesReportSensor.class);

private final Set<CxxReportIssue> uniqueIssues = new HashSet<>();
public static final String DEFAULT_UNKNOWN_RULE_KEY = "unknown";

private final HashSet<CxxReportIssue> uniqueIssues = new HashSet<>();
private int savedNewIssues = 0;

private final HashMap<String, HashSet<String>> knownRulesPerRepositoryKey = new HashMap<>();
private final HashSet<String> mappedRuleIds = new HashSet<>();

/**
* {@inheritDoc}
*/
Expand All @@ -57,13 +63,42 @@ protected CxxIssuesReportSensor() {
*/
@Override
public void executeImpl() {
downloadRulesFromServer();
List<File> reports = getReports(getReportPathsKey());
for (var report : reports) {
executeReport(report);
}
}

private void downloadRulesFromServer() {

// deactivate mapping if 'unknown' rule is not active
if (context.activeRules().find(RuleKey.of(getRuleRepositoryKey(), DEFAULT_UNKNOWN_RULE_KEY)) == null) {
LOG.info("Rule mapping to '{}:{}' is not active", getRuleRepositoryKey(), DEFAULT_UNKNOWN_RULE_KEY);
return;
}

try {
String url = context.config().get("sonar.host.url").orElse("http://localhost:9000");
LOG.info("Downloading rules for '{}' from server '{}'", getRuleRepositoryKey(), url);
var ruleKeys = SonarServerWebApi.getRuleKeys(
url,
context.config().get("sonar.token")
.or(() -> context.config().get("sonar.login")) // deprecated: can be removed in future
.orElse(System.getenv("SONAR_TOKEN")),
"cxx",
getRuleRepositoryKey());
if (!ruleKeys.isEmpty()) {
knownRulesPerRepositoryKey.put(getRuleRepositoryKey(), ruleKeys);
LOG.debug("{} rules for '{}' were loaded from server", ruleKeys.size(), getRuleRepositoryKey());
}
} catch (IOException e) {
LOG.warn("Rules for '{}' could not be loaded from server", getRuleRepositoryKey(), e);
}
}

private void saveIssue(String ruleId, CxxReportIssue issue) {
ruleId = mapUnknownRuleId(ruleId, issue);
var newIssue = context.newIssue();
if (addLocations(newIssue, ruleId, issue)) {
addFlow(newIssue, issue);
Expand All @@ -72,6 +107,21 @@ private void saveIssue(String ruleId, CxxReportIssue issue) {
}
}

private String mapUnknownRuleId(String ruleId, CxxReportIssue issue) {
var repository = knownRulesPerRepositoryKey.get(getRuleRepositoryKey());
if (repository != null && !repository.contains(ruleId)) {
if (mappedRuleIds.add(ruleId)) {
LOG.info("Rule '{}' is unknown in '{}' and will be mapped to '{}'",
ruleId,
getRuleRepositoryKey(),
DEFAULT_UNKNOWN_RULE_KEY);
}
issue.addMappedInfo();
ruleId = DEFAULT_UNKNOWN_RULE_KEY;
}
return ruleId;
}

/**
* Saves code violation only if it wasn't already saved
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* C++ Community Plugin (cxx plugin)
* Copyright (C) 2010-2024 SonarOpenCommunity
* http://github.com/SonarOpenCommunity/sonar-cxx
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.cxx.sensors.utils;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Base64;
import java.util.HashSet;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SonarServerWebApi {

private static final Logger LOG = LoggerFactory.getLogger(CxxIssuesReportSensor.class);
static ObjectMapper objectMapper = new ObjectMapper();

private SonarServerWebApi() {
}

/**
* Get list with rule keys from server.
*
* @param serverUrl url of the SonarQube server
* @param authenticationToken authentication token to use for API access
* @param language language filter for result
* @param tag repository key
* @return list of all keys of the rules matching the filter criteria
*
* @throws IOException if an I/O error occurs when sending or receiving
*/
public static HashSet<String> getRuleKeys(String serverUrl, String authenticationToken, String language, String tag)
throws IOException {

int p = 0;
int total;
Response response = null;
String requestURL = createUrl(serverUrl, language, tag);
do {
p++;
Response res = objectMapper.readValue(get(requestURL + p, authenticationToken), Response.class);
if (response == null || response.rules() == null) {
response = res;
} else {
response.rules().addAll(res.rules());
}
total = res.total();
} while (total - p * 500 > 0);

return response.rules().stream()
.map(Rule::key)
.map(k -> k.replace(tag + ":", ""))
.collect(Collectors.toCollection(HashSet::new));
}

private static String createUrl(String sonarUrl, String language, String tag) {
StringBuilder builder = new StringBuilder(1024);
builder.append(sonarUrl);
if (!sonarUrl.endsWith("/")) {
builder.append("/");
}
builder.append("api/rules/search?f=internalKey&ps=500");
builder.append("&language=").append(language);
builder.append("&tags=").append(tag);
builder.append("&p=");

return builder.toString();
}

/**
* HTTP method GET.
*
* @param uri uri to use for the GET method
* @param authenticationToken authentication token to use for the GET method
* @return response of the server
*
* @throws IOException if an I/O error occurs when sending or receiving
*/
public static String get(String uri, String authenticationToken) throws IOException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((authenticationToken + ":").getBytes()))
.build();

try {
long start = System.currentTimeMillis();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
long finish = System.currentTimeMillis();
LOG.debug("{} {} {} | time={}ms", response.request().method(), response.statusCode(), response.request().uri(),
finish - start);
return response.body();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException(e);
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static record Response(int total, int p, int ps, HashSet<Rule> rules) {

}

@JsonIgnoreProperties(ignoreUnknown = true)
public static record Rule(String key, String type) {

}

}
Loading

0 comments on commit f155c37

Please sign in to comment.