Merge branch 'bug-1425' into pmd/5.3.x

This commit is contained in:
Andreas Dangel
2015-10-16 12:25:51 +02:00
11 changed files with 185 additions and 31 deletions

View File

@ -115,7 +115,7 @@ public abstract class AbstractNode implements Node {
}
public boolean hasImageEqualTo(String image) {
return this.image != null && this.image.equals(image);
return this.getImage() != null && this.getImage().equals(image);
}
public int getBeginLine() {

View File

@ -24,6 +24,8 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
public static final StringProperty ENCODING = new StringProperty("encoding",
"XML encoding format, defaults to UTF-8.", "UTF-8", 0);
private boolean useUTF8 = false;
public XMLRenderer() {
super(NAME, "XML format.");
@ -45,6 +47,9 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
@Override
public void start() throws IOException {
String encoding = getProperty(ENCODING);
if (encoding.equalsIgnoreCase("utf-8")) {
useUTF8 = true;
}
Writer writer = getWriter();
StringBuilder buf = new StringBuilder(500);
@ -76,7 +81,7 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
}
filename = rv.getFilename();
buf.append("<file name=\"");
StringUtil.appendXmlEscaped(buf, filename);
StringUtil.appendXmlEscaped(buf, filename, useUTF8);
buf.append("\">").append(PMD.EOL);
}
@ -85,9 +90,9 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
buf.append("\" begincolumn=\"").append(rv.getBeginColumn());
buf.append("\" endcolumn=\"").append(rv.getEndColumn());
buf.append("\" rule=\"");
StringUtil.appendXmlEscaped(buf, rv.getRule().getName());
StringUtil.appendXmlEscaped(buf, rv.getRule().getName(), useUTF8);
buf.append("\" ruleset=\"");
StringUtil.appendXmlEscaped(buf, rv.getRule().getRuleSetName());
StringUtil.appendXmlEscaped(buf, rv.getRule().getRuleSetName(), useUTF8);
buf.append('"');
maybeAdd("package", rv.getPackageName(), buf);
maybeAdd("class", rv.getClassName(), buf);
@ -97,7 +102,7 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
buf.append(" priority=\"");
buf.append(rv.getRule().getPriority().getPriority());
buf.append("\">").append(PMD.EOL);
StringUtil.appendXmlEscaped(buf, rv.getDescription());
StringUtil.appendXmlEscaped(buf, rv.getDescription(), useUTF8);
buf.append(PMD.EOL);
buf.append("</violation>");
@ -121,9 +126,9 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
for (Report.ProcessingError pe : errors) {
buf.setLength(0);
buf.append("<error ").append("filename=\"");
StringUtil.appendXmlEscaped(buf, pe.getFile());
StringUtil.appendXmlEscaped(buf, pe.getFile(), useUTF8);
buf.append("\" msg=\"");
StringUtil.appendXmlEscaped(buf, pe.getMsg());
StringUtil.appendXmlEscaped(buf, pe.getMsg(), useUTF8);
buf.append("\"/>").append(PMD.EOL);
writer.write(buf.toString());
}
@ -133,13 +138,13 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
for (Report.SuppressedViolation s : suppressed) {
buf.setLength(0);
buf.append("<suppressedviolation ").append("filename=\"");
StringUtil.appendXmlEscaped(buf, s.getRuleViolation().getFilename());
StringUtil.appendXmlEscaped(buf, s.getRuleViolation().getFilename(), useUTF8);
buf.append("\" suppressiontype=\"");
StringUtil.appendXmlEscaped(buf, s.suppressedByNOPMD() ? "nopmd" : "annotation");
StringUtil.appendXmlEscaped(buf, s.suppressedByNOPMD() ? "nopmd" : "annotation", useUTF8);
buf.append("\" msg=\"");
StringUtil.appendXmlEscaped(buf, s.getRuleViolation().getDescription());
StringUtil.appendXmlEscaped(buf, s.getRuleViolation().getDescription(), useUTF8);
buf.append("\" usermsg=\"");
StringUtil.appendXmlEscaped(buf, s.getUserMessage() == null ? "" : s.getUserMessage());
StringUtil.appendXmlEscaped(buf, s.getUserMessage() == null ? "" : s.getUserMessage(), useUTF8);
buf.append("\"/>").append(PMD.EOL);
writer.write(buf.toString());
}
@ -151,7 +156,7 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
private void maybeAdd(String attr, String value, StringBuilder buf) {
if (value != null && value.length() > 0) {
buf.append(' ').append(attr).append("=\"");
StringUtil.appendXmlEscaped(buf, value);
StringUtil.appendXmlEscaped(buf, value, useUTF8);
buf.append('"');
}
}

View File

@ -189,7 +189,10 @@ public final class StringUtil {
*
* @param buf The destination XML stream
* @param src The String to append to the stream
*
* @deprecated use {@link #appendXmlEscaped(StringBuilder, String, boolean)} instead
*/
@Deprecated
public static void appendXmlEscaped(StringBuilder buf, String src) {
appendXmlEscaped(buf, src, SUPPORTS_UTF8);
}
@ -244,7 +247,14 @@ public final class StringUtil {
c = src.charAt(i);
if (c > '~') {// 126
if (!supportUTF8) {
buf.append("&#x").append(Integer.toHexString(c)).append(';');
int codepoint = c;
// surrogate characters are not allowed in XML
if (Character.isHighSurrogate(c)) {
char low = src.charAt(i + 1);
codepoint = Character.toCodePoint(c, low);
i += 1;
}
buf.append("&#x").append(Integer.toHexString(codepoint)).append(';');
} else {
buf.append(c);
}

View File

@ -3,8 +3,26 @@
*/
package net.sourceforge.pmd.renderers;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilderFactory;
import net.sourceforge.pmd.FooRule;
import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.Report.ProcessingError;
import net.sourceforge.pmd.ReportTest;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.lang.ast.DummyNode;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class XMLRendererTest extends AbstractRendererTst {
@ -65,4 +83,43 @@ public class XMLRendererTest extends AbstractRendererTst {
String result = expected.replaceAll(" timestamp=\"[^\"]+\">", " timestamp=\"\">");
return result;
}
private static RuleViolation createRuleViolation(String description) {
DummyNode node = new DummyNode(1);
node.testingOnly__setBeginLine(1);
node.testingOnly__setBeginColumn(1);
node.testingOnly__setEndLine(1);
node.testingOnly__setEndColumn(1);
RuleContext ctx = new RuleContext();
ctx.setSourceCodeFilename("n/a");
return new ParametricRuleViolation<Node>(new FooRule(), ctx, node, description);
}
private void verifyXmlEscaping(Renderer renderer, String shouldContain) throws Exception {
Report report = new Report();
String surrogatePair = "\ud801\udc1c";
String msg = "The String literal \"Tokenizer " + surrogatePair + "\" appears...";
report.addRuleViolation(createRuleViolation(msg));
String actual = ReportTest.render(renderer, report);
Assert.assertTrue(actual.contains(shouldContain));
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(actual)));
NodeList violations = doc.getElementsByTagName("violation");
Assert.assertEquals(1, violations.getLength());
Assert.assertEquals(msg,
violations.item(0).getTextContent().trim());
}
@Test
public void testXMLEscapingWithUTF8() throws Exception {
Renderer renderer = getRenderer();
renderer.setProperty(XMLRenderer.ENCODING, "UTF-8");
verifyXmlEscaping(renderer, "\ud801\udc1c");
}
@Test
public void testXMLEscapingWithoutUTF8() throws Exception {
Renderer renderer = getRenderer();
renderer.setProperty(XMLRenderer.ENCODING, "ISO-8859-1");
verifyXmlEscaping(renderer, "&#x1041c;");
}
}

View File

@ -104,6 +104,35 @@ public class ASTLiteral extends AbstractJavaTypeNode {
return isString;
}
/**
* Tries to reconstruct the original string literal.
* If the original length is greater than the parsed String literal, then
* probably some unicode escape sequences have been used.
*
* @return
*/
public String getEscapedStringLiteral() {
String image = getImage();
if (!isStringLiteral() && !isCharLiteral()) {
return image;
}
int fullLength = getEndColumn() - getBeginColumn();
if (fullLength > image.length()) {
StringBuilder result = new StringBuilder(fullLength);
for (int i = 0; i < image.length(); i++) {
char c = image.charAt(i);
if (c < 0x20 || c > 0xff || image.length() == 1) {
String hex = "0000" + Integer.toHexString(c);
result.append("\\u").append(hex.substring(hex.length() - 4));
} else {
result.append(c);
}
}
return result.toString();
}
return image;
}
/**
* Returns true if this is a String literal with only one character.
* Handles octal and escape characters.

View File

@ -143,9 +143,11 @@ public class AvoidDuplicateLiteralsRule extends AbstractJavaRule {
for (Map.Entry<String, List<ASTLiteral>> entry : literals.entrySet()) {
List<ASTLiteral> occurrences = entry.getValue();
if (occurrences.size() >= threshold) {
Object[] args = new Object[] { entry.getKey(), Integer.valueOf(occurrences.size()),
Integer.valueOf(occurrences.get(0).getBeginLine()) };
addViolation(data, occurrences.get(0), args);
ASTLiteral first = occurrences.get(0);
String rawImage = first.getEscapedStringLiteral();
Object[] args = new Object[] { rawImage, Integer.valueOf(occurrences.size()),
Integer.valueOf(first.getBeginLine()) };
addViolation(data, first, args);
}
}
}

View File

@ -1,5 +1,6 @@
package net.sourceforge.pmd.lang.java.ast;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -54,6 +55,50 @@ public class ASTLiteralTest extends ParserTst {
assertTrue(((ASTLiteral)(literals.iterator().next())).isCharLiteral());
}
@Test
public void testStringUnicodeEscapesNotEscaped() {
ASTLiteral literal = new ASTLiteral(1);
literal.setStringLiteral();
literal.setImage("abcüabc");
literal.testingOnly__setBeginColumn(1);
literal.testingOnly__setEndColumn(7);
assertEquals("abcüabc", literal.getEscapedStringLiteral());
assertEquals("abcüabc", literal.getImage());
}
@Test
public void testStringUnicodeEscapesInvalid() {
ASTLiteral literal = new ASTLiteral(1);
literal.setStringLiteral();
literal.setImage("abc\\uXYZAabc");
literal.testingOnly__setBeginColumn(1);
literal.testingOnly__setEndColumn(12);
assertEquals("abc\\uXYZAabc", literal.getEscapedStringLiteral());
assertEquals("abc\\uXYZAabc", literal.getImage());
}
@Test
public void testStringUnicodeEscapesValid() {
ASTLiteral literal = new ASTLiteral(1);
literal.setStringLiteral();
literal.setImage("abc\u1234abc");
literal.testingOnly__setBeginColumn(1);
literal.testingOnly__setEndColumn(12);
assertEquals("abc\\u1234abc", literal.getEscapedStringLiteral());
assertEquals("abcሴabc", literal.getImage());
}
@Test
public void testCharacterUnicodeEscapesValid() {
ASTLiteral literal = new ASTLiteral(1);
literal.setCharLiteral();
literal.setImage("\u0030");
literal.testingOnly__setBeginColumn(1);
literal.testingOnly__setEndColumn(6);
assertEquals("\\u0030", literal.getEscapedStringLiteral());
assertEquals("0", literal.getImage());
}
private static final String TEST1 =
"public class Foo {" + PMD.EOL +
" String x = \"foo\";" + PMD.EOL +
@ -88,8 +133,4 @@ public class ASTLiteralTest extends ParserTst {
"public class Foo {" + PMD.EOL +
" char x = 'x';" + PMD.EOL +
"}";
public static junit.framework.Test suite() {
return new junit.framework.JUnit4TestAdapter(ASTLiteralTest.class);
}
}

View File

@ -8,19 +8,9 @@ import static org.junit.Assert.assertTrue;
import java.util.Set;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.testframework.SimpleAggregatorTst;
import org.junit.Test;
public class AvoidDuplicateLiteralsRuleTest extends SimpleAggregatorTst {
@Test
public void testAll() {
Rule rule = findRule("java-strings", "AvoidDuplicateLiterals");
rule.setProperty(AvoidDuplicateLiteralsRule.THRESHOLD_DESCRIPTOR, 2);
runTests(rule);
}
public class AvoidDuplicateLiteralsRuleTest {
@Test
public void testStringParserEmptyString() {
AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(',');

View File

@ -12,6 +12,7 @@ public class StringsRulesTest extends SimpleAggregatorTst {
@Override
public void setUp() {
addRule(RULESET, "AppendCharacterWithChar");
addRule(RULESET, "AvoidDuplicateLiterals");
addRule(RULESET, "AvoidStringBufferField");
addRule(RULESET, "ConsecutiveAppendsShouldReuse");
addRule(RULESET, "ConsecutiveLiteralAppends");

View File

@ -165,4 +165,22 @@ public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>#1425 Invalid XML Characters in Output</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>The String literal "Tokenizer \ud801\udc1ctest" appears 4 times in this file; the first occurrence is on line 2</message>
</expected-messages>
<code><![CDATA[
public class Duplicate {
String s1 = "Tokenizer \ud801\udc1ctest";
String s2 = "Tokenizer \ud801\udc1ctest";
String s3 = "Tokenizer \ud801\udc1ctest";
String s4 = "Tokenizer \ud801\udc1ctest";
char c = '\uffef';
char c\u0030 = 'a';
}
]]></code>
</test-code>
</test-data>

View File

@ -18,5 +18,6 @@
* [#1428](https://sourceforge.net/p/pmd/bugs/1428/): False positive in UnusedPrivateField when local variable hides member variable
* General
* [#1429](https://sourceforge.net/p/pmd/bugs/1429/): Java - Parse Error: Cast in return expression
* [#1425](https://sourceforge.net/p/pmd/bugs/1425/): Invalid XML Characters in Output
**API Changes:**