/*
* Sonar Cxx Plugin, open source software quality management tool.
* Copyright (C) 2010 - 2011, Neticoa SAS France - Tous droits réservés.
* Author(s) : Franck Bonin, Neticoa SAS France.
* Copyright (C) 2010 Vincent Hardion
*
* Sonar Cxx Plugin 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.
*
* Sonar Cxx 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar Cxx Plugin; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.cxx.cppcheck;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.commons.lang.StringUtils;
import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.StaxParser;
import org.sonar.api.utils.XmlParserException;
import org.sonar.plugins.cxx.CxxFile;
import org.sonar.plugins.cxx.CxxPlugin;
import org.sonar.plugins.cxx.utils.ReportsHelper;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.Violation;
/**
* Sensor for CppCheck external tool.
*
* CppCheck is an equivalent to FindBug but for C++
*
* @author fbonin
* @author vhardion
* @todo enable include dirs (-I)
* @todo allow configuration of path to analyze
*/
public class CxxCppCheckSensor extends ReportsHelper implements Sensor {
private static final String EXEC = "cppcheck";
private static final String ARGS = "--enable=all -v --quiet --xml";
private static final String GROUP_ID = "org.codehaus.mojo";
private static final String ARTIFACT_ID = "cxx-maven-plugin";
private static final String SENSOR_ID = "cppcheck";
private static final String DEFAULT_CPPCHECK_REPORTS_DIR = "cppcheck-reports";
private static final String DEFAULT_REPORTS_FILE_PATTERN = "**/cppcheck-result-*.xml";
private RuleFinder ruleFinder;
private boolean dynamicAnalysis = false;
public CxxCppCheckSensor(RuleFinder ruleFinder, Configuration conf) {
this.ruleFinder = ruleFinder;
this.dynamicAnalysis = conf.getBoolean(
"sonar.cxx.cppcheck.runAnalysis", this.dynamicAnalysis);
}
private static Logger logger = LoggerFactory
.getLogger(CxxCppCheckSensor.class);
/**
* This plugin should be executed on C++ project
*
* @param project
* @return
*/
public boolean shouldExecuteOnProject(Project project) {
return CxxPlugin.KEY.equals(project.getLanguageKey());
}
@Override
protected String getArtifactId() {
return ARTIFACT_ID;
}
@Override
protected String getSensorId() {
return SENSOR_ID;
}
@Override
protected String getDefaultReportsDir() {
return DEFAULT_CPPCHECK_REPORTS_DIR;
}
@Override
protected String getDefaultReportsFilePattern() {
return DEFAULT_REPORTS_FILE_PATTERN;
}
@Override
protected String getGroupId() {
return GROUP_ID;
}
@Override
protected Logger getLogger() {
return logger;
}
public void analyse(Project project, SensorContext context) {
if (dynamicAnalysis) {
Process p;
try {
String cmd = EXEC + " " + ARGS + " "
+ project.getPom().getBuild().getSourceDirectory();
logger.debug(cmd);
p = Runtime.getRuntime().exec(cmd);
p.waitFor();
// Write result in local file
File resultOutputFile = new File(project.getFileSystem()
.getSonarWorkingDirectory()
+ "/cppcheck-result.xml");
// resultOutputFile.createNewFile();
logger.debug("Output result to "
+ resultOutputFile.getAbsolutePath());
// Becarefull ... CppCheck print its result into Error output !
// TeeInputStream is used to read result from stream and both
// write it to a file
FileOutputStream fos = new FileOutputStream(resultOutputFile);
TeeInputStream tis = new TeeInputStream(p.getErrorStream(),
fos, true);
parseReport(project, tis, context);
} catch (InterruptedException ex) {
logger.error("Analysis can't wait for the end of the process",
ex);
} catch (IOException ex) {
logger.error("IO EXCEPTION", ex);
}
} else {
File reportDirectory = getReportsDirectory(project);
if (reportDirectory != null) {
File reports[] = getReports(project, reportDirectory);
for (File report : reports) {
parseReport(project, report, context);
}
}
}
}
private void parseReport(final Project project, File xmlFile,
final SensorContext context) {
try {
parseReport(project, new FileInputStream(xmlFile), context);
} catch (FileNotFoundException ex) {
logger.error("CppCheck Report not found : "
+ xmlFile.getAbsoluteFile(), ex);
}
}
/**
* Parse the stream of CppCheck XML report
*
* @param project
* @param xmlStream
* - This stream will be closed at the end of this method
* @param context
*/
private void parseReport(final Project project, InputStream xmlStream,
final SensorContext context) {
try {
logger.info("parsing CppCheck XML stream{}");
StaxParser parser = new StaxParser(
new StaxParser.XmlStreamHandler() {
public void stream(SMHierarchicCursor rootCursor)
throws XMLStreamException {
try {
rootCursor.advance();
collectError(project, rootCursor
.childElementCursor("error"), context);
} catch (ParseException e) {
throw new XMLStreamException(e);
}
}
});
parser.parse(xmlStream);
} catch (XMLStreamException e) {
throw new XmlParserException(e);
} finally {
try {
if (xmlStream != null) {
xmlStream.close();
}
} catch (IOException ex) {
logger.error("Can't close the xml stream", ex);
}
}
}
private void collectError(Project project, SMInputCursor error,
SensorContext context) throws ParseException, XMLStreamException {
while (error.getNext() != null) {
// logger.info("collectError nodename = {} {}",
// error.getPrefixedName(), error.getAttrCount());
String id = error.getAttrValue("id");
String msg = error.getAttrValue("msg");
String file = error.getAttrValue("file");
String line = error.getAttrValue("line");
if (StringUtils.isEmpty(line)) {
line = "0";
}
if (!StringUtils.isEmpty(file)) {
CxxFile ressource = CxxFile.fromFileName(project, file,
getReportsIncludeSourcePath(project), false);
if (fileExist(context, ressource)) {
Rule rule = ruleFinder.findByKey(
CxxCppCheckRuleRepository.REPOSITORY_KEY, id);
if (rule != null) {
Object t[] = { id, msg, line, ressource.getKey() };
logger
.debug(
"error id={} msg={} found at line {} from ressource {}",
t);
Violation violation = Violation.create(rule, ressource);
violation.setMessage(msg);
violation.setLineId(Integer.parseInt(line));
context.saveViolation(violation);
} else {
Object t[] = { id, msg, line, file };
logger
.warn(
"No rule for error id={} msg={} found at line {} from file {}",
t);
}
} else {
Object t[] = { id, msg, line, file };
logger
.warn(
"error id={} msg={} found at line {} from file {} has no ressource associated",
t);
}
} else {
Object t[] = { id, msg, line };
logger
.warn(
"error id={} msg={} found at line {} has no file associated",
t);
}
}
}
private boolean fileExist(SensorContext context, CxxFile file) {
return context.getResource(file) != null;
}
}