Skip to content

Commit

Permalink
Merge pull request #1096 from stefanweiser/master
Browse files Browse the repository at this point in the history
Add an XSLT pre-analyse step
  • Loading branch information
guwirth authored Apr 29, 2017
2 parents 5188274 + 3a626ff commit 8ff30b9
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,49 @@
package org.sonar.cxx.sensors.other;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.io.FilenameUtils;
import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.cxx.CxxLanguage;
import org.sonar.cxx.sensors.utils.CxxMetrics;
import org.sonar.cxx.sensors.utils.CxxUtils;
import org.sonar.cxx.sensors.utils.CxxReportSensor;
import org.sonar.cxx.sensors.utils.StaxParser;

/**
* Custom Rule Import, all static analysis are supported.
*
* @author jorge costa
* @author jorge costa, stefan weiser
*/
public class CxxOtherSensor extends CxxReportSensor {
public static final Logger LOG = Loggers.get(CxxOtherSensor.class);
public static final String REPORT_PATH_KEY = "other.reportPath";
public static final String KEY = "Other";
public static final String KEY = "other";
public static final String OTHER_XSLT_KEY = KEY + ".xslt.";
public static final String STYLESHEET_KEY = ".stylesheet";
public static final String INPUT_KEY = ".inputs";
public static final String OUTPUT_KEY = ".outputs";
private CxxLanguage cxxLanguage;

/**
* {@inheritDoc}
*/
public CxxOtherSensor(CxxLanguage language) {
super(language);
this.cxxLanguage = language;
}

@Override
Expand All @@ -58,11 +74,20 @@ protected String reportPathKey() {
public void describe(SensorDescriptor descriptor) {
descriptor.onlyOnLanguage(this.language.getKey()).name(language.getName() + " ExternalRulesSensor");
}


/**
* {@inheritDoc}
*/
@Override
public void processReport(final SensorContext context, File report) throws XMLStreamException {
public void execute(SensorContext context) {
transformFiles(context.fileSystem().baseDir());
super.execute(context);
}

@Override
public void processReport(final SensorContext context, File report) throws XMLStreamException, IOException, URISyntaxException, TransformerException {
LOG.debug("Parsing 'other' format");

StaxParser parser = new StaxParser(new StaxParser.XmlStreamHandler() {

/**
Expand All @@ -86,9 +111,67 @@ public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException {

parser.parse(report);
}

@Override
protected String getSensorKey() {
return KEY;
}

public void transformFiles(final File baseDir) {
Boolean goOn = true;
for (int i = 1; (i < 10) && goOn; i++) {
String stylesheetKey = OTHER_XSLT_KEY + i + STYLESHEET_KEY;
String inputKey = OTHER_XSLT_KEY + i + INPUT_KEY;
String outputKey = OTHER_XSLT_KEY + i + OUTPUT_KEY;

String stylesheet = resolveFilename(baseDir.getAbsolutePath(), cxxLanguage.getStringOption(stylesheetKey));
List<File> inputs = getReports(cxxLanguage, baseDir, inputKey);
String outputStrings[] = language.getStringArrayOption(outputKey);
List<String> outputs = Arrays.asList((outputStrings != null) ? outputStrings : new String[] {});

if (inputs.size() != outputs.size()) {
LOG.error("Number of source XML files is not equal to the the number of output files.");
goOn = false;
} else if ((stylesheet != null) ||
((inputs != null) && (inputs.size() > 0)) ||
((outputs != null) && (outputs.size() > 0))) {
if (stylesheet == null) {
LOG.error(stylesheetKey + " is not defined.");
goOn = false;
} else if (inputs == null) {
LOG.error(inputKey + " file is not defined.");
goOn = false;
} else if (outputs == null) {
LOG.error(outputKey + " is not defined.");
goOn = false;
} else {
LOG.debug("Converting " + stylesheet + " with " + inputs.toString() + " to " + outputs.toString() + ".");
File stylesheetFile = new File(stylesheet);
if (stylesheetFile.isAbsolute()) {
transformFileList(baseDir.getAbsolutePath(), stylesheetFile, inputs, outputs);
}
}
} else {
// No keys found.
goOn = false;
}
}
}

private void transformFileList(final String baseDir, File stylesheetFile, List<File> inputs, List<String> outputs) {
for (int j = 0; j < inputs.size(); j++) {
try {
String normalizedOutputFilename = resolveFilename(baseDir, outputs.get(j));
CxxUtils.transformFile(new StreamSource(stylesheetFile), inputs.get(j), new File(normalizedOutputFilename));
} catch (Exception e) {
String msg = new StringBuilder()
.append("Cannot transform report files: '")
.append(e)
.append("'")
.toString();
LOG.error(msg);
CxxUtils.validateRecovery(e, cxxLanguage);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,8 @@
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.sonar.api.batch.sensor.SensorContext;
Expand Down Expand Up @@ -251,15 +245,8 @@ File transformReport(File report)
}

Source xsl = new StreamSource(inputStream);
TransformerFactory factory = TransformerFactory.newInstance();
Templates template = factory.newTemplates(xsl);
Transformer xformer = template.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");

Source source = new StreamSource(report);
transformed = new File(report.getAbsolutePath() + ".after_xslt");
Result result = new StreamResult(transformed);
xformer.transform(source, result);
CxxUtils.transformFile(xsl, report, transformed);
} else {
LOG.debug("Transformation skipped: no xslt given");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ protected String getStringProperty(String name, String def) {
return value;
}

public static String resolveFilename(final String baseDir, final String filename) {

// Normalization can return null if path is null, is invalid, or is a path with back-ticks outside known directory structure
String normalizedPath = FilenameUtils.normalize(filename);
if ((normalizedPath != null) && (new File(normalizedPath).isAbsolute())) {
return normalizedPath;
}

// Prefix with absolute module base dir, attempt normalization again -- can still get null here
normalizedPath = FilenameUtils.normalize(baseDir + File.separator + filename);
if (normalizedPath != null) {
return normalizedPath;
}

return null;
}

public static List<File> getReports(CxxLanguage language,
final File moduleBaseDir,
String genericReportKeyData) {
Expand All @@ -134,19 +151,13 @@ public static List<File> getReports(CxxLanguage language,
return reports;
}

List<String> reportPaths = Arrays.asList(language.getStringArrayOption(genericReportKeyData));
String reportPathStrings[] = language.getStringArrayOption(genericReportKeyData);
List<String> reportPaths = Arrays.asList((reportPathStrings != null) ? reportPathStrings : new String[] {});
if (!reportPaths.isEmpty()) {
List<String> includes = new ArrayList<>();
for (String reportPath : reportPaths) {
// Normalization can return null if path is null, is invalid, or is a path with back-ticks outside known directory structure
String normalizedPath = FilenameUtils.normalize(reportPath);
if (normalizedPath != null && new File(normalizedPath).isAbsolute()) {
includes.add(normalizedPath);
continue;
}

// Prefix with absolute module base dir, attempt normalization again -- can still get null here
normalizedPath = FilenameUtils.normalize(moduleBaseDir.getAbsolutePath() + File.separator + reportPath);
String normalizedPath = resolveFilename(moduleBaseDir.getAbsolutePath(), reportPath);
if (normalizedPath != null) {
includes.add(normalizedPath);
continue;
Expand Down Expand Up @@ -247,7 +258,7 @@ private void saveViolation(SensorContext sensorContext, String ruleRepoKey,
newIssue.save();
violationsCount++;
} catch (Exception ex) {
LOG.error("Could not add the issue '{}', skipping issue", ex.getMessage());
LOG.error("Could not add the issue '{}' for rule '{}:{}', skipping issue", ex.getMessage(), ruleRepoKey, ruleId);
CxxUtils.validateRecovery(ex, this.language);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.cxx.CxxLanguage;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;


/**
Expand Down Expand Up @@ -65,13 +73,20 @@ public static String normalizePathFull(String filename, String baseDir) {
if (targetfile.isAbsolute()) {
filePath = normalizePath(filename);
} else {
// RATS, CppCheck and Vera++ provide names like './file.cpp' - add source folder for index check
// RATS, CppCheck and Vera++ provide names like './file.cpp' - add input folder for index check
filePath = normalizePath(baseDir + File.separator + filename);
}
return filePath;
}




public static void transformFile(Source stylesheetFile, File input, File output) throws TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(stylesheetFile);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(new StreamSource(input), new StreamResult(output));
}


/**
* <p>Gets the stack trace from a Throwable as a String.</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Sonar C++ Plugin (Community)
* Copyright (C) 2010-2017 SonarOpenCommunity
* http://github.com/SonarOpenCommunity/sonar-cxx
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.cxx.sensors.other;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.File;
import static org.fest.assertions.Assertions.assertThat;
import org.junit.Assert;

import org.junit.Before;
import org.junit.Test;
import org.apache.commons.io.FileUtils;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.sensor.internal.SensorContextTester;
import org.sonar.cxx.CxxLanguage;
import org.sonar.api.config.Settings;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.cxx.sensors.utils.TestUtils;

public class CxxOtherXsltTest {

private FileSystem fs;

@Before
public void setUp() {
fs = TestUtils.mockFileSystem();
}

@Test
public void shouldReportNothingWhenNoReportFound() {
SensorContextTester context = SensorContextTester.create(fs.baseDir());
CxxLanguage language = TestUtils.mockCxxLanguage();
when(language.getStringOption(CxxOtherSensor.REPORT_PATH_KEY)).thenReturn("notexistingpath");
when(language.getStringOption(CxxOtherSensor.OTHER_XSLT_KEY + "1" + CxxOtherSensor.STYLESHEET_KEY)).thenReturn("notexistingpath");
when(language.getStringArrayOption(CxxOtherSensor.OTHER_XSLT_KEY + "1" + CxxOtherSensor.INPUT_KEY)).thenReturn(new String[] {"notexistingpath"});
when(language.getStringArrayOption(CxxOtherSensor.OTHER_XSLT_KEY + "1" + CxxOtherSensor.OUTPUT_KEY)).thenReturn(new String[] {"notexistingpath"});
CxxOtherSensor sensor = new CxxOtherSensor(language);

sensor.execute(context);

File reportAfter = new File("notexistingpath");
Assert.assertFalse("The output file does exist!", reportAfter.exists() && reportAfter.isFile());
}

@Test
public void transformReport_shouldTransformReport()
throws java.io.IOException, javax.xml.transform.TransformerException {
System.out.print("Starting transformReport_shouldTransformReport");
String stylesheetFile = "externalrules-reports" + File.separator + "externalrules-xslt-stylesheet.xslt";
String inputFile = "externalrules-reports" + File.separator + "externalrules-xslt-input.xml";
String outputFile = "externalrules-reports" + File.separator + "externalrules-xslt-output.xml";

CxxLanguage language = TestUtils.mockCxxLanguage();
when(language.getStringOption(CxxOtherSensor.REPORT_PATH_KEY)).thenReturn("externalrules-xslt-output.xml");
when(language.getStringOption(CxxOtherSensor.OTHER_XSLT_KEY + "1" + CxxOtherSensor.STYLESHEET_KEY)).thenReturn(stylesheetFile);
when(language.getStringArrayOption(CxxOtherSensor.OTHER_XSLT_KEY + "1" + CxxOtherSensor.INPUT_KEY)).thenReturn(new String[] {inputFile});
when(language.getStringArrayOption(CxxOtherSensor.OTHER_XSLT_KEY + "1" + CxxOtherSensor.OUTPUT_KEY)).thenReturn(new String[] {outputFile});
CxxOtherSensor sensor = new CxxOtherSensor(language);

sensor.transformFiles(fs.baseDir());

File reportBefore = new File(fs.baseDir() + "/" + inputFile);
File reportAfter = new File(fs.baseDir() + "/" + outputFile);
Assert.assertTrue("The output file does not exist!", reportAfter.exists() && reportAfter.isFile());
Assert.assertTrue("The input and output file is equal!", !FileUtils.contentEquals(reportBefore, reportAfter));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<results>
<warning filename="sources/utils/code_chunks.cpp" line="1" identifier="cxxexternal-unusedFunction" message="The function 'foo' is never used"/>
<warning filename="sources/utils/utils.cpp" line="1" identifier="cxxexternal-unusedFunction" message="The function 'utils' is never used"/>
</results>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:template match="results">
<results>
<xsl:apply-templates/>
</results>
</xsl:template>
<xsl:template match="warning">
<error id="{@identifier}" msg="{@message}" file="{@filename}" line="{@line}" />
</xsl:template>
</xsl:stylesheet>

0 comments on commit 8ff30b9

Please sign in to comment.