-
Notifications
You must be signed in to change notification settings - Fork 11
/
ICodeSensor.java
393 lines (343 loc) · 17.6 KB
/
ICodeSensor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/*
* This file is part of sonar-icode-cnes-plugin.
*
* sonar-icode-cnes-plugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sonar-icode-cnes-plugin 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sonar-icode-cnes-plugin. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.cnes.sonar.plugins.icode.check;
import fr.cnes.icode.Analyzer;
import fr.cnes.icode.data.CheckResult;
import fr.cnes.icode.services.languages.LanguageService;
import fr.cnes.sonar.plugins.icode.exceptions.ICodeException;
import fr.cnes.sonar.plugins.icode.languages.Fortran77Language;
import fr.cnes.sonar.plugins.icode.languages.Fortran90Language;
import fr.cnes.sonar.plugins.icode.languages.ShellLanguage;
import fr.cnes.sonar.plugins.icode.measures.ICodeMetricsProcessor;
import fr.cnes.sonar.plugins.icode.model.AnalysisFile;
import fr.cnes.sonar.plugins.icode.model.AnalysisProject;
import fr.cnes.sonar.plugins.icode.model.AnalysisRule;
import fr.cnes.sonar.plugins.icode.model.XmlHandler;
import fr.cnes.sonar.plugins.icode.rules.ICodeRulesDefinition;
import fr.cnes.sonar.plugins.icode.settings.ICodePluginProperties;
import org.sonar.api.batch.fs.*;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.config.Configuration;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
/**
* Executed during sonar-scanner call.
* This Sensor is able to:
* - Run i-Code checks.
* - Import i-Code reports into SonarQube.
* - Run a specified external version of i-Code.
*/
public class ICodeSensor implements Sensor {
/**
* Logger for this class.
*/
private static final Logger LOGGER = Loggers.get(ICodeSensor.class);
/**
* Give information about this sensor.
*
* @param sensorDescriptor Descriptor injected to set the sensor.
*/
@Override
public void describe(final SensorDescriptor sensorDescriptor) {
// Prevents sensor to be run during all analysis.
sensorDescriptor.onlyOnLanguages(ShellLanguage.KEY, Fortran77Language.KEY, Fortran90Language.KEY);
// Defines sensor name
sensorDescriptor.name("Sonar i-Code");
// Only main files are concerned, not tests.
sensorDescriptor.onlyOnFileType(InputFile.Type.MAIN);
// This sensor is activated only if a rule from the following repo is activated.
sensorDescriptor.createIssuesForRuleRepositories(
ICodeRulesDefinition.getRepositoryKeyForLanguage(ShellLanguage.KEY),
ICodeRulesDefinition.getRepositoryKeyForLanguage(Fortran77Language.KEY),
ICodeRulesDefinition.getRepositoryKeyForLanguage(Fortran90Language.KEY));
}
/**
* Execute the analysis.
*
* @param sensorContext Provide SonarQube services to register results.
*/
@Override
public void execute(final SensorContext sensorContext) {
// Represent the configuration used for the analysis.
final Configuration config = sensorContext.config();
// Run external version of i-Code CNES on during analysis.
if(config.getBoolean(ICodePluginProperties.AUTOLAUNCH_PROP_KEY)
.orElse(Boolean.getBoolean(ICodePluginProperties.AUTOLAUNCH_PROP_DEFAULT))) {
executeExternalICode(sensorContext);
}
// Run embedded version of i-Code CNES on during analysis.
if(config.getBoolean(ICodePluginProperties.USE_EMBEDDED_PROP_KEY)
.orElse(Boolean.getBoolean(ICodePluginProperties.USE_EMBEDDED_PROP_DEFAULT))) {
executeEmbeddedICode(sensorContext);
}
// Import i-Code issues from external result files.
executeExternalResultsImport(sensorContext);
}
/**
* Import i-Code issues from external result files.
*
* @param sensorContext Context of the sensor.
*/
protected void executeExternalResultsImport(final SensorContext sensorContext) {
// Represent the file system used for the analysis.
final FileSystem fileSystem = sensorContext.fileSystem();
// Represent the configuration used for the analysis.
final Configuration config = sensorContext.config();
// Represent the active rules used for the analysis.
final ActiveRules activeRules = sensorContext.activeRules();
// Report files found in file system and corresponding to SQ property.
final List<File> reportFiles = getReportFiles(config, fileSystem);
// If exists, unmarshal each xml result file.
for(final File reportFile : reportFiles) {
try {
// Unmarshal the xml.
final FileInputStream file = new FileInputStream(reportFile);
final AnalysisProject analysisProject = (AnalysisProject) XmlHandler.unmarshal(file, AnalysisProject.class);
// Retrieve file in a SonarQube format.
final Map<String, InputFile> scannedFiles = getScannedFiles(fileSystem, analysisProject);
// Handles issues.
for (final AnalysisRule rule : analysisProject.getAnalysisRules()) {
if(isRuleActive(activeRules, rule.getAnalysisRuleId())) { // manage active rules
saveIssue(sensorContext, scannedFiles, rule);
} else if (ICodeMetricsProcessor.isMetric(rule.getAnalysisRuleId())) { // manage trivial measures
ICodeMetricsProcessor.saveMeasure(sensorContext, scannedFiles, rule);
} else { // log ignored data
LOGGER.info(String.format(
"An issue for rule '%s' was detected by i-Code but this rule is deactivated in current analysis.",
rule.getAnalysisRuleId()));
}
}
ICodeMetricsProcessor.saveExtraMeasures(sensorContext, scannedFiles, analysisProject);
} catch (FileNotFoundException e) {
LOGGER.error(e.getMessage(), e);
sensorContext.newAnalysisError().message(e.getMessage()).save();
}
}
}
/**
* Execute the embedded version of i-Code.
*
* @param sensorContext Context of the sensor.
*/
private void executeEmbeddedICode(final SensorContext sensorContext) {
// Initialisation of tools for analysis.
final Analyzer analyzer = new Analyzer();
final FileSystem fileSystem = sensorContext.fileSystem();
final FilePredicates predicates = fileSystem.predicates();
final ActiveRules activeRules = sensorContext.activeRules();
final Iterable<InputFile> inputFiles = fileSystem.inputFiles(predicates.hasType(InputFile.Type.MAIN));
final HashSet<File> files = new HashSet<>();
final HashMap<String,InputFile> filesMap = new HashMap<>();
// Gather all files in a Set.
for(final InputFile inputFile : inputFiles) {
files.add(new File(inputFile.uri()));
filesMap.put(inputFile.uri().getPath(), inputFile);
}
// Run all checkers on all files.
final List<CheckResult> results = analyzer.stableCheck(files, LanguageService.getLanguagesIds(), null);
// Add each issue to SonarQube.
for(final CheckResult result : results) {
if(isRuleActive(activeRules, result.getName())) { // manage active rules
saveIssue(sensorContext, result);
} else if (ICodeMetricsProcessor.isMetric(result.getName())) { // manage trivial measures
ICodeMetricsProcessor.saveMeasure(sensorContext, result);
} else { // log ignored data
LOGGER.info(String.format(
"An issue for rule '%s' was detected by i-Code but this rule is deactivated in current analysis.",
result.getName()));
}
}
ICodeMetricsProcessor.saveExtraMeasures(sensorContext, filesMap, results);
}
/**
* Execute i-Code through a system process.
*
* @param sensorContext Context of the sensor.
*/
private void executeExternalICode(final SensorContext sensorContext) {
LOGGER.info("i-Code CNES auto-launch enabled.");
final Configuration config = sensorContext.config();
final FileSystem fileSystem = sensorContext.fileSystem();
final FilePredicates predicates = fileSystem.predicates();
final Iterable<InputFile> inputFiles = fileSystem.inputFiles(predicates.hasType(InputFile.Type.MAIN));
final StringBuilder files = new StringBuilder();
inputFiles.forEach(file -> files.append(file.uri().getPath()).append(" "));
final String executable = config.get(ICodePluginProperties.ICODE_PATH_KEY).orElse(ICodePluginProperties.ICODE_PATH_DEFAULT);
final String outputFile = config.get(ICodePluginProperties.REPORT_PATH_KEY).orElse(ICodePluginProperties.REPORT_PATH_DEFAULT);
final String outputPath = Paths.get(sensorContext.fileSystem().baseDir().toString(),outputFile).toString();
final String outputOption = "-o";
final String command = String.join(" ", executable, files.toString(), outputOption, outputPath);
LOGGER.info("Running i-Code CNES and generating results to "+ outputPath);
try {
int success = runICode(command);
if(0!=success){
final String message = String.format("i-Code CNES auto-launch analysis failed with exit code %d.", success);
throw new ICodeException(message);
}
LOGGER.info("Auto-launch successfully executed i-Code CNES.");
} catch (final InterruptedException | IOException | ICodeException e) {
LOGGER.error(e.getMessage(), e);
}
}
/**
* Run the i-Code command.
*
* @param command The i-Code Command to execute.
* @return 0 if all was fine.
* @throws IOException On possible unknown file.
* @throws InterruptedException On possible process problem.
*/
protected int runICode(final String command) throws IOException, InterruptedException {
final Process icode = Runtime.getRuntime().exec(command);
return icode.waitFor();
}
/**
* This method save an issue into the SonarQube service.
*
* @param sensorContext A SensorContext to reach SonarQube services.
* @param result A CheckResult with the convenient format for i-Code.
*/
static void saveIssue(final SensorContext sensorContext, final CheckResult result) {
final FileSystem fileSystem = sensorContext.fileSystem();
final FilePredicates predicates = fileSystem.predicates();
final NewIssue issue = sensorContext.newIssue();
final String fileToFind = result.getFile().getPath();
final FilePredicate predicate = predicates.or(predicates.hasPath(fileToFind), predicates.hasRelativePath(fileToFind));
final InputFile file = fileSystem.inputFile(predicate);
if(Objects.nonNull(file)) {
final String repositoryKey = ICodeRulesDefinition.getRepositoryKeyForLanguage(file.language());
final RuleKey ruleKey = RuleKey.of(repositoryKey, result.getName());
final NewIssueLocation location = issue.newLocation();
final TextRange textRange = file.selectLine(result.getLine()>0?result.getLine():1);
location.on(file);
location.at(textRange);
location.message(result.getMessage());
issue.at(location);
issue.forRule(ruleKey);
issue.save();
}
}
/**
* This method save an issue into the SonarQube service.
*
* @param context A SensorContext to reach SonarQube services.
* @param files Map containing files in SQ format.
* @param issue A AnalysisRule with the convenient format for i-Code.
*/
static void saveIssue(final SensorContext context, final Map<String, InputFile> files, final AnalysisRule issue) {
// Retrieve the file containing the issue.
final InputFile inputFile = files.getOrDefault(issue.getResult().getFileName(), null);
if(inputFile!=null) {
// Retrieve the ruleKey if it exists.
final RuleKey ruleKey = RuleKey.of(ICodeRulesDefinition.getRepositoryKeyForLanguage(inputFile.language()), issue.getAnalysisRuleId());
// Create a new issue for SonarQube, but it must be saved using NewIssue.save().
final NewIssue newIssue = context.newIssue();
// Create a new location for this issue.
final NewIssueLocation newIssueLocation = newIssue.newLocation();
// Calculate the line number of the issue (must be between 1 and max, otherwise 1).
int issueLine = Integer.parseInt(issue.getResult().getResultLine());
issueLine = issueLine > 0 && issueLine <= inputFile.lines() ? issueLine : 1;
// Set trivial issue's attributes from AnalysisRule fields.
newIssueLocation.on(inputFile);
newIssueLocation.at(inputFile.selectLine(issueLine));
newIssueLocation.message(issue.getResult().getResultMessage());
newIssue.forRule(ruleKey);
newIssue.at(newIssueLocation);
newIssue.save();
} else {
LOGGER.error(String.format(
"Issue '%s' on file '%s' has not been saved because source file was not found.",
issue.getAnalysisRuleId(), issue.getResult().getFileName()
));
}
}
/**
* Construct a map with all found source files.
*
* @param fileSystem The file system on which the analysis is running.
* @param analysisProject The i-Code report content.
* @return A possibly empty Map of InputFile.
*/
protected Map<String, InputFile> getScannedFiles(final FileSystem fileSystem, final AnalysisProject analysisProject) {
// Contains the result to be returned.
final Map<String, InputFile> result = new HashMap<>();
final List<AnalysisFile> files = analysisProject.getAnalysisFiles();
// Looks for each file in file system, print an error if not found.
for(final AnalysisFile file : files) {
// Checks if the file system contains a file with corresponding path (relative or absolute).
final String fileToFind = new File(fileSystem.baseDir(), file.getFileName()).getPath();
final FilePredicate predicate = fileSystem.predicates().hasRelativePath(fileToFind);
final InputFile inputFile = fileSystem.inputFile(predicate);
if(inputFile!=null) {
result.put(file.getFileName(), inputFile);
} else {
LOGGER.error(String.format("The source file '%s' was not found.", file.getFileName()));
}
}
return result;
}
/**
* Returns a list of processable result files.
*
* @param config Configuration of the analysis where properties are put.
* @param fileSystem The current file system.
* @return Return a list of path 'findable' in the file system.
*/
protected List<File> getReportFiles(final Configuration config, final FileSystem fileSystem) {
// Contains the result to be returned.
final List<File> result = new ArrayList<>();
// Retrieves the non-verified path list from the SonarQube property.
final String[] pathArray = config.getStringArray(ICodePluginProperties.REPORT_PATH_KEY);
// Check if each path is known by the file system and add it to the processable path list,
// otherwise print a warning and ignore this result file.
for(final String path : pathArray) {
final File file = new File(fileSystem.baseDir(), path);
if(file.exists() && file.isFile()) {
result.add(file);
LOGGER.info(String.format("Results file %s has been found and will be processed.", path));
} else {
LOGGER.warn(String.format("Results file %s has not been found and wont be processed.", path));
}
}
return result;
}
/**
* Check if a rule is activated in current analysis.
*
* @param activeRules Set of active rules during an analysis.
* @param rule Key (i-Code) of the rule to check.
* @return True if the rule is active and false if not or not exists.
*/
protected boolean isRuleActive(final ActiveRules activeRules, final String rule) {
final RuleKey ruleKeyShell = RuleKey.of(ICodeRulesDefinition.getRepositoryKeyForLanguage(ShellLanguage.KEY), rule);
final RuleKey ruleKeyF77 = RuleKey.of(ICodeRulesDefinition.getRepositoryKeyForLanguage(Fortran77Language.KEY), rule);
final RuleKey ruleKeyF90 = RuleKey.of(ICodeRulesDefinition.getRepositoryKeyForLanguage(Fortran90Language.KEY), rule);
return activeRules.find(ruleKeyShell)!=null || activeRules.find(ruleKeyF77)!=null || activeRules.find(ruleKeyF90)!=null;
}
}