/*
* 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.
*
* 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.valgrind;
import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.lang.StringUtils;
import org.codehaus.staxmate.in.SMEvent;
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.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.Violation;
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.plugins.cxx.veraxx.CxxVeraxxRuleRepository;
public class CxxValgrindSensor extends ReportsHelper implements Sensor {
private RuleFinder ruleFinder;
public CxxValgrindSensor(RuleFinder ruleFinder)
{
this.ruleFinder = ruleFinder;
}
private static Logger logger = LoggerFactory
.getLogger(CxxValgrindSensor.class);
public boolean shouldExecuteOnProject(Project project) {
return CxxPlugin.KEY.equals(project.getLanguageKey());
}
public static final String GROUP_ID = "org.codehaus.mojo";
public static final String ARTIFACT_ID = "cxx-maven-plugin";
public static final String SENSOR_ID = "valgrind";
public static final String DEFAULT_VERAXX_REPORTS_DIR = "valgrind-reports";
public static final String DEFAULT_REPORTS_FILE_PATTERN = "**/valgrind-result-*.xml";
@Override
protected String getArtifactId() {
return ARTIFACT_ID;
}
@Override
protected String getSensorId() {
return SENSOR_ID;
}
@Override
protected String getDefaultReportsDir() {
return DEFAULT_VERAXX_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) {
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 {
logger.info("parsing {}", xmlFile);
StaxParser parser = new StaxParser(
new StaxParser.XmlStreamHandler() {
public void stream(SMHierarchicCursor rootCursor)
throws XMLStreamException {
try {
Map<String, FileData> fileDataPerFilename = new HashMap<String, FileData>();
rootCursor.advance();
collectError(project, rootCursor.childElementCursor("error"),
fileDataPerFilename,
context);
for (FileData d : fileDataPerFilename.values()) {
d.saveMetric(project, context);
}
} catch (ParseException e) {
e.printStackTrace();
throw new XMLStreamException(e);
}
}
});
parser.parse(xmlFile);
} catch (XMLStreamException e) {
e.printStackTrace();
throw new XmlParserException(e);
}
}
private class Frame {
private String fn = "";
private String dir = "";
private String file = "";
private String line = "";
public String getText() {
return "Function :" + fn + " at line :" + line;
}
}
private class ValgrindError {
private List<Frame> stack = new ArrayList<Frame>();
private List<String> comments = new ArrayList<String>();
private String kind = "";
private String unique = "";
public Map<String, List<String>> LinesErrorPerFileKey = new HashMap<String, List<String>>();
public String getText() {
StringBuilder res = new StringBuilder();
for (String comment : comments) {
res.append(comment + "\n");
}
res.append("Stack trace : \n");
for (Frame f : stack) {
res.append(f.getText()+ "\n");
}
return res.toString();
}
}
private class FileData {
FileData(CxxFile f) {
file = f;
}
private CxxFile file;
public void saveMetric(Project project, SensorContext context) {
for (ValgrindError error : fileErrors.values())
{
Rule rule = ruleFinder.findByKey(CxxValgrindRuleRepository.REPOSITORY_KEY, error.kind);
if (rule != null)
{
List<String> Lines = error.LinesErrorPerFileKey.get(file.getKey());
if (Lines != null)
{
List<String> Done = new ArrayList<String>();
for (String line : Lines)
{
if (!Done.contains(file.getKey()+error.unique+line)) {
Object t[] = {file.getKey(), error.getText(), line};
//logger.info("error (source={}) message={} found at line {}", t);
Violation violation = Violation.create(rule, file);
violation.setMessage(error.getText());
violation.setLineId(Integer.parseInt(line));
context.saveViolation(violation);
Done.add(file.getKey()+error.unique+line);
} else {
Object t[] = {error.getText(), file.getKey(), line};
logger.warn("error (message={}) in source={} at line {} already reported", t);
}
}
} else {
Object t[] = {error.getText(), file.getKey()};
logger.warn("No Line for error (message={}) in source={} message={}", t);
}
} else {
Object t[] = {error.getText(), file.getKey()};
logger.warn("No rule for error (message={}) in source={}", t);
}
}
}
public Map<String, ValgrindError> fileErrors = new HashMap<String, ValgrindError>();
}
private void collectError(Project project, SMInputCursor error,
Map<String, FileData> fileDataPerFilename, SensorContext context) throws ParseException, XMLStreamException {
while (error.getNext() != null) {
//logger.info("collectError nodename = {} {}", error.getPrefixedName(), error.getAttrCount());
SMInputCursor child = error.childElementCursor();
ValgrindError ValError = new ValgrindError();
Map<String, CxxFile> FileInvolved = new HashMap<String, CxxFile>();
while (child.getNext() != null) {
if (child.getLocalName().equalsIgnoreCase("unique")) {
ValError.unique = child.getElemStringValue();
} else if (child.getLocalName().equalsIgnoreCase("kind")) {
ValError.kind = child.getElemStringValue();
} else if (child.getLocalName().matches(".*what.*")) {
SMInputCursor text = child.childElementCursor("text");
while (text.getNext() != null) {
ValError.comments.add(text.getElemStringValue());
}
} else if (child.getLocalName().equalsIgnoreCase("stack")) {
SMInputCursor frame = child.childElementCursor("frame");
while (frame.getNext() != null) {
SMInputCursor frameChild = frame.childElementCursor();
Frame f = new Frame();
while (frameChild.getNext() != null) {
if (frameChild.getLocalName().equalsIgnoreCase("fn")) {
f.fn = frameChild.getElemStringValue();
} else if (frameChild.getLocalName().equalsIgnoreCase("dir")) {
f.dir = frameChild.getElemStringValue();
} else if (frameChild.getLocalName().equalsIgnoreCase("file")) {
f.file = frameChild.getElemStringValue();
} else if (frameChild.getLocalName().equalsIgnoreCase("line")) {
f.line = frameChild.getElemStringValue();
}
CxxFile cxxfile = null;
if (!StringUtils.isEmpty(f.file) && !StringUtils.isEmpty(f.dir)) {
cxxfile = CxxFile.fromFileName(project, f.dir + "/" + f.file, false);
} else if (!StringUtils.isEmpty(f.file)) {
cxxfile = CxxFile.fromFileName(project, f.file, false);
}
if (null != cxxfile && fileExist(context, cxxfile) && !StringUtils.isEmpty(f.line)) {
List<String> Lines = ValError.LinesErrorPerFileKey.get(cxxfile.getKey());
if (Lines == null) {
Lines = new ArrayList<String>();
ValError.LinesErrorPerFileKey.put(cxxfile.getKey(), Lines);
}
if (-1 == Lines.indexOf(f.line)) {
Lines.add(f.line);
}
if (null == FileInvolved.get(cxxfile.getKey())) {
FileInvolved.put(cxxfile.getKey(), cxxfile);
}
}
}
ValError.stack.add(f);
}
}
}
for (CxxFile curResource : FileInvolved.values()) {
FileData data = fileDataPerFilename.get(curResource.getKey());
if (data == null) {
data = new FileData(curResource);
fileDataPerFilename.put(curResource.getKey(), data);
}
data.fileErrors.put(ValError.unique, ValError);
}
}
}
private boolean fileExist(SensorContext context, CxxFile file) {
return context.getResource(file) != null;
}
}