首先贴一下findbugs plugin 代码目录:
代码:
代码里面有很几个关于plugin定义的关键类:
language包下面定义了扫描jsp 的思路:
public class Jsp
extends AbstractLanguage
{
public static final String KEY = "jsp";
public static final String NAME = "JSP";
public static final String FILE_SUFFIXES_KEY = "sonar.jsp.file.suffixes";
public static final String DEFAULT_FILE_SUFFIXES = ".jsp";
private final Settings settings;
public Jsp(Settings settings)
{
super("jsp", "JSP");
this.settings = settings;
}
public String[] getFileSuffixes()
{
String[] suffixes = filterEmptyStrings(this.settings.getStringArray("sonar.jsp.file.suffixes"));
if (suffixes.length == 0) {
suffixes = StringUtils.split(".jsp", ",");
}
return suffixes;
}
private static String[] filterEmptyStrings(String[] stringArray)
{
List nonEmptyStrings = Lists.newArrayList();
for (String string : stringArray) {
if (StringUtils.isNotBlank(string.trim())) {
nonEmptyStrings.add(string.trim());
}
}
return (String[])nonEmptyStrings.toArray(new String[nonEmptyStrings.size()]);
}
}
JspCodeColorizerFormat 类定义了如何识别标签扫描jsp
public class JspCodeColorizerFormat
extends CodeColorizerFormat
{
private final List tokenizers = new ArrayList();
public JspCodeColorizerFormat()
{
super("jsp");
String tagAfter = "";
this.tokenizers.add(new RegexpTokenizer("", tagAfter, "?[:\\w]+>?"));
this.tokenizers.add(new RegexpTokenizer("", tagAfter, ">"));
this.tokenizers.add(new RegexpTokenizer("", tagAfter, ""));
this.tokenizers.add(new MultilinesDocTokenizer("", "", tagAfter));
this.tokenizers.add(new MultilinesDocTokenizer("<%--", "--%>", "", tagAfter));
this.tokenizers.add(new MultilinesDocTokenizer("<%@", "%>", "", tagAfter));
this.tokenizers.add(new MultilinesDocTokenizer("<%", "%>", "", tagAfter));
this.tokenizers.add(new StringTokenizer("", tagAfter));
}
public List getTokenizers()
{
return this.tokenizers;
}
}
profiles 包中定义了规则相关:
FindBugs + FB-Contrib 规则,针对语言java
public class FindbugsContribProfile
extends ProfileDefinition
{
private static final String FB_CONTRIB_PROFILE_NAME = "FindBugs + FB-Contrib";
private final FindbugsProfileImporter importer;
public FindbugsContribProfile(FindbugsProfileImporter importer)
{
this.importer = importer;
}
public RulesProfile createProfile(ValidationMessages messages)
{
Reader findbugsProfile = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/plugins/findbugs/profile-findbugs-and-fb-contrib.xml"));
RulesProfile profile = this.importer.importProfile(findbugsProfile, messages);
profile.setLanguage("java");
profile.setName("FindBugs + FB-Contrib");
return profile;
}
}
定义FindBugs 规则,针对语言java
public class FindbugsProfile
extends ProfileDefinition
{
private static final String FINDBUGS_PROFILE_NAME = "FindBugs";
private final FindbugsProfileImporter importer;
public FindbugsProfile(FindbugsProfileImporter importer)
{
this.importer = importer;
}
public RulesProfile createProfile(ValidationMessages messages)
{
Reader findbugsProfile = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/plugins/findbugs/profile-findbugs-only.xml"));
RulesProfile profile = this.importer.importProfile(findbugsProfile, messages);
profile.setLanguage("java");
profile.setName("FindBugs");
return profile;
}
}
定义了规则FindBugs Security Audit,针对语言java
public class FindbugsSecurityAuditProfile
extends ProfileDefinition
{
private static final String FINDBUGS_SECURITY_AUDIT_PROFILE_NAME = "FindBugs Security Audit";
private final FindbugsProfileImporter importer;
public FindbugsSecurityAuditProfile(FindbugsProfileImporter importer)
{
this.importer = importer;
}
public RulesProfile createProfile(ValidationMessages messages)
{
Reader findbugsProfile = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/plugins/findbugs/profile-findbugs-security-audit.xml"));
RulesProfile profile = this.importer.importProfile(findbugsProfile, messages);
profile.setLanguage("java");
profile.setName("FindBugs Security Audit");
return profile;
}
}
定义检查规则FindBugs Security JSP 针对语言:jsp
public class FindbugsSecurityJspProfile
extends ProfileDefinition
{
private static final String FINDBUGS_SECURITY_JSP_PROFILE_NAME = "FindBugs Security JSP";
private final FindbugsProfileImporter importer;
public FindbugsSecurityJspProfile(FindbugsProfileImporter importer)
{
this.importer = importer;
}
public RulesProfile createProfile(ValidationMessages messages)
{
Reader findbugsProfile = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/plugins/findbugs/profile-findbugs-security-jsp.xml"));
RulesProfile profile = this.importer.importProfile(findbugsProfile, messages);
profile.setLanguage("jsp");
profile.setName("FindBugs Security JSP");
return profile;
}
定义规则:FindBugs Security Minimal 针对语言java
public class FindbugsSecurityMinimalProfile
extends ProfileDefinition
{
private static final String FINDBUGS_SECURITY_AUDIT_PROFILE_NAME = "FindBugs Security Minimal";
private final FindbugsProfileImporter importer;
public FindbugsSecurityMinimalProfile(FindbugsProfileImporter importer)
{
this.importer = importer;
}
public RulesProfile createProfile(ValidationMessages messages)
{
Reader findbugsProfile = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/plugins/findbugs/profile-findbugs-security-minimal.xml"));
RulesProfile profile = this.importer.importProfile(findbugsProfile, messages);
profile.setLanguage("java");
profile.setName("FindBugs Security Minimal");
return profile;
}
}
resource包下面定义的类,是加载扫描jsp 相关java类
public class ByteCodeResourceLocator
implements BatchExtension
{
private static final Logger LOG = LoggerFactory.getLogger(ByteCodeResourceLocator.class);
private static final String[] SOURCE_DIRECTORIES = { "src/main/java", "src/main/webapp", "src/main/resources", "src", "/src/java" };
public InputFile findJavaClassFile(String className, FileSystem fs)
{
int indexDollarSign = className.indexOf("$");
if (indexDollarSign != -1) {
className = className.substring(0, indexDollarSign);
}
return buildInputFile(className.replaceAll("\\.", "/") + ".java", fs);
}
public InputFile findJavaOuterClassFile(String className, File classFile, FileSystem fs)
{
try
{
InputStream in = new FileInputStream(classFile);Throwable localThrowable4 = null;
try
{
DebugExtensionExtractor debug = new DebugExtensionExtractor();
String source = debug.getDebugSourceFromClass(in);
if (source == null) {
return null;
}
String newClassName = FilenameUtils.getBaseName(source);
String packagePrefix = className.lastIndexOf(".") != -1 ? FilenameUtils.getBaseName(className) + "." : "";
String fullClassName = packagePrefix + newClassName;
return findJavaClassFile(fullClassName, fs);
}
catch (Throwable localThrowable2)
{
localThrowable4 = localThrowable2;throw localThrowable2;
}
finally
{
if (in != null) {
if (localThrowable4 != null) {
try
{
in.close();
}
catch (Throwable localThrowable3)
{
localThrowable4.addSuppressed(localThrowable3);
}
} else {
in.close();
}
}
}
return null;
}
catch (IOException e)
{
LOG.warn("An error occurs while opening classfile : " + classFile.getPath());
}
}
public InputFile findTemplateFile(String className, FileSystem fs)
{
List potentialJspFilenames = new ArrayList();
if (className.startsWith("jsp_servlet"))
{
String jspFile = className.substring(11).replaceFirst("\\.__([^\\.]+)$", "/$1\\.jsp").replace("._", "/");
potentialJspFilenames.add(jspFile);
}
String jspFileFromClass;
if (className.endsWith("_jsp"))
{
jspFileFromClass = JasperUtils.decodeJspClassName(className);
potentialJspFilenames.add(jspFileFromClass);
for (String packageName : Arrays.asList(new String[] { "jsp/", "org/apache/jsp/" })) {
if (jspFileFromClass.startsWith(packageName)) {
potentialJspFilenames.add(jspFileFromClass.substring(packageName.length()));
}
}
}
for (String jspFilename : potentialJspFilenames)
{
InputFile file = buildInputFile(jspFilename, fs);
if (file != null) {
return file;
}
}
return null;
}
public InputFile buildInputFile(String fileName, FileSystem fs)
{
for (String sourceDir : SOURCE_DIRECTORIES)
{
Iterable files = fs.inputFiles(fs.predicates().hasRelativePath(sourceDir + "/" + fileName));
Iterator localIterator = files.iterator();
if (localIterator.hasNext())
{
InputFile f = (InputFile)localIterator.next();
return f;
}
}
return null;
}
@Nullable
public SmapParser.SmapLocation extractSmapLocation(String className, int originalLine, File classFile)
{
String smap;
try
{
InputStream in = new FileInputStream(classFile);Throwable localThrowable7 = null;
try
{
DebugExtensionExtractor debug = new DebugExtensionExtractor();
smap = debug.getDebugExtFromClass(in);
if (smap != null) {
return getJspLineNumberFromSmap(smap, Integer.valueOf(originalLine));
}
}
catch (Throwable localThrowable2)
{
localThrowable7 = localThrowable2;throw localThrowable2;
}
finally
{
if (in != null) {
if (localThrowable7 != null) {
try
{
in.close();
}
catch (Throwable localThrowable3)
{
localThrowable7.addSuppressed(localThrowable3);
}
} else {
in.close();
}
}
}
}
catch (IOException e)
{
LOG.warn("An error occurs while opening classfile : " + classFile.getPath());
}
LOG.debug("No smap file found for the class: " + className);
File smapFile = new File(classFile.getPath() + ".smap");
if (smapFile.exists()) {
try
{
Object smapInputStream = new FileInputStream(smapFile);localThrowable2 = null;
try
{
return getJspLineNumberFromSmap(IOUtils.toString((InputStream)smapInputStream), Integer.valueOf(originalLine));
}
catch (Throwable localThrowable5)
{
localThrowable2 = localThrowable5;throw localThrowable5;
}
finally
{
if (smapInputStream != null) {
if (localThrowable2 != null) {
try
{
((InputStream)smapInputStream).close();
}
catch (Throwable localThrowable6)
{
localThrowable2.addSuppressed(localThrowable6);
}
} else {
((InputStream)smapInputStream).close();
}
}
}
LOG.debug("No smap mapping found.");
}
catch (IOException e)
{
LOG.debug("Unable to open smap file : " + smapFile.getAbsolutePath());
}
}
return null;
}
private SmapParser.SmapLocation getJspLineNumberFromSmap(String smap, Integer originalLine)
throws IOException
{
SmapParser parser = new SmapParser(smap);
return parser.getSmapLocation(originalLine);
}
}
public class SmapParser
{
private String javaFilename;
private final Map fileinfo = new HashMap();
private final Map java2jsp = new HashMap();
private static final Pattern LINE_INFO_PATTERN = Pattern.compile("([0-9]+)(?:#([0-9]+))?(?:,([0-9]+))?:([0-9]+)(?:,([0-9]+))?");
private static String getLine(BufferedReader reader)
throws IOException
{
String s = reader.readLine();
if (s == null) {
throw new IOException("EOF parsing SMAP");
}
return s;
}
public SmapParser(String smap)
throws IOException
{
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(smap.getBytes())));
String header = getLine(reader);
this.javaFilename = getLine(reader);
String jsp = getLine(reader);
String stratum = getLine(reader);
String f = getLine(reader);
if ((!header.equals("SMAP")) || (!stratum.startsWith("*S ")) || (!f.equals("*F"))) {
throw new IllegalArgumentException("Unexpected SMAP file format");
}
String line;
while (((line = getLine(reader)) != null) && (!line.equals("*L")))
{
String path = null;
if (line.startsWith("+ "))
{
path = getLine(reader);
line = line.substring(2);
}
int pos = line.indexOf(" ");
int fileNum = Integer.parseInt(line.substring(0, pos));
String name = line.substring(pos + 1);
this.fileinfo.put(Integer.valueOf(fileNum), new FileInfo(name, path == null ? name : path));
}
int lastLFI = 0;
while (((line = getLine(reader)) != null) && (!line.equals("*E"))) {
if (!line.startsWith("*"))
{
Matcher m = LINE_INFO_PATTERN.matcher(line);
if (!m.matches()) {
throw new IllegalArgumentException(line);
}
int inputStartLine = Integer.parseInt(m.group(1));
int lineFileID = m.group(2) == null ? lastLFI : Integer.parseInt(m.group(2));
int repeatCount = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
int outputStartLine = Integer.parseInt(m.group(4));
int outputLineIncrement = m.group(5) == null ? 1 : Integer.parseInt(m.group(5));
for (int i = 0; i < repeatCount; i++)
{
int[] inputMapping = { lineFileID, inputStartLine + i };
int baseOL = outputStartLine + i * outputLineIncrement;
for (int ol = baseOL; ol < baseOL + outputLineIncrement; ol++) {
if (!this.java2jsp.containsKey(Integer.valueOf(ol))) {
this.java2jsp.put(Integer.valueOf(ol), inputMapping);
}
}
}
lastLFI = lineFileID;
}
}
}
public String getJavaFilename()
{
return this.javaFilename;
}
public String getScriptFilename(int fileIndex)
{
FileInfo f = (FileInfo)this.fileinfo.get(Integer.valueOf(fileIndex));
return f.name;
}
public int[] getScriptLineNumber(Integer lineNo)
{
return (int[])this.java2jsp.get(lineNo);
}
public SmapLocation getSmapLocation(Integer lineNo)
{
int[] origSource = (int[])this.java2jsp.get(lineNo);
FileInfo info = (FileInfo)this.fileinfo.get(Integer.valueOf(origSource[0]));
return new SmapLocation(info, origSource[1], origSource[0] == 0);
}
public static class FileInfo
{
public final String name;
public final String path;
public FileInfo(String name, String path)
{
this.name = name;
this.path = path;
}
}
public static class SmapLocation
{
public final SmapParser.FileInfo fileInfo;
public final int line;
public final boolean isPrimaryFile;
public SmapLocation(SmapParser.FileInfo fileInfo, int line, boolean isPrimaryFile)
{
this.fileInfo = fileInfo;
this.line = line;
this.isPrimaryFile = isPrimaryFile;
}
}
}
public static String decodeJspClassName(String className)
{
className = className.replaceAll("\\.", "/");
for (char ch = '\000'; ch < ''; ch = (char)(ch + '\001')) {
if (((isPrintableChar(ch)) && (!Character.isJavaIdentifierPart(ch))) || (ch == '_')) {
className = className.replace(mangleChar(ch), "" + ch);
}
}
return className.replaceAll("_jsp", ".jsp");
}
public static final String mangleChar(char ch)
{
char[] result = new char[5];
result[0] = '_';
result[1] = Character.forDigit(ch >> '\f' & 0xF, 16);
result[2] = Character.forDigit(ch >> '\b' & 0xF, 16);
result[3] = Character.forDigit(ch >> '\004' & 0xF, 16);
result[4] = Character.forDigit(ch & 0xF, 16);
return new String(result);
}
public static boolean isPrintableChar(char c)
{
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
return (!Character.isISOControl(c)) && (c != 65535) && (block != null) && (block != Character.UnicodeBlock.SPECIALS);
}
}
xml 包中定义一些规则相关的bean
FindbugsPlugin 定义类:
public class FindbugsPlugin
implements Plugin
{
public void define(Plugin.Context context)
{
context.addExtensions(FindbugsConfiguration.getPropertyDefinitions());
context.addExtensions(Arrays.asList(new Class[] { Jsp.class, JspCodeColorizerFormat.class, FindbugsSensor.class, FindbugsConfiguration.class, FindbugsExecutor.class, FindbugsProfileExporter.class, FindbugsProfileImporter.class, FindbugsProfile.class, FindbugsContribProfile.class, FindbugsSecurityAuditProfile.class, FindbugsSecurityMinimalProfile.class, FindbugsSecurityJspProfile.class, FindbugsRulesDefinition.class, FbContribRulesDefinition.class, FindSecurityBugsRulesDefinition.class, FindSecurityBugsJspRulesDefinition.class, ByteCodeResourceLocator.class }));
}
}
加载各种定义的 profile类,然后就是FindbugsProfileExporter和FindbugsProfileImporter,规则文件的到处生产和规则文件的导入类:
FindbugsProfileExporter类:
public class FindbugsProfileExporter
extends ProfileExporter
{
public FindbugsProfileExporter()
{
super("findbugs", "FindBugs");
setSupportedLanguages(new String[] { "java", "jsp" });
setMimeType("application/xml");
}
public void exportProfile(RulesProfile profile, Writer writer)
{
try
{
FindBugsFilter filter = buildFindbugsFilter(Iterables.concat(profile
.getActiveRulesByRepository("findbugs"), profile
.getActiveRulesByRepository("fb-contrib"), profile
.getActiveRulesByRepository("findsecbugs"), profile
.getActiveRulesByRepository("findsecbugs-jsp")));
XStream xstream = FindBugsFilter.createXStream();
writer.append("\n\n".concat(xstream.toXML(filter)));
}
catch (IOException e)
{
throw new SonarException("Fail to export the Findbugs profile : " + profile, e);
}
}
public static FindBugsFilter buildFindbugsFilter(Iterable activeRules)
{
FindBugsFilter root = new FindBugsFilter();
for (ActiveRule activeRule : activeRules) {
if (("findbugs".equals(activeRule.getRepositoryKey())) ||
("fb-contrib".equals(activeRule.getRepositoryKey())) ||
("findsecbugs".equals(activeRule.getRepositoryKey())) ||
("findsecbugs-jsp".equals(activeRule.getRepositoryKey())))
{
Match child = new Match();
child.setBug(new Bug(activeRule.getConfigKey()));
root.addMatch(child);
}
}
return root;
}
}
FindbugsProfileImporter规则导入类:
public class FindbugsProfileImporter
extends ProfileImporter
{
private final RuleFinder ruleFinder;
private static final Logger LOG = LoggerFactory.getLogger(FindbugsProfileImporter.class);
public FindbugsProfileImporter(RuleFinder ruleFinder)
{
super("findbugs", "FindBugs");
setSupportedLanguages(new String[] { "java", "jsp" });
this.ruleFinder = ruleFinder;
}
public RulesProfile importProfile(Reader findbugsConf, ValidationMessages messages)
{
RulesProfile profile = RulesProfile.create();
try
{
XStream xStream = FindBugsFilter.createXStream();
FindBugsFilter filter = (FindBugsFilter)xStream.fromXML(findbugsConf);
activateRulesByCategory(profile, filter, messages);
activateRulesByCode(profile, filter, messages);
activateRulesByPattern(profile, filter, messages);
return profile;
}
catch (Exception e)
{
String errorMessage = "The Findbugs configuration file is not valid";
messages.addErrorText(errorMessage + " : " + e.getMessage());
LOG.error(errorMessage, e);
}
return profile;
}
private void activateRulesByPattern(RulesProfile profile, FindBugsFilter filter, ValidationMessages messages)
{
for (Map.Entry patternLevel : filter.getPatternLevels(new FindbugsLevelUtils()).entrySet())
{
Rule rule = this.ruleFinder.findByKey("findbugs", (String)patternLevel.getKey());
if (rule == null)
{
rule = this.ruleFinder.findByKey("fb-contrib", (String)patternLevel.getKey());
if (rule == null)
{
rule = this.ruleFinder.findByKey("findsecbugs", (String)patternLevel.getKey());
if (rule == null) {
rule = this.ruleFinder.findByKey("findsecbugs-jsp", (String)patternLevel.getKey());
}
}
}
if (rule != null) {
profile.activateRule(rule, getPriorityFromSeverity((String)patternLevel.getValue()));
} else {
messages.addWarningText("Unable to activate unknown rule : '" + (String)patternLevel.getKey() + "'");
}
}
}
private void activateRulesByCode(RulesProfile profile, FindBugsFilter filter, ValidationMessages messages)
{
for (Map.Entry codeLevel : filter.getCodeLevels(new FindbugsLevelUtils()).entrySet())
{
boolean someRulesHaveBeenActivated = false;
for (Rule rule : rules()) {
if ((rule.getKey().equals(codeLevel.getKey())) || (StringUtils.startsWith(rule.getKey(), (String)codeLevel.getKey() + "_")))
{
someRulesHaveBeenActivated = true;
profile.activateRule(rule, getPriorityFromSeverity((String)codeLevel.getValue()));
}
}
if (!someRulesHaveBeenActivated) {
messages.addWarningText("Unable to find any rules associated to code : '" + (String)codeLevel.getKey() + "'");
}
}
}
private void activateRulesByCategory(RulesProfile profile, FindBugsFilter filter, ValidationMessages messages)
{
for (Map.Entry categoryLevel : filter.getCategoryLevels(new FindbugsLevelUtils()).entrySet())
{
boolean someRulesHaveBeenActivated = false;
String sonarCateg = FindbugsCategory.findbugsToSonar((String)categoryLevel.getKey());
for (Rule rule : rules()) {
if ((sonarCateg != null) && (rule.getName().startsWith(sonarCateg)))
{
someRulesHaveBeenActivated = true;
profile.activateRule(rule, getPriorityFromSeverity((String)categoryLevel.getValue()));
}
}
if (!someRulesHaveBeenActivated) {
messages.addWarningText("Unable to find any rules associated to category : '" + (String)categoryLevel.getKey() + "'");
}
}
}
private static RulePriority getPriorityFromSeverity(String severity)
{
if ("INFO".equals(severity)) {
return RulePriority.INFO;
}
if ("MAJOR".equals(severity)) {
return RulePriority.MAJOR;
}
if ("BLOCKER".equals(severity)) {
return RulePriority.BLOCKER;
}
return null;
}
private Iterable rules()
{
return Iterables.concat(this.ruleFinder
.findAll(RuleQuery.create().withRepositoryKey("findbugs")), this.ruleFinder
.findAll(RuleQuery.create().withRepositoryKey("fb-contrib")), this.ruleFinder
.findAll(RuleQuery.create().withRepositoryKey("findsecbugs")), this.ruleFinder
.findAll(RuleQuery.create().withRepositoryKey("findsecbugs-jsp")));
}
}
然后加载findbugsSensor类:findbugsSensor定义了,各个profile的引入和扫描jsp文件以及对应的java 的class文件。
package org.sonar.plugins.findbugs;
import java.io.File;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.rule.ActiveRule;
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.profiles.RulesProfile;
import org.sonar.plugins.findbugs.resource.ByteCodeResourceLocator;
import org.sonar.plugins.findbugs.resource.ClassMetadataLoadingException;
import org.sonar.plugins.findbugs.resource.SmapParser.FileInfo;
import org.sonar.plugins.findbugs.resource.SmapParser.SmapLocation;
import org.sonar.plugins.java.api.JavaResourceLocator;
public class FindbugsSensor
implements Sensor
{
private static final Logger LOG = LoggerFactory.getLogger(FindbugsSensor.class);
private RulesProfile profile;
private ActiveRules ruleFinder;
private FindbugsExecutor executor;
private final JavaResourceLocator javaResourceLocator;
private final ByteCodeResourceLocator byteCodeResourceLocator;
private final FileSystem fs;
private final SensorContext sensorContext;
public FindbugsSensor(RulesProfile profile, ActiveRules ruleFinder, SensorContext sensorContext, FindbugsExecutor executor, JavaResourceLocator javaResourceLocator, FileSystem fs, ByteCodeResourceLocator byteCodeResourceLocator)
{
this.profile = profile;
this.ruleFinder = ruleFinder;
this.sensorContext = sensorContext;
this.executor = executor;
this.javaResourceLocator = javaResourceLocator;
this.byteCodeResourceLocator = byteCodeResourceLocator;
this.fs = fs;
}
private boolean hasActiveFindbugsRules()
{
return !this.profile.getActiveRulesByRepository("findbugs").isEmpty();
}
private boolean hasActiveFbContribRules()
{
return !this.profile.getActiveRulesByRepository("fb-contrib").isEmpty();
}
private boolean hasActiveFindSecBugsRules()
{
return !this.profile.getActiveRulesByRepository("findsecbugs").isEmpty();
}
private boolean hasActiveFindSecBugsJspRules()
{
return !this.profile.getActiveRulesByRepository("findsecbugs-jsp").isEmpty();
}
public void execute(SensorContext context)
{
if ((!hasActiveFindbugsRules()) && (!hasActiveFbContribRules()) && (!hasActiveFindSecBugsRules()) && (!hasActiveFindSecBugsJspRules())) {
return;
}
Collection collection = this.executor.execute(hasActiveFbContribRules(), (hasActiveFindSecBugsRules()) || (hasActiveFindSecBugsJspRules()));
for (ReportedBug bugInstance : collection) {
try
{
String[] repos = { "findbugs", "fb-contrib", "findsecbugs", "findsecbugs-jsp" };
ActiveRule rule = null;
for (String repoKey : repos)
{
rule = this.ruleFinder.findByInternalKey(repoKey, bugInstance.getType());
if (rule != null) {
break;
}
}
if (rule == null)
{
LOG.warn("Findbugs rule '{}' is not active in Sonar.", bugInstance.getType());
}
else
{
String className = bugInstance.getClassName();
String longMessage = bugInstance.getMessage();
int line = bugInstance.getStartLine();
InputFile resource = null;
resource = this.byteCodeResourceLocator.findJavaClassFile(className, this.fs);
if (resource != null)
{
insertIssue(rule, resource, line, longMessage);
}
else
{
File classFile = findOriginalClassForBug(bugInstance.getClassName());
resource = this.byteCodeResourceLocator.findJavaOuterClassFile(className, classFile, this.fs);
if (resource != null) {
insertIssue(rule, resource, line, longMessage);
} else if (classFile != null) {
try
{
SmapParser.SmapLocation location = this.byteCodeResourceLocator.extractSmapLocation(className, line, classFile);
if (location != null)
{
if (!location.isPrimaryFile) {
continue;
}
resource = this.byteCodeResourceLocator.buildInputFile(location.fileInfo.path, this.fs);
if (resource != null) {
insertIssue(rule, resource, location.line, longMessage);
}
}
else
{
resource = this.byteCodeResourceLocator.findTemplateFile(className, this.fs);
if (resource != null)
{
insertIssue(rule, resource, line, longMessage);
continue;
}
}
}
catch (ClassMetadataLoadingException e)
{
LOG.warn("Failed to load the class file metadata", e);
}
} else {
LOG.warn("The class '" + className + "' could not be matched to its original source file. It might be a dynamically generated class.");
}
}
}
}
catch (Exception e)
{
String bugInstanceDebug = String.format("[BugInstance type=%s, class=%s, line=%s]", new Object[] { bugInstance.getType(), bugInstance.getClassName(), Integer.valueOf(bugInstance.getStartLine()) });
LOG.warn("An error occurs while processing the bug instance " + bugInstanceDebug, e);
}
}
}
protected void insertIssue(ActiveRule rule, InputFile resource, int line, String message)
{
NewIssue newIssue = this.sensorContext.newIssue().forRule(rule.ruleKey());
NewIssueLocation location = newIssue.newLocation().on(resource).at(resource.selectLine(line > 0 ? line : 1)).message(message);
newIssue.at(location);
newIssue.save();
}
private File findOriginalClassForBug(String className)
{
String classFile = className.replaceAll("\\.", "/").concat(".class");
for (File classPath : this.javaResourceLocator.classpath()) {
if (classPath.isDirectory())
{
File testClassFile = new File(classPath, classFile);
if (testClassFile.exists()) {
return testClassFile;
}
}
}
return null;
}
public void describe(SensorDescriptor descriptor)
{
descriptor.onlyOnLanguages(new String[] { "java", "jsp" });
descriptor.name("FindBugs Sensor");
}
}
FindbugsConfiguration类,这个类中定义了findbugs plugin相关工作空间,排除java文件,扫描生成文件等等。
public class FindbugsConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(FindbugsExecutor.class);
private final FileSystem fileSystem;
private final Settings settings;
private final RulesProfile profile;
private final FindbugsProfileExporter exporter;
private final JavaResourceLocator javaResourceLocator;
private File jsr305Lib;
private File annotationsLib;
private File fbContrib;
private File findSecBugs;
public FindbugsConfiguration(FileSystem fileSystem, Settings settings, RulesProfile profile, FindbugsProfileExporter exporter, JavaResourceLocator javaResourceLocator)
{
this.fileSystem = fileSystem;
this.settings = settings;
this.profile = profile;
this.exporter = exporter;
this.javaResourceLocator = javaResourceLocator;
}
public File getTargetXMLReport()
{
return new File(this.fileSystem.workDir(), "findbugs-result.xml");
}
public Project getFindbugsProject()
throws IOException
{
Project findbugsProject = new Project();
for (Iterator localIterator1 = getSourceFiles().iterator(); localIterator1.hasNext();)
{
file = (File)localIterator1.next();
if (FilenameUtils.getExtension(file.getName()).equals("java")) {
findbugsProject.addFile(file.getCanonicalPath());
}
}
File file;
Object classFilesToAnalyze = new ArrayList(this.javaResourceLocator.classFilesToAnalyze());
for (File file : this.javaResourceLocator.classpath())
{
if (file.isDirectory()) {
((List)classFilesToAnalyze).addAll(scanForAdditionalClasses(file));
}
findbugsProject.addAuxClasspathEntry(file.getCanonicalPath());
}
boolean hasJspFiles = this.fileSystem.hasFiles(this.fileSystem.predicates().hasLanguage("jsp"));
boolean hasPrecompiledJsp = false;
for (File classToAnalyze : (List)classFilesToAnalyze)
{
String absolutePath = classToAnalyze.getCanonicalPath();
if ((hasJspFiles) && (!hasPrecompiledJsp) && (
(absolutePath.endsWith("_jsp.class")) ||
(absolutePath.contains("/jsp_servlet/")))) {
hasPrecompiledJsp = true;
}
findbugsProject.addFile(absolutePath);
}
if (((List)classFilesToAnalyze).isEmpty())
{
LOG.warn("Findbugs needs sources to be compiled. Please build project before executing sonar or check the location of compiled classes to make it possible for Findbugs to analyse your project.");
if (hasSourceFiles()) {
throw new IllegalStateException("This project contains Java source files that are not compiled.");
}
}
if ((hasJspFiles) && (!hasPrecompiledJsp)) {
LOG.warn("JSP files were found in the current project but FindBugs requires their precompiled form. For more information on how to configure JSP precompilation : https://github.com/find-sec-bugs/find-sec-bugs/wiki/JSP-precompilation");
}
copyLibs();
if (this.annotationsLib != null)
{
findbugsProject.addAuxClasspathEntry(this.annotationsLib.getCanonicalPath());
findbugsProject.addAuxClasspathEntry(this.jsr305Lib.getCanonicalPath());
}
findbugsProject.setCurrentWorkingDirectory(this.fileSystem.workDir());
return findbugsProject;
}
private Iterable getSourceFiles()
{
FilePredicates pred = this.fileSystem.predicates();
return this.fileSystem.files(pred.and(new FilePredicate[] {pred
.hasType(InputFile.Type.MAIN), pred
.hasLanguage("java"), pred
.not(pred.matchesPathPattern("**/package-info.java")) }));
}
private boolean hasSourceFiles()
{
FilePredicates pred = this.fileSystem.predicates();
return this.fileSystem.hasFiles(pred
.and(new FilePredicate[] {pred
.hasType(InputFile.Type.MAIN), pred
.hasLanguage("java"), pred
.not(pred.matchesPathPattern("**/package-info.java")) }));
}
@VisibleForTesting
File saveIncludeConfigXml()
throws IOException
{
StringWriter conf = new StringWriter();
this.exporter.exportProfile(this.profile, conf);
File file = new File(this.fileSystem.workDir(), "findbugs-include.xml");
FileUtils.write(file, conf.toString(), "UTF-8");
return file;
}
public static List scanForAdditionalClasses(File folder)
throws IOException
{
List allFiles = new ArrayList();
Queue dirs = new LinkedList();
dirs.add(folder);
while (!dirs.isEmpty())
{
File dirPoll = (File)dirs.poll();
if (dirPoll == null) {
break;
}
for (File f : dirPoll.listFiles()) {
if (f.isDirectory()) {
dirs.add(f);
} else if ((f.isFile()) && (f.getName().endsWith(".class"))) {
allFiles.add(f);
}
}
}
return allFiles;
}
@VisibleForTesting
List getExcludesFilters()
{
List result = Lists.newArrayList();
PathResolver pathResolver = new PathResolver();
String[] filters = this.settings.getStringArray("sonar.findbugs.excludesFilters");
for (String excludesFilterPath : filters)
{
excludesFilterPath = StringUtils.trim(excludesFilterPath);
if (StringUtils.isNotBlank(excludesFilterPath)) {
result.add(pathResolver.relativeFile(this.fileSystem.baseDir(), excludesFilterPath));
}
}
return result;
}
public String getEffort()
{
return StringUtils.lowerCase(this.settings.getString("sonar.findbugs.effort"));
}
public String getConfidenceLevel()
{
return StringUtils.lowerCase(this.settings.getString("sonar.findbugs.confidenceLevel"));
}
public long getTimeout()
{
return this.settings.getLong("sonar.findbugs.timeout");
}
public void copyLibs()
{
if (this.jsr305Lib == null) {
this.jsr305Lib = copyLib("/jsr305.jar");
}
if (this.annotationsLib == null) {
this.annotationsLib = copyLib("/annotations.jar");
}
if (this.fbContrib == null) {
this.fbContrib = copyLib("/fb-contrib.jar");
}
if (this.findSecBugs == null) {
this.findSecBugs = copyLib("/findsecbugs-plugin.jar");
}
}
public void stop()
{
if (this.jsr305Lib != null) {
this.jsr305Lib.delete();
}
if (this.annotationsLib != null) {
this.annotationsLib.delete();
}
if (this.fbContrib != null) {
this.fbContrib.delete();
}
if (this.findSecBugs != null) {
this.findSecBugs.delete();
}
}
private File copyLib(String name)
{
InputStream input = null;
try
{
input = getClass().getResourceAsStream(name);
File dir = new File(this.fileSystem.workDir(), "findbugs");
FileUtils.forceMkdir(dir);
File target = new File(dir, name);
FileUtils.copyInputStreamToFile(input, target);
return target;
}
catch (IOException e)
{
throw new IllegalStateException("Fail to extract Findbugs dependency", e);
}
finally
{
IOUtils.closeQuietly(input);
}
}
public File getFbContribJar()
{
return this.fbContrib;
}
public File getFindSecBugsJar()
{
return this.findSecBugs;
}
public static List getPropertyDefinitions()
{
String subCategory = "FindBugs";
return ImmutableList.of(
PropertyDefinition.builder("sonar.findbugs.effort")
.defaultValue("Default")
.category("java")
.subCategory(subCategory)
.name("Effort")
.description("Effort of the bug finders. Valid values are Min, Default and Max. Setting 'Max' increases precision but also increases memory consumption.")
.onQualifiers("TRK", new String[] { "BRC" })
.build(),
PropertyDefinition.builder("sonar.findbugs.timeout")
.defaultValue(Long.toString(600000L))
.category("java")
.subCategory(subCategory)
.name("Timeout")
.description("Specifies the amount of time, in milliseconds, that FindBugs may run before it is assumed to be hung and is terminated. The default is 600,000 milliseconds, which is ten minutes.")
.onQualifiers("TRK", new String[] { "BRC" })
.type(PropertyType.INTEGER)
.build(),
PropertyDefinition.builder("sonar.findbugs.excludesFilters")
.category("java")
.subCategory(subCategory)
.name("Excludes Filters")
.description("Paths to findbugs filter-files with exclusions.")
.onQualifiers("TRK", new String[] { "BRC" })
.multiValues(true)
.build(),
PropertyDefinition.builder("sonar.findbugs.confidenceLevel")
.defaultValue("medium")
.category("java")
.subCategory(subCategory)
.name("Confidence Level")
.description("Specifies the confidence threshold (previously called \"priority\") for reporting issues. If set to \"low\", confidence is not used to filter bugs. If set to \"medium\" (the default), low confidence issues are supressed. If set to \"high\", only high confidence bugs are reported. ")
.onQualifiers("TRK", new String[] { "BRC" })
.build());
}
}
FindbugsExecutor 类这个类定义了该plugin 有执行扫描功能,去创建线程执行,在执行之前去加载其他插件。其他plugin可能只提供规则,但是这个plugin可以具备扫描执行能力。
@BatchSide
public class FindbugsExecutor
{
private static final String FINDBUGS_CORE_PLUGIN_ID = "edu.umd.cs.findbugs.plugins.core";
private static final Logger LOG = LoggerFactory.getLogger(FindbugsExecutor.class);
private static Map priorityNameToValueMap = new HashMap();
static
{
priorityNameToValueMap.put("high", Integer.valueOf(1));
priorityNameToValueMap.put("medium", Integer.valueOf(2));
priorityNameToValueMap.put("low", Integer.valueOf(3));
priorityNameToValueMap.put("experimental", Integer.valueOf(4));
}
private static final Integer DEFAULT_PRIORITY = Integer.valueOf(2);
private final FindbugsConfiguration configuration;
public FindbugsExecutor(FindbugsConfiguration configuration)
{
this.configuration = configuration;
}
@VisibleForTesting
Collection execute()
{
return execute(true);
}
public Collection execute(boolean useAllPlugin)
{
return execute(useAllPlugin, useAllPlugin);
}
public Collection execute(boolean useFbContrib, boolean useFindSecBugs)
{
SecurityManager currentSecurityManager = System.getSecurityManager();
ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader());
Locale initialLocale = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
OutputStream xmlOutput = null;
Collection customPlugins = null;
ExecutorService executorService = Executors.newSingleThreadExecutor();
try
{
FindBugs2 engine = new FindBugs2();
Project project = this.configuration.getFindbugsProject();
if (project.getFileCount() == 0)
{
LOG.info("Findbugs analysis skipped for this project.");
return new ArrayList();
}
customPlugins = loadFindbugsPlugins(useFbContrib, useFindSecBugs);
disableUpdateChecksOnEveryPlugin();
engine.setProject(project);
XMLBugReporter xmlBugReporter = new XMLBugReporter(project);
xmlBugReporter.setPriorityThreshold(determinePriorityThreshold().intValue());
xmlBugReporter.setAddMessages(true);
File xmlReport = this.configuration.getTargetXMLReport();
LOG.info("Findbugs output report: " + xmlReport.getAbsolutePath());
xmlOutput = FileUtils.openOutputStream(xmlReport);
xmlBugReporter.setOutputStream(new PrintStream(xmlOutput));
engine.setBugReporter(xmlBugReporter);
UserPreferences userPreferences = UserPreferences.createDefaultUserPreferences();
userPreferences.setEffort(this.configuration.getEffort());
engine.setUserPreferences(userPreferences);
engine.addFilter(this.configuration.saveIncludeConfigXml().getAbsolutePath(), true);
for (Object localObject1 = this.configuration.getExcludesFilters().iterator(); ((Iterator)localObject1).hasNext();)
{
File filterFile = (File)((Iterator)localObject1).next();
if (filterFile.isFile())
{
LOG.info("Use filter-file: {}", filterFile);
engine.addFilter(filterFile.getAbsolutePath(), false);
}
else
{
LOG.warn("FindBugs filter-file not found: {}", filterFile);
}
}
engine.setDetectorFactoryCollection(DetectorFactoryCollection.instance());
engine.setAnalysisFeatureSettings(FindBugs.DEFAULT_EFFORT);
engine.finishSettings();
executorService.submit(new FindbugsTask(engine)).get(this.configuration.getTimeout(), TimeUnit.MILLISECONDS);
return toReportedBugs(xmlBugReporter.getBugCollection());
}
catch (TimeoutException e)
{
throw new IllegalStateException("Can not execute Findbugs with a timeout threshold value of " + this.configuration.getTimeout() + " milliseconds", e);
}
catch (Exception e)
{
throw new IllegalStateException("Can not execute Findbugs", e);
}
finally
{
System.setSecurityManager(currentSecurityManager);
resetCustomPluginList(customPlugins);
executorService.shutdown();
IOUtils.closeQuietly(xmlOutput);
Thread.currentThread().setContextClassLoader(initialClassLoader);
Locale.setDefault(initialLocale);
}
}
private static Collection toReportedBugs(BugCollection bugCollection)
{
Collection bugs = new ArrayList();
for (BugInstance bugInstance : bugCollection) {
if (bugInstance.getPrimarySourceLineAnnotation() == null) {
LOG.warn("No source line for " + bugInstance.getType());
} else {
bugs.add(new ReportedBug(bugInstance));
}
}
return bugs;
}
private Integer determinePriorityThreshold()
{
Integer integer = (Integer)priorityNameToValueMap.get(this.configuration.getConfidenceLevel());
if (integer == null) {
integer = DEFAULT_PRIORITY;
}
return integer;
}
private static class FindbugsTask
implements Callable
findbugs-result.xml 扫描相关的类:
class FindbugsXmlReportParser
{
private final File findbugsXmlReport;
private final String findbugsXmlReportPath;
public FindbugsXmlReportParser(File findbugsXmlReport)
{
this.findbugsXmlReport = findbugsXmlReport;
this.findbugsXmlReportPath = findbugsXmlReport.getAbsolutePath();
if (!findbugsXmlReport.exists()) {
throw new IllegalStateException("The findbugs XML report can't be found at '" + this.findbugsXmlReportPath + "'");
}
}
public List getBugInstances()
{
List result = Lists.newArrayList();
try
{
SMInputFactory inf = new SMInputFactory(XMLInputFactory.newInstance());
SMInputCursor cursor = inf.rootElementCursor(this.findbugsXmlReport).advance();
SMInputCursor bugInstanceCursor = cursor.childElementCursor("BugInstance").advance();
while (bugInstanceCursor.asEvent() != null)
{
XmlBugInstance xmlBugInstance = new XmlBugInstance();
xmlBugInstance.type = bugInstanceCursor.getAttrValue("type");
xmlBugInstance.longMessage = "";
result.add(xmlBugInstance);
ImmutableList.Builder lines = ImmutableList.builder();
SMInputCursor bugInstanceChildCursor = bugInstanceCursor.childElementCursor().advance();
while (bugInstanceChildCursor.asEvent() != null)
{
String nodeName = bugInstanceChildCursor.getLocalName();
if ("LongMessage".equals(nodeName))
{
xmlBugInstance.longMessage = bugInstanceChildCursor.collectDescendantText();
}
else if ("SourceLine".equals(nodeName))
{
XmlSourceLineAnnotation xmlSourceLineAnnotation = new XmlSourceLineAnnotation();
xmlSourceLineAnnotation.parseStart(bugInstanceChildCursor.getAttrValue("start"));
xmlSourceLineAnnotation.parseEnd(bugInstanceChildCursor.getAttrValue("end"));
xmlSourceLineAnnotation.parsePrimary(bugInstanceChildCursor.getAttrValue("primary"));
xmlSourceLineAnnotation.className = bugInstanceChildCursor.getAttrValue("classname");
lines.add(xmlSourceLineAnnotation);
}
bugInstanceChildCursor.advance();
}
xmlBugInstance.sourceLines = lines.build();
bugInstanceCursor.advance();
}
cursor.getStreamReader().closeCompletely();
}
catch (XMLStreamException e)
{
throw new IllegalStateException("Unable to parse the Findbugs XML Report '" + this.findbugsXmlReportPath + "'", e);
}
return result;
}
public static class XmlBugInstance
{
private String type;
private String longMessage;
private List sourceLines;
public String getType()
{
return this.type;
}
public String getLongMessage()
{
return this.longMessage;
}
@CheckForNull
public FindbugsXmlReportParser.XmlSourceLineAnnotation getPrimarySourceLine()
{
for (FindbugsXmlReportParser.XmlSourceLineAnnotation sourceLine : this.sourceLines) {
if (sourceLine.isPrimary()) {
return sourceLine;
}
}
return this.sourceLines.isEmpty() ? null : (FindbugsXmlReportParser.XmlSourceLineAnnotation)this.sourceLines.get(0);
}
}
public static class XmlSourceLineAnnotation
{
private boolean primary;
private Integer start;
private Integer end;
@VisibleForTesting
protected String className;
public void parseStart(String attrValue)
{
try
{
this.start = Integer.valueOf(Integer.parseInt(attrValue));
}
catch (NumberFormatException e)
{
this.start = null;
}
}
public void parseEnd(String attrValue)
{
try
{
this.end = Integer.valueOf(Integer.parseInt(attrValue));
}
catch (NumberFormatException e)
{
this.end = null;
}
}
public void parsePrimary(String attrValue)
{
this.primary = Boolean.parseBoolean(attrValue);
}
public boolean isPrimary()
{
return this.primary;
}
public Integer getStart()
{
return this.start;
}
public Integer getEnd()
{
return this.end;
}
public String getClassName()
{
return this.className;
}
public String getSonarJavaFileKey()
{
if (this.className.indexOf('$') > -1) {
return this.className.substring(0, this.className.indexOf('$'));
}
return this.className;
}
}
}