Skip to content

Commit c78915c

Browse files
authored
Merge pull request #348 from h3xstream/spotbugs
SARIF support for Github action : Add the option to transform relative path to complete path
2 parents 190dd4b + 92dedf3 commit c78915c

File tree

5 files changed

+303
-14
lines changed

5 files changed

+303
-14
lines changed

src/it/sarif-2/invoker.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
invoker.goals = clean compile compiler:testCompile site
2+
3+
# The expected result of the build, possible values are "success" (default) and "failure"
4+
invoker.buildResult = success

src/it/sarif-2/pom.xml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright (C) 2006-2020 the original author or authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
https://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
20+
<modelVersion>4.0.0</modelVersion>
21+
<parent>
22+
<groupId>spotbugs-maven-plugin.it</groupId>
23+
<artifactId>common</artifactId>
24+
<version>testing</version>
25+
<relativePath>../common.xml</relativePath>
26+
</parent>
27+
<artifactId>sarif-2</artifactId>
28+
<name>sarif-2</name>
29+
<packaging>jar</packaging>
30+
<reporting>
31+
<excludeDefaults>true</excludeDefaults>
32+
<plugins>
33+
<plugin>
34+
<groupId>org.apache.maven.plugins</groupId>
35+
<artifactId>maven-jxr-plugin</artifactId>
36+
<version>@jxrPluginVersion@</version>
37+
</plugin>
38+
<plugin>
39+
<groupId>com.github.spotbugs</groupId>
40+
<artifactId>spotbugs-maven-plugin</artifactId>
41+
<configuration>
42+
<sarifOutput>true</sarifOutput> <!-- SARIF is enabled here -->
43+
<sarifFullPath>true</sarifFullPath> <!-- Full path expansion enabled -->
44+
<includeTests>true</includeTests>
45+
</configuration>
46+
</plugin>
47+
</plugins>
48+
</reporting>
49+
</project>

src/it/sarif-2/verify.groovy

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (C) 2006-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import groovy.json.JsonSlurper
18+
19+
def effortLevel = 'default'
20+
21+
22+
File spotbugSarifFile = new File(basedir, 'target/spotbugsSarif.json')
23+
assert spotbugSarifFile.exists()
24+
25+
26+
println '**********************************'
27+
println "Checking SARIF file"
28+
println '**********************************'
29+
30+
31+
def String normalizePath(String path) {
32+
return path.replaceAll("\\\\","/");
33+
}
34+
35+
def slurpedResult = new JsonSlurper().parse(spotbugSarifFile)
36+
37+
def results = slurpedResult.runs.results[0]
38+
39+
for (result in slurpedResult.runs.results[0]) {
40+
41+
for (loc in result.locations) {
42+
String location = normalizePath(loc.physicalLocation.artifactLocation.uri)
43+
//Making sure that the path was expanded
44+
assert location.contains("src/it-src/test/java") || location.contains("src/java") : "$location does not contain 'src/it-src/test/java'"
45+
}
46+
}
47+
48+
49+
println "BugInstance size is ${results.size()}"
50+
51+
52+
assert results.size() > 0
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package org.codehaus.mojo.spotbugs
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* https://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import org.apache.maven.execution.MavenSession
23+
import org.apache.maven.model.Resource
24+
import org.apache.maven.project.MavenProject
25+
26+
import java.nio.file.Paths
27+
28+
/**
29+
* Utility class used to transform path relative to the <b>source directory</b>
30+
* to their path relative to the <b>root directory</b>.
31+
*/
32+
class SourceFileIndexer {
33+
34+
/**
35+
* List of source files found in the current Maven project
36+
*/
37+
private List<String> allSourceFiles;
38+
39+
/**
40+
* Initialize the complete list of source files with their
41+
*
42+
* @param project Reference to the Maven project to get the list of source directories
43+
* @param session Reference to the Maven session used to get the location of the root directory
44+
*/
45+
protected void buildListSourceFiles(MavenProject project, MavenSession session) {
46+
47+
//String basePath = project.basedir.absolutePath
48+
String basePath = normalizePath(session.getExecutionRootDirectory())
49+
50+
def allSourceFiles = new ArrayList<>()
51+
//Resource
52+
for(Resource r in project.getResources()) {
53+
scanDirectory(new File(r.directory), allSourceFiles, basePath)
54+
}
55+
for(Resource r in project.getTestResources()) {
56+
scanDirectory(new File(r.directory), allSourceFiles, basePath)
57+
}
58+
//Source files
59+
for(String sourceRoot in project.getCompileSourceRoots()) {
60+
scanDirectory(new File(sourceRoot), allSourceFiles, basePath)
61+
}
62+
for(String sourceRoot in project.getTestCompileSourceRoots()) {
63+
scanDirectory(new File(sourceRoot), allSourceFiles, basePath)
64+
}
65+
66+
for(String sourceRoot in project.getScriptSourceRoots()) {
67+
scanDirectory(new File(sourceRoot), allSourceFiles, basePath)
68+
}
69+
70+
//While not perfect, add the following paths will add basic support for Kotlin and Groovy
71+
scanDirectory(new File(project.getBasedir(),"src/main/webapp"), allSourceFiles, basePath)
72+
scanDirectory(new File(project.getBasedir(),"src/main/groovy"), allSourceFiles, basePath)
73+
scanDirectory(new File(project.getBasedir(),"src/main/kotlin"), allSourceFiles, basePath)
74+
75+
this.allSourceFiles = allSourceFiles
76+
}
77+
78+
/**
79+
* Recursively scan the directory given and add all files found to the files array list.
80+
* The base directory will be truncated from the path stored.
81+
*
82+
* @param directory Directory to scan
83+
* @param files ArrayList where files found will be stored
84+
* @param baseDirectory This part will be truncated from path stored
85+
* @throws IOException
86+
*/
87+
private void scanDirectory(File directory,List<String> files,String baseDirectory) throws IOException {
88+
89+
if(directory.exists()) {
90+
for(File child : directory.listFiles()) {
91+
if(child.isDirectory()) {
92+
scanDirectory(child,files,baseDirectory);
93+
}
94+
else {
95+
String newSourceFile = normalizePath(child.canonicalPath)
96+
if(newSourceFile.startsWith(baseDirectory)) {
97+
//The project will not be at the root of our file system.
98+
//It will most likely be stored in a work directory.
99+
// /work/project-code-to-scan/src/main/java/File.java => src/main/java/File.java
100+
// (Here baseDirectory is /work/project-code-to-scan/)
101+
String relativePath = Paths.get(baseDirectory).relativize(Paths.get(newSourceFile))
102+
files.add(normalizePath(relativePath))
103+
}
104+
else {
105+
//Use the full path instead:
106+
//This will occurs in many cases including when the pom.xml is
107+
// not in the same directory tree as the sources.
108+
files.add(newSourceFile)
109+
}
110+
}
111+
}
112+
}
113+
}
114+
115+
/**
116+
* Normalize path to use forward slash.
117+
* This will facilitate searches.
118+
*
119+
* @param path Path to clean up
120+
* @return Path safe to use for comparison
121+
*/
122+
private String normalizePath(String path) {
123+
return path.replaceAll("\\\\","/");
124+
}
125+
126+
/**
127+
* Transform partial path to complete path
128+
*
129+
* @param filename Partial name to search
130+
* @return Complete path found. Null is not found!
131+
*/
132+
protected String searchActualFilesLocation(String filename) {
133+
134+
if(allSourceFiles == null) {
135+
throw new RuntimeException("Source files cache must be built prior to searches.")
136+
}
137+
138+
for(String fileFound in allSourceFiles) {
139+
140+
if(fileFound.endsWith(filename)) {
141+
return fileFound
142+
}
143+
144+
}
145+
146+
return null //Not found
147+
}
148+
}

src/main/groovy/org/codehaus/mojo/spotbugs/SpotBugsMojo.groovy

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package org.codehaus.mojo.spotbugs
1919
* under the License.
2020
*/
2121

22+
import groovy.json.JsonBuilder
23+
2224
import groovy.json.JsonSlurper
2325
import groovy.xml.XmlSlurper
2426
import groovy.xml.StreamingMarkupBuilder
@@ -28,7 +30,6 @@ import org.apache.maven.doxia.siterenderer.Renderer
2830
import org.apache.maven.doxia.tools.SiteTool
2931

3032
import org.apache.maven.execution.MavenSession
31-
3233
import org.apache.maven.plugin.MojoExecutionException
3334

3435
import org.apache.maven.plugins.annotations.Component
@@ -78,6 +79,9 @@ class SpotBugsMojo extends AbstractMavenReport implements SpotBugsPluginsTrait {
7879
@Parameter(defaultValue = "false", property = "spotbugs.sarifOutput", required = true)
7980
boolean sarifOutput
8081

82+
@Parameter(defaultValue = "false", property = "spotbugs.sarifFullPath", required = true)
83+
boolean sarifFullPath
84+
8185
/**
8286
* Specifies the directory where the xml output will be generated.
8387
*
@@ -1040,13 +1044,14 @@ class SpotBugsMojo extends AbstractMavenReport implements SpotBugsPluginsTrait {
10401044
long startTime, duration
10411045

10421046
File xmlTempFile = new File("${project.build.directory}/spotbugsTemp.xml")
1043-
File sarifFile = new File("${project.build.directory}/spotbugsSarif.json")
1047+
File sarifTempFile = new File("${project.build.directory}/spotbugsTempSarif.json")
1048+
File sarifFinalFile = new File("${project.build.directory}/spotbugsSarif.json")
10441049

10451050
if (xmlOutput || !sarifOutput) {
10461051
forceFileCreation(xmlTempFile)
10471052
}
10481053
else {
1049-
forceFileCreation(sarifFile)
1054+
forceFileCreation(sarifTempFile)
10501055
}
10511056

10521057

@@ -1064,7 +1069,7 @@ class SpotBugsMojo extends AbstractMavenReport implements SpotBugsPluginsTrait {
10641069
log.debug("Plugin Artifacts to be added -> ${pluginArtifacts.toString()}")
10651070
log.debug("outputFile is ${outputFile.getCanonicalPath()}")
10661071
log.debug("output Directory is ${spotbugsXmlOutputDirectory.getAbsolutePath()}")
1067-
log.debug("TempFile is ${(sarifOutput ? sarifFile : xmlTempFile).getCanonicalPath()}");
1072+
log.debug("TempFile is ${(sarifOutput ? sarifTempFile : xmlTempFile).getCanonicalPath()}");
10681073
}
10691074

10701075
def ant = new AntBuilder()
@@ -1075,7 +1080,7 @@ class SpotBugsMojo extends AbstractMavenReport implements SpotBugsPluginsTrait {
10751080
startTime = System.nanoTime()
10761081
}
10771082

1078-
def spotbugsArgs = !sarifOutput ? getSpotbugsArgs(xmlTempFile) : getSpotbugsArgs(sarifFile)
1083+
def spotbugsArgs = !sarifOutput ? getSpotbugsArgs(xmlTempFile) : getSpotbugsArgs(sarifTempFile)
10791084

10801085
def effectiveEncoding = System.getProperty("file.encoding", "UTF-8")
10811086

@@ -1193,19 +1198,50 @@ class SpotBugsMojo extends AbstractMavenReport implements SpotBugsPluginsTrait {
11931198

11941199
}
11951200

1196-
if(sarifFile && sarifOutput) {
1197-
if (sarifFile.size() > 0) {
1198-
def path = new JsonSlurper().parse(sarifFile)
1201+
if(sarifTempFile && sarifOutput && sarifTempFile.size() > 0) {
11991202

1200-
try {
1203+
def slurpedResult = new JsonSlurper().parse(sarifTempFile)
1204+
def builder = new JsonBuilder(slurpedResult)
1205+
1206+
// With -Dspotbugs.sarifFullPath=true
1207+
// The location uri will be replace by path relative to the root of project
1208+
// SomeFile.java => src/main/java/SomeFile.java
1209+
//This change is required for some tool including Github code scanning API
1210+
if (sarifFullPath) {
1211+
1212+
def indexer = new SourceFileIndexer()
1213+
1214+
indexer.buildListSourceFiles(getProject(),getSession())
1215+
1216+
for (result in slurpedResult.runs.results[0]) {
1217+
1218+
for (loc in result.locations) {
1219+
def originalFullPath = loc.physicalLocation.artifactLocation.uri
1220+
1221+
//We replace relative path to the complete path
1222+
String newFileName = indexer.searchActualFilesLocation(originalFullPath)
1223+
1224+
if (newFileName != null) {
1225+
if(getLog().isDebugEnabled()) {
1226+
getLog().info("$originalFullPath modified to $newFileName")
1227+
}
1228+
loc.physicalLocation.artifactLocation.uri = newFileName
1229+
} else {
1230+
getLog().warn("No source file found for $originalFullPath. " +
1231+
"The path include in the SARIF report could be incomplete.")
1232+
}
1233+
}
12011234

1202-
def results = path.runs.results[0]
1203-
log.debug("BugInstance size is ${results.size()}")
1204-
}
1205-
catch (Exception e) {
1206-
log.error("Unexpected format for the file $sarifFile",e)
12071235
}
12081236
}
1237+
1238+
sarifFinalFile.withWriter {
1239+
builder.writeTo(it)
1240+
}
1241+
1242+
if (!log.isDebugEnabled()) {
1243+
sarifTempFile.delete()
1244+
}
12091245
}
12101246

12111247
}

0 commit comments

Comments
 (0)