Merge branch 'master' into pmd/7.0.x
This commit is contained in:
@ -32,6 +32,8 @@ mvn dependency:build-classpath -DincludeScope=test -Dmdep.outputFile=classpath.t
|
||||
<connection>https://github.com/spring-projects/spring-framework</connection>
|
||||
<tag>v5.0.6.RELEASE</tag>
|
||||
|
||||
<exclude-pattern>.*/build/generated-sources/.*</exclude-pattern>
|
||||
|
||||
<build-command><![CDATA[#!/usr/bin/env bash
|
||||
if test -e classpath.txt; then
|
||||
exit
|
||||
@ -86,7 +88,7 @@ export PATH=$JAVA_HOME/bin:$PATH
|
||||
EOF
|
||||
) | patch
|
||||
|
||||
./gradlew build -x javadoc -x dokka -x asciidoctor -x test -x testNG -x api -x distZip
|
||||
./gradlew build testClasses -x javadoc -x dokka -x asciidoctor -x test -x testNG -x api -x distZip
|
||||
./gradlew createSquishClasspath -q > classpath.txt
|
||||
]]></build-command>
|
||||
<auxclasspath-command>cat classpath.txt</auxclasspath-command>
|
||||
|
@ -119,7 +119,7 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt
|
||||
{% include custom/cli_option_row.html options="--ignore-literal-sequences"
|
||||
description="Ignore sequences of literals (common e.g. in list initializers)"
|
||||
default="false"
|
||||
languages="C#"
|
||||
languages="C#, C++"
|
||||
%}
|
||||
{% include custom/cli_option_row.html options="--ignore-usings"
|
||||
description="Ignore `using` directives in C# when comparing text"
|
||||
|
@ -19,12 +19,21 @@ This is a {{ site.pmd.release_type }} release.
|
||||
|
||||
### New and noteworthy
|
||||
|
||||
#### CPD
|
||||
|
||||
* The C++ module now supports the new option [`--ignore-literal-sequences`](https://pmd.github.io/latest/pmd_userdocs_cpd.html#-ignore-literal-sequences),
|
||||
which can be used to avoid detection of some uninteresting clones. This options has been
|
||||
introduced with PMD 6.30.0 for C# and is now available for C++ as well. See [#2963](https://github.com/pmd/pmd/pull/2963).
|
||||
|
||||
#### New Rules
|
||||
|
||||
* The new Apex rule {% rule "apex/errorprone/OverrideBothEqualsAndHashcode" %} brings the well known Java rule
|
||||
to Apex. In Apex the same principle applies: `equals` and `hashCode` should always be overridden
|
||||
together to ensure collection classes such as Maps and Sets work as expected.
|
||||
|
||||
* The new Visualforce rule {% rule "vf/security/VfHtmlStyleTagXss" %} checks for potential XSS problems
|
||||
when using `<style>` tags on Visualforce pages.
|
||||
|
||||
### Fixed Issues
|
||||
|
||||
* core
|
||||
@ -32,19 +41,39 @@ This is a {{ site.pmd.release_type }} release.
|
||||
* [#2994](https://github.com/pmd/pmd/pull/2994): \[core] Fix code climate severity strings
|
||||
* java-bestpractices
|
||||
* [#575](https://github.com/pmd/pmd/issues/575): \[java] LiteralsFirstInComparisons should consider constant fields
|
||||
* [#2454](https://github.com/pmd/pmd/issues/2454): \[java] UnusedPrivateMethod violation for disabled class in 6.23.0
|
||||
* [#2833](https://github.com/pmd/pmd/issues/2833): \[java] NPE in UseCollectionIsEmptyRule with enums
|
||||
* [#2876](https://github.com/pmd/pmd/issues/2876): \[java] UnusedPrivateField cannot override ignored annotations property
|
||||
* java-codestyle
|
||||
* [#2960](https://github.com/pmd/pmd/issues/2960): \[java] Thread issue in MethodNamingConventionsRule
|
||||
* java-errorprone
|
||||
* [#2976](https://github.com/pmd/pmd/issues/2976): \[java] CompareObjectsWithEquals: FP with array.length
|
||||
* [#2977](https://github.com/pmd/pmd/issues/2977): \[java] 6.30.0 introduces new false positive in CloseResource rule?
|
||||
* [#2979](https://github.com/pmd/pmd/issues/2979): \[java] UseEqualsToCompareStrings: FP with "var" variables
|
||||
* [#3004](https://github.com/pmd/pmd/issues/3004): \[java] UseEqualsToCompareStrings false positive with PMD 6.30.0
|
||||
* [#3062](https://github.com/pmd/pmd/issues/3062): \[java] CloseResource FP with reassigned stream
|
||||
|
||||
### API Changes
|
||||
|
||||
#### Experimental APIs
|
||||
|
||||
* The method {% jdoc !!core::lang.ast.GenericToken#getKind() %} has been added as experimental. This
|
||||
unifies the token interface for both JavaCC and Antlr. The already existing method
|
||||
{% jdoc !!core::cpd.token.AntlrToken#getKind() %} is therefore experimental as well. The
|
||||
returned constant depends on the actual language and might change whenever the grammar
|
||||
of the language is changed.
|
||||
|
||||
### External Contributions
|
||||
|
||||
* [#2666](https://github.com/pmd/pmd/pull/2666): \[swift] Manage swift5 string literals - [kenji21](https://github.com/kenji21)
|
||||
* [#2959](https://github.com/pmd/pmd/pull/2959): \[apex] New Rule: override equals and hashcode rule - [recdevs](https://github.com/recdevs)
|
||||
* [#2963](https://github.com/pmd/pmd/pull/2963): \[cpp] Add option to ignore sequences of literals - [Maikel Steneker](https://github.com/maikelsteneker)
|
||||
* [#2964](https://github.com/pmd/pmd/pull/2964): \[cs] Update C# grammar for additional C# 7 and C# 8 features - [Maikel Steneker](https://github.com/maikelsteneker)
|
||||
* [#2965](https://github.com/pmd/pmd/pull/2965): \[cs] Improvements for ignore sequences of literals functionality - [Maikel Steneker](https://github.com/maikelsteneker)
|
||||
* [#2968](https://github.com/pmd/pmd/pull/2968): \[java] NPE in UseCollectionIsEmptyRule with enums - [foxmason](https://github.com/foxmason)
|
||||
* [#2983](https://github.com/pmd/pmd/pull/2983): \[java] LiteralsFirstInComparisons should consider constant fields - [Ozan Gulle](https://github.com/ozangulle)
|
||||
* [#2994](https://github.com/pmd/pmd/pull/2994): \[core] Fix code climate severity strings - [Vincent Maurin](https://github.com/vmaurin)
|
||||
* [#3005](https://github.com/pmd/pmd/pull/3005): \[vf] \[New Rule] Handle XSS violations that can occur within Html Style tags - [rmohan20](https://github.com/rmohan20)
|
||||
|
||||
{% endtocmaker %}
|
||||
|
||||
|
@ -167,7 +167,16 @@ public class GUI implements CPDListener {
|
||||
|
||||
@Override
|
||||
public boolean canIgnoreLiteralSequences() {
|
||||
return "cs".equals(terseName);
|
||||
if (terseName == null) {
|
||||
return false;
|
||||
}
|
||||
switch(terseName) {
|
||||
case "cpp":
|
||||
case "cs":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package net.sourceforge.pmd.internal.util;
|
||||
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -13,6 +12,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
public final class AssertionUtil {
|
||||
|
||||
private static final Pattern PACKAGE_PATTERN = Pattern.compile("[\\w$]+(\\.[\\w$]+)*|");
|
||||
private static final Pattern BINARY_NAME_PATTERN = Pattern.compile("[\\w$]+(?:\\.[\\w$]+)*(?:\\[])*");
|
||||
|
||||
private AssertionUtil() {
|
||||
// utility class
|
||||
@ -33,7 +33,7 @@ public final class AssertionUtil {
|
||||
}
|
||||
|
||||
public static boolean isJavaBinaryName(CharSequence name) {
|
||||
return name.length() > 0 && PACKAGE_PATTERN.matcher(name).matches();
|
||||
return name.length() > 0 && BINARY_NAME_PATTERN.matcher(name).matches();
|
||||
}
|
||||
|
||||
private static boolean isValidRange(int startInclusive, int endExclusive, int minIndex, int maxIndex) {
|
||||
@ -118,5 +118,4 @@ public final class AssertionUtil {
|
||||
: prefix + ": " + message;
|
||||
return new AssertionError(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.ast;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.internal.util.IteratorUtil;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
|
||||
@ -138,4 +139,16 @@ public interface GenericToken<T extends GenericToken<T>> {
|
||||
return () -> IteratorUtil.generate(from.getPreviousComment(), JavaccToken::getPreviousComment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique integer representing the kind of token this is.
|
||||
* The semantics of this kind depend on the language.
|
||||
*
|
||||
* <p><strong>Note:</strong> This is an experimental API.
|
||||
*
|
||||
* <p>The returned constants can be looked up in the language's "*ParserConstants",
|
||||
* e.g. CppParserConstants or JavaParserConstants. These constants are considered
|
||||
* internal API and may change at any time when the language's grammar is changed.
|
||||
*/
|
||||
@Experimental
|
||||
int getKind();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import java.util.regex.Pattern;
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
|
||||
/**
|
||||
@ -137,6 +138,8 @@ public class AntlrToken implements GenericToken<AntlrToken> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Experimental
|
||||
public int getKind() {
|
||||
return token.getType();
|
||||
}
|
||||
|
@ -116,6 +116,11 @@ public class JavaccToken implements GenericToken<JavaccToken>, Comparable<Javacc
|
||||
return kind == EOF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaccToken getNext() {
|
||||
return next;
|
||||
|
@ -10,4 +10,6 @@ This ruleset contains links to rules that are new in PMD v6.31.0
|
||||
|
||||
<rule ref="category/apex/errorprone.xml/OverrideBothEqualsAndHashcode" />
|
||||
|
||||
<rule ref="category/vf/security.xml/VfHtmlStyleTagXss" />
|
||||
|
||||
</ruleset>
|
||||
|
@ -68,6 +68,11 @@ public class BaseTokenFilterTest {
|
||||
public int getEndColumn() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKind() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class StringTokenManager implements TokenManager<StringToken> {
|
||||
|
@ -12,11 +12,16 @@ import java.util.Properties;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.cpd.internal.JavaCCTokenizer;
|
||||
import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter;
|
||||
import net.sourceforge.pmd.cpd.token.TokenFilter;
|
||||
import net.sourceforge.pmd.lang.TokenManager;
|
||||
import net.sourceforge.pmd.lang.ast.CharStream;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.cpp.ast.CppCharStream;
|
||||
import net.sourceforge.pmd.lang.cpp.ast.CppTokenKinds;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
import net.sourceforge.pmd.lang.cpp.CppTokenManager;
|
||||
import net.sourceforge.pmd.lang.cpp.ast.CppParserConstants;
|
||||
import net.sourceforge.pmd.util.IOUtil;
|
||||
|
||||
/**
|
||||
@ -27,6 +32,7 @@ public class CPPTokenizer extends JavaCCTokenizer {
|
||||
private boolean skipBlocks;
|
||||
private String skipBlocksStart;
|
||||
private String skipBlocksEnd;
|
||||
private boolean ignoreLiteralSequences = false;
|
||||
|
||||
public CPPTokenizer() {
|
||||
setProperties(new Properties()); // set the defaults
|
||||
@ -35,10 +41,10 @@ public class CPPTokenizer extends JavaCCTokenizer {
|
||||
/**
|
||||
* Sets the possible options for the C++ tokenizer.
|
||||
*
|
||||
* @param properties
|
||||
* the properties
|
||||
* @param properties the properties
|
||||
* @see #OPTION_SKIP_BLOCKS
|
||||
* @see #OPTION_SKIP_BLOCKS_PATTERN
|
||||
* @see #OPTION_IGNORE_LITERAL_SEQUENCES
|
||||
*/
|
||||
public void setProperties(Properties properties) {
|
||||
skipBlocks = Boolean.parseBoolean(properties.getProperty(OPTION_SKIP_BLOCKS, Boolean.TRUE.toString()));
|
||||
@ -52,6 +58,8 @@ public class CPPTokenizer extends JavaCCTokenizer {
|
||||
skipBlocksEnd = split[1];
|
||||
}
|
||||
}
|
||||
ignoreLiteralSequences = Boolean.parseBoolean(properties.getProperty(OPTION_IGNORE_LITERAL_SEQUENCES,
|
||||
Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
private String maybeSkipBlocks(String test) throws IOException {
|
||||
@ -97,4 +105,85 @@ public class CPPTokenizer extends JavaCCTokenizer {
|
||||
CharStream charStream = makeCharStream(reader);
|
||||
return makeLexerImpl(charStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TokenFilter getTokenFilter(final TokenManager tokenManager) {
|
||||
return new CppTokenFilter(tokenManager, ignoreLiteralSequences);
|
||||
}
|
||||
|
||||
private static class CppTokenFilter extends JavaCCTokenFilter {
|
||||
private final boolean ignoreLiteralSequences;
|
||||
private GenericToken discardingLiteralsUntil = null;
|
||||
private boolean discardCurrent = false;
|
||||
|
||||
CppTokenFilter(final TokenManager tokenManager, final boolean ignoreLiteralSequences) {
|
||||
super(tokenManager);
|
||||
this.ignoreLiteralSequences = ignoreLiteralSequences;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void analyzeTokens(final GenericToken currentToken, final Iterable<GenericToken> remainingTokens) {
|
||||
discardCurrent = false;
|
||||
skipLiteralSequences(currentToken, remainingTokens);
|
||||
}
|
||||
|
||||
private void skipLiteralSequences(final GenericToken currentToken, final Iterable<GenericToken> remainingTokens) {
|
||||
if (ignoreLiteralSequences) {
|
||||
final int kind = currentToken.getKind();
|
||||
if (isDiscardingLiterals()) {
|
||||
if (currentToken == discardingLiteralsUntil) { // NOPMD - intentional check for reference equality
|
||||
discardingLiteralsUntil = null;
|
||||
discardCurrent = true;
|
||||
}
|
||||
} else if (kind == CppParserConstants.LCURLYBRACE) {
|
||||
final GenericToken finalToken = findEndOfSequenceOfLiterals(remainingTokens);
|
||||
discardingLiteralsUntil = finalToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static GenericToken findEndOfSequenceOfLiterals(final Iterable<GenericToken> remainingTokens) {
|
||||
boolean seenLiteral = false;
|
||||
int braceCount = 0;
|
||||
for (final GenericToken token : remainingTokens) {
|
||||
switch (token.getKind()) {
|
||||
case CppParserConstants.BINARY_INT_LITERAL:
|
||||
case CppParserConstants.DECIMAL_INT_LITERAL:
|
||||
case CppParserConstants.FLOAT_LITERAL:
|
||||
case CppParserConstants.HEXADECIMAL_INT_LITERAL:
|
||||
case CppParserConstants.OCTAL_INT_LITERAL:
|
||||
case CppParserConstants.ZERO:
|
||||
seenLiteral = true;
|
||||
break; // can be skipped; continue to the next token
|
||||
case CppParserConstants.COMMA:
|
||||
break; // can be skipped; continue to the next token
|
||||
case CppParserConstants.LCURLYBRACE:
|
||||
braceCount++;
|
||||
break; // curly braces are allowed, as long as they're balanced
|
||||
case CppParserConstants.RCURLYBRACE:
|
||||
braceCount--;
|
||||
if (braceCount < 0) {
|
||||
// end of the list; skip all contents
|
||||
return seenLiteral ? token : null;
|
||||
} else {
|
||||
// curly braces are not yet balanced; continue to the next token
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// some other token than the expected ones; this is not a sequence of literals
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isDiscardingLiterals() {
|
||||
return discardingLiteralsUntil != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLanguageSpecificDiscarding() {
|
||||
return isDiscardingLiterals() || discardCurrent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +129,18 @@ public class CPPTokenizerTest extends CpdTextComparisonTest {
|
||||
doTest("tabWidth");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongListsOfNumbersAreNotIgnored() {
|
||||
doTest("listOfNumbers");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongListsOfNumbersAreIgnored() {
|
||||
doTest("listOfNumbers", "_ignored", skipLiteralSequences());
|
||||
}
|
||||
|
||||
private static Properties skipBlocks(String skipPattern) {
|
||||
return properties(true, skipPattern);
|
||||
return properties(true, skipPattern, false);
|
||||
}
|
||||
|
||||
private static Properties skipBlocks() {
|
||||
@ -139,15 +148,20 @@ public class CPPTokenizerTest extends CpdTextComparisonTest {
|
||||
}
|
||||
|
||||
private static Properties dontSkipBlocks() {
|
||||
return properties(false, null);
|
||||
return properties(false, null, false);
|
||||
}
|
||||
|
||||
private static Properties properties(boolean skipBlocks, String skipPattern) {
|
||||
private static Properties skipLiteralSequences() {
|
||||
return properties(false, null, true);
|
||||
}
|
||||
|
||||
private static Properties properties(boolean skipBlocks, String skipPattern, boolean skipLiteralSequences) {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty(Tokenizer.OPTION_SKIP_BLOCKS, Boolean.toString(skipBlocks));
|
||||
if (skipPattern != null) {
|
||||
properties.setProperty(Tokenizer.OPTION_SKIP_BLOCKS_PATTERN, skipPattern);
|
||||
}
|
||||
properties.setProperty(Tokenizer.OPTION_IGNORE_LITERAL_SEQUENCES, Boolean.toString(skipLiteralSequences));
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
26
pmd-cpp/src/test/resources/net/sourceforge/pmd/lang/cpp/cpd/testdata/listOfNumbers.cpp
vendored
Normal file
26
pmd-cpp/src/test/resources/net/sourceforge/pmd/lang/cpp/cpd/testdata/listOfNumbers.cpp
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
#include <iostream>
|
||||
int main() {
|
||||
int a[50] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
double b[14] = {
|
||||
157, // decimal literal
|
||||
0377, // octal literal
|
||||
36'000'000, // literal with digit separators
|
||||
0x3fff, // hexadecimal literal
|
||||
0X3FFF, // same hexadecimal literal
|
||||
328u, // unsigned value
|
||||
0x7FFFFFL, // long value
|
||||
0776745ul, // unsigned long value
|
||||
18.46, // double with number after decimal point
|
||||
38., // double without number after decimal point
|
||||
18.46e0, // double with exponent
|
||||
18.46e1, // double with exponent
|
||||
0B001101, // C++ 14 binary literal
|
||||
0b000001, // C++ 14 binary literal
|
||||
};
|
||||
int c[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}}; // multi-dimensional array
|
||||
int d[3] = {a, a, a}; // identifiers should not be filtered out
|
||||
int e[1][3] = {{a, a, a}}; // identifiers in multi-dimensional array
|
||||
int f[1] = {main()}; // method invocations should not be filtered out
|
||||
int g[1][1] = {{main()}}; // method invocation in multi-dimensional array
|
||||
return 0;
|
||||
}
|
284
pmd-cpp/src/test/resources/net/sourceforge/pmd/lang/cpp/cpd/testdata/listOfNumbers.txt
vendored
Normal file
284
pmd-cpp/src/test/resources/net/sourceforge/pmd/lang/cpp/cpd/testdata/listOfNumbers.txt
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
[Image] or [Truncated image[ Bcol Ecol
|
||||
L2
|
||||
[int] 1 3
|
||||
[main] 5 8
|
||||
[(] 9 9
|
||||
[)] 10 10
|
||||
[{] 12 12
|
||||
L3
|
||||
[int] 3 5
|
||||
[a] 7 7
|
||||
[\[] 8 8
|
||||
[50] 9 10
|
||||
[\]] 11 11
|
||||
[=] 13 13
|
||||
[{] 15 15
|
||||
[0] 16 16
|
||||
[,] 17 17
|
||||
[0] 18 18
|
||||
[,] 19 19
|
||||
[0] 20 20
|
||||
[,] 21 21
|
||||
[0] 22 22
|
||||
[,] 23 23
|
||||
[0] 24 24
|
||||
[,] 25 25
|
||||
[0] 26 26
|
||||
[,] 27 27
|
||||
[0] 28 28
|
||||
[,] 29 29
|
||||
[0] 30 30
|
||||
[,] 31 31
|
||||
[0] 32 32
|
||||
[,] 33 33
|
||||
[0] 34 34
|
||||
[,] 35 35
|
||||
[0] 36 36
|
||||
[,] 37 37
|
||||
[0] 38 38
|
||||
[,] 39 39
|
||||
[0] 40 40
|
||||
[,] 41 41
|
||||
[0] 42 42
|
||||
[,] 43 43
|
||||
[0] 44 44
|
||||
[,] 45 45
|
||||
[0] 46 46
|
||||
[,] 47 47
|
||||
[0] 48 48
|
||||
[,] 49 49
|
||||
[0] 50 50
|
||||
[,] 51 51
|
||||
[0] 52 52
|
||||
[,] 53 53
|
||||
[0] 54 54
|
||||
[,] 55 55
|
||||
[0] 56 56
|
||||
[,] 57 57
|
||||
[0] 58 58
|
||||
[,] 59 59
|
||||
[0] 60 60
|
||||
[,] 61 61
|
||||
[0] 62 62
|
||||
[,] 63 63
|
||||
[0] 64 64
|
||||
[,] 65 65
|
||||
[0] 66 66
|
||||
[,] 67 67
|
||||
[0] 68 68
|
||||
[,] 69 69
|
||||
[0] 70 70
|
||||
[,] 71 71
|
||||
[0] 72 72
|
||||
[,] 73 73
|
||||
[0] 74 74
|
||||
[,] 75 75
|
||||
[0] 76 76
|
||||
[,] 77 77
|
||||
[0] 78 78
|
||||
[,] 79 79
|
||||
[0] 80 80
|
||||
[,] 81 81
|
||||
[0] 82 82
|
||||
[,] 83 83
|
||||
[0] 84 84
|
||||
[,] 85 85
|
||||
[0] 86 86
|
||||
[,] 87 87
|
||||
[0] 88 88
|
||||
[,] 89 89
|
||||
[0] 90 90
|
||||
[,] 91 91
|
||||
[0] 92 92
|
||||
[,] 93 93
|
||||
[0] 94 94
|
||||
[,] 95 95
|
||||
[0] 96 96
|
||||
[,] 97 97
|
||||
[0] 98 98
|
||||
[,] 99 99
|
||||
[0] 100 100
|
||||
[,] 101 101
|
||||
[0] 102 102
|
||||
[,] 103 103
|
||||
[0] 104 104
|
||||
[,] 105 105
|
||||
[0] 106 106
|
||||
[,] 107 107
|
||||
[0] 108 108
|
||||
[,] 109 109
|
||||
[0] 110 110
|
||||
[,] 111 111
|
||||
[0] 112 112
|
||||
[,] 113 113
|
||||
[0] 114 114
|
||||
[}] 115 115
|
||||
[;] 116 116
|
||||
L4
|
||||
[double] 3 8
|
||||
[b] 10 10
|
||||
[\[] 11 11
|
||||
[14] 12 13
|
||||
[\]] 14 14
|
||||
[=] 16 16
|
||||
[{] 18 18
|
||||
L5
|
||||
[157] 5 7
|
||||
[,] 8 8
|
||||
L6
|
||||
[0377] 5 8
|
||||
[,] 9 9
|
||||
L7
|
||||
[36'000'000] 5 14
|
||||
[,] 15 15
|
||||
L8
|
||||
[0x3fff] 5 10
|
||||
[,] 11 11
|
||||
L9
|
||||
[0X3FFF] 5 10
|
||||
[,] 11 11
|
||||
L10
|
||||
[328u] 5 8
|
||||
[,] 9 9
|
||||
L11
|
||||
[0x7FFFFFL] 5 13
|
||||
[,] 14 14
|
||||
L12
|
||||
[0776745ul] 5 13
|
||||
[,] 14 14
|
||||
L13
|
||||
[18.46] 5 9
|
||||
[,] 10 10
|
||||
L14
|
||||
[38.] 5 7
|
||||
[,] 8 8
|
||||
L15
|
||||
[18.46e0] 5 11
|
||||
[,] 12 12
|
||||
L16
|
||||
[18.46e1] 5 11
|
||||
[,] 12 12
|
||||
L17
|
||||
[0B001101] 5 12
|
||||
[,] 13 13
|
||||
L18
|
||||
[0b000001] 5 12
|
||||
[,] 13 13
|
||||
L19
|
||||
[}] 3 3
|
||||
[;] 4 4
|
||||
L20
|
||||
[int] 3 5
|
||||
[c] 7 7
|
||||
[\[] 8 8
|
||||
[3] 9 9
|
||||
[\]] 10 10
|
||||
[\[] 11 11
|
||||
[4] 12 12
|
||||
[\]] 13 13
|
||||
[=] 15 15
|
||||
[{] 17 17
|
||||
[{] 18 18
|
||||
[0] 19 19
|
||||
[,] 20 20
|
||||
[1] 21 21
|
||||
[,] 22 22
|
||||
[2] 23 23
|
||||
[,] 24 24
|
||||
[3] 25 25
|
||||
[}] 26 26
|
||||
[,] 27 27
|
||||
[{] 28 28
|
||||
[4] 29 29
|
||||
[,] 30 30
|
||||
[5] 31 31
|
||||
[,] 32 32
|
||||
[6] 33 33
|
||||
[,] 34 34
|
||||
[7] 35 35
|
||||
[}] 36 36
|
||||
[,] 37 37
|
||||
[{] 38 38
|
||||
[8] 39 39
|
||||
[,] 40 40
|
||||
[9] 41 41
|
||||
[,] 42 42
|
||||
[10] 43 44
|
||||
[,] 45 45
|
||||
[11] 46 47
|
||||
[}] 48 48
|
||||
[}] 49 49
|
||||
[;] 50 50
|
||||
L21
|
||||
[int] 3 5
|
||||
[d] 7 7
|
||||
[\[] 8 8
|
||||
[3] 9 9
|
||||
[\]] 10 10
|
||||
[=] 12 12
|
||||
[{] 14 14
|
||||
[a] 15 15
|
||||
[,] 16 16
|
||||
[a] 18 18
|
||||
[,] 19 19
|
||||
[a] 21 21
|
||||
[}] 22 22
|
||||
[;] 23 23
|
||||
L22
|
||||
[int] 3 5
|
||||
[e] 7 7
|
||||
[\[] 8 8
|
||||
[1] 9 9
|
||||
[\]] 10 10
|
||||
[\[] 11 11
|
||||
[3] 12 12
|
||||
[\]] 13 13
|
||||
[=] 15 15
|
||||
[{] 17 17
|
||||
[{] 18 18
|
||||
[a] 19 19
|
||||
[,] 20 20
|
||||
[a] 22 22
|
||||
[,] 23 23
|
||||
[a] 25 25
|
||||
[}] 26 26
|
||||
[}] 27 27
|
||||
[;] 28 28
|
||||
L23
|
||||
[int] 3 5
|
||||
[f] 7 7
|
||||
[\[] 8 8
|
||||
[1] 9 9
|
||||
[\]] 10 10
|
||||
[=] 12 12
|
||||
[{] 14 14
|
||||
[main] 15 18
|
||||
[(] 19 19
|
||||
[)] 20 20
|
||||
[}] 21 21
|
||||
[;] 22 22
|
||||
L24
|
||||
[int] 3 5
|
||||
[g] 7 7
|
||||
[\[] 8 8
|
||||
[1] 9 9
|
||||
[\]] 10 10
|
||||
[\[] 11 11
|
||||
[1] 12 12
|
||||
[\]] 13 13
|
||||
[=] 15 15
|
||||
[{] 17 17
|
||||
[{] 18 18
|
||||
[main] 19 22
|
||||
[(] 23 23
|
||||
[)] 24 24
|
||||
[}] 25 25
|
||||
[}] 26 26
|
||||
[;] 27 27
|
||||
L25
|
||||
[return] 3 8
|
||||
[0] 10 10
|
||||
[;] 11 11
|
||||
L26
|
||||
[}] 1 1
|
||||
EOF
|
108
pmd-cpp/src/test/resources/net/sourceforge/pmd/lang/cpp/cpd/testdata/listOfNumbers_ignored.txt
vendored
Normal file
108
pmd-cpp/src/test/resources/net/sourceforge/pmd/lang/cpp/cpd/testdata/listOfNumbers_ignored.txt
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
[Image] or [Truncated image[ Bcol Ecol
|
||||
L2
|
||||
[int] 1 3
|
||||
[main] 5 8
|
||||
[(] 9 9
|
||||
[)] 10 10
|
||||
[{] 12 12
|
||||
L3
|
||||
[int] 3 5
|
||||
[a] 7 7
|
||||
[\[] 8 8
|
||||
[50] 9 10
|
||||
[\]] 11 11
|
||||
[=] 13 13
|
||||
[;] 116 116
|
||||
L4
|
||||
[double] 3 8
|
||||
[b] 10 10
|
||||
[\[] 11 11
|
||||
[14] 12 13
|
||||
[\]] 14 14
|
||||
[=] 16 16
|
||||
L19
|
||||
[;] 4 4
|
||||
L20
|
||||
[int] 3 5
|
||||
[c] 7 7
|
||||
[\[] 8 8
|
||||
[3] 9 9
|
||||
[\]] 10 10
|
||||
[\[] 11 11
|
||||
[4] 12 12
|
||||
[\]] 13 13
|
||||
[=] 15 15
|
||||
[;] 50 50
|
||||
L21
|
||||
[int] 3 5
|
||||
[d] 7 7
|
||||
[\[] 8 8
|
||||
[3] 9 9
|
||||
[\]] 10 10
|
||||
[=] 12 12
|
||||
[{] 14 14
|
||||
[a] 15 15
|
||||
[,] 16 16
|
||||
[a] 18 18
|
||||
[,] 19 19
|
||||
[a] 21 21
|
||||
[}] 22 22
|
||||
[;] 23 23
|
||||
L22
|
||||
[int] 3 5
|
||||
[e] 7 7
|
||||
[\[] 8 8
|
||||
[1] 9 9
|
||||
[\]] 10 10
|
||||
[\[] 11 11
|
||||
[3] 12 12
|
||||
[\]] 13 13
|
||||
[=] 15 15
|
||||
[{] 17 17
|
||||
[{] 18 18
|
||||
[a] 19 19
|
||||
[,] 20 20
|
||||
[a] 22 22
|
||||
[,] 23 23
|
||||
[a] 25 25
|
||||
[}] 26 26
|
||||
[}] 27 27
|
||||
[;] 28 28
|
||||
L23
|
||||
[int] 3 5
|
||||
[f] 7 7
|
||||
[\[] 8 8
|
||||
[1] 9 9
|
||||
[\]] 10 10
|
||||
[=] 12 12
|
||||
[{] 14 14
|
||||
[main] 15 18
|
||||
[(] 19 19
|
||||
[)] 20 20
|
||||
[}] 21 21
|
||||
[;] 22 22
|
||||
L24
|
||||
[int] 3 5
|
||||
[g] 7 7
|
||||
[\[] 8 8
|
||||
[1] 9 9
|
||||
[\]] 10 10
|
||||
[\[] 11 11
|
||||
[1] 12 12
|
||||
[\]] 13 13
|
||||
[=] 15 15
|
||||
[{] 17 17
|
||||
[{] 18 18
|
||||
[main] 19 22
|
||||
[(] 23 23
|
||||
[)] 24 24
|
||||
[}] 25 25
|
||||
[}] 26 26
|
||||
[;] 27 27
|
||||
L25
|
||||
[return] 3 8
|
||||
[0] 10 10
|
||||
[;] 11 11
|
||||
L26
|
||||
[}] 1 1
|
||||
EOF
|
@ -22,9 +22,17 @@ public class CsTokenizer extends AntlrTokenizer {
|
||||
private boolean ignoreUsings = false;
|
||||
private boolean ignoreLiteralSequences = false;
|
||||
|
||||
/**
|
||||
* Sets the possible options for the C# tokenizer.
|
||||
*
|
||||
* @param properties the properties
|
||||
* @see #IGNORE_USINGS
|
||||
* @see #OPTION_IGNORE_LITERAL_SEQUENCES
|
||||
*/
|
||||
public void setProperties(Properties properties) {
|
||||
ignoreUsings = Boolean.parseBoolean(properties.getProperty(IGNORE_USINGS, "false"));
|
||||
ignoreLiteralSequences = Boolean.parseBoolean(properties.getProperty(OPTION_IGNORE_LITERAL_SEQUENCES, "false"));
|
||||
ignoreUsings = Boolean.parseBoolean(properties.getProperty(IGNORE_USINGS, Boolean.FALSE.toString()));
|
||||
ignoreLiteralSequences = Boolean.parseBoolean(properties.getProperty(OPTION_IGNORE_LITERAL_SEQUENCES,
|
||||
Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
public void setIgnoreUsings(boolean ignoreUsings) {
|
||||
|
@ -4,6 +4,10 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.NodeStream;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
|
||||
|
||||
@ -28,9 +32,12 @@ public interface Annotatable extends JavaNode {
|
||||
/**
|
||||
* Returns true if an annotation with the given qualified name is
|
||||
* applied to this node.
|
||||
*
|
||||
* @param annotQualifiedName
|
||||
* Note: for now, canonical names are tolerated, this may be changed in PMD 7.
|
||||
*/
|
||||
default boolean isAnnotationPresent(String annotQualifiedName) {
|
||||
return getDeclaredAnnotations().any(t -> TypeTestUtil.isA(annotQualifiedName, t));
|
||||
return getDeclaredAnnotations().any(t -> TypeTestUtil.isA(StringUtils.deleteWhitespace(annotQualifiedName), t));
|
||||
}
|
||||
|
||||
|
||||
@ -41,4 +48,33 @@ public interface Annotatable extends JavaNode {
|
||||
default boolean isAnnotationPresent(Class<?> type) {
|
||||
return getDeclaredAnnotations().any(t -> TypeTestUtil.isA(type, t));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a specific annotation on this node, or null if absent.
|
||||
*
|
||||
* @param binaryName
|
||||
* Binary name of the annotation type.
|
||||
* Note: for now, canonical names are tolerated, this may be changed in PMD 7.
|
||||
*/
|
||||
default ASTAnnotation getAnnotation(String binaryName) {
|
||||
return getDeclaredAnnotations().filter(t -> TypeTestUtil.isA(StringUtils.deleteWhitespace(binaryName), t)).first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any annotation is present on this node.
|
||||
*
|
||||
* @param binaryNames
|
||||
* Collection that contains binary names of annotations.
|
||||
* Note: for now, canonical names are tolerated, this may be changed in PMD 7.
|
||||
* @return <code>true</code> if any annotation is present on this node, else <code>false</code>
|
||||
*/
|
||||
default boolean isAnyAnnotationPresent(Collection<String> binaryNames) {
|
||||
for (String annotQualifiedName : binaryNames) {
|
||||
if (isAnnotationPresent(annotQualifiedName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -40,16 +40,18 @@ public class UnusedPrivateFieldRule extends AbstractLombokAwareRule {
|
||||
|
||||
@Override
|
||||
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
|
||||
boolean classHasLombok = hasLombokAnnotation(node);
|
||||
if (hasIgnoredAnnotation(node)) {
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope()
|
||||
.getDeclarations(VariableNameDeclaration.class);
|
||||
.getDeclarations(VariableNameDeclaration.class);
|
||||
for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : vars.entrySet()) {
|
||||
VariableNameDeclaration decl = entry.getKey();
|
||||
AccessNode accessNodeParent = decl.getAccessNodeParent();
|
||||
if (!accessNodeParent.isPrivate() || isOK(decl.getImage()) || classHasLombok
|
||||
|| hasIgnoredAnnotation((Annotatable) accessNodeParent)
|
||||
|| hasIgnoredAnnotation(node)) {
|
||||
if (!accessNodeParent.isPrivate()
|
||||
|| isOK(decl.getImage())
|
||||
|| hasIgnoredAnnotation((Annotatable) accessNodeParent)) {
|
||||
continue;
|
||||
}
|
||||
if (!actuallyUsed(entry.getValue())) {
|
||||
|
@ -167,7 +167,7 @@ public class CloseResourceRule extends AbstractJavaRule {
|
||||
} else if (shouldVarOfTypeBeClosedInMethod(resVar, resVarType, methodOrConstructor)) {
|
||||
reportedVarNames.add(resVar.getVarId().getName());
|
||||
addCloseResourceViolation(resVar.getVarId(), resVarType, data);
|
||||
} else {
|
||||
} else if (isNotAllowedResourceType(resVarType)) {
|
||||
ASTStatementExpression reassigningStatement = getFirstReassigningStatementBeforeBeingClosed(resVar, methodOrConstructor);
|
||||
if (reassigningStatement != null) {
|
||||
reportedVarNames.add(resVar.getVarId().getName());
|
||||
|
@ -12,6 +12,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTForInit;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
|
||||
@ -21,6 +22,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.ast.TypeNode;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
|
||||
|
||||
@ -65,11 +68,27 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractJavaRule {
|
||||
private boolean notCollectionAccess(ASTAllocationExpression node) {
|
||||
if (node.getNthParent(4) instanceof ASTArgumentList && node.getNthParent(8) instanceof ASTStatementExpression) {
|
||||
ASTStatementExpression statement = (ASTStatementExpression) node.getNthParent(8);
|
||||
return !TypeTestUtil.isA(Collection.class, statement);
|
||||
return !isCallOnReceiverOfType(Collection.class, statement);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isCallOnReceiverOfType(Class<?> receiverType, JavaNode expression) {
|
||||
if ((expression instanceof ASTExpression || expression instanceof ASTStatementExpression)
|
||||
&& expression.getNumChildren() == 1) {
|
||||
expression = expression.getChild(0);
|
||||
}
|
||||
int numChildren = expression.getNumChildren();
|
||||
if (expression instanceof ASTPrimaryExpression && numChildren >= 2) {
|
||||
JavaNode lastChild = expression.getChild(numChildren - 1);
|
||||
if (lastChild instanceof ASTPrimarySuffix && ((ASTPrimarySuffix) lastChild).isArguments()) {
|
||||
JavaNode receiverExpr = expression.getChild(numChildren - 2);
|
||||
return receiverExpr instanceof TypeNode && TypeTestUtil.isA(receiverType, (TypeNode) receiverExpr);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean notBreakFollowing(ASTAllocationExpression node) {
|
||||
ASTStatement statement = node.getFirstParentOfType(ASTStatement.class);
|
||||
if (statement != null) {
|
||||
|
@ -111,6 +111,7 @@ public final class TypeTestUtil {
|
||||
*/
|
||||
public static boolean isA(final @NonNull String canonicalName, final @Nullable TypeNode node) {
|
||||
AssertionUtil.requireParamNotNull("canonicalName", (Object) canonicalName);
|
||||
AssertionUtil.assertValidJavaBinaryName(canonicalName);
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
@ -248,6 +249,7 @@ public final class TypeTestUtil {
|
||||
* @see #isExactlyA(Class, TypeNode)
|
||||
*/
|
||||
public static boolean isExactlyA(@NonNull String canonicalName, final @Nullable TypeNode node) {
|
||||
AssertionUtil.assertValidJavaBinaryName(canonicalName);
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -8,8 +8,11 @@
|
||||
<description>OK, guard is here - log4j</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
public class Foo {
|
||||
private static final Logger logger = Logger.getLogger(Foo.class);
|
||||
private static final Logger logger = LogManager.getLogger(Foo.class);
|
||||
|
||||
private void foo() {
|
||||
if ( logger.isDebugEnabled() )
|
||||
@ -23,6 +26,9 @@ public class Foo {
|
||||
<description>ok, no error expected - apache commons logging</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
public class Test {
|
||||
private static final Log __log = LogFactory.getLog(Test.class);
|
||||
public void test() {
|
||||
@ -39,6 +45,8 @@ public class Test {
|
||||
<description>Guarded call - OK - java util</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Foo {
|
||||
|
||||
private void foo(Logger logger) {
|
||||
@ -54,8 +62,11 @@ public class Foo {
|
||||
<description>KO, missing guard 1 - log4j</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
public class Foo {
|
||||
private static final Logger logger = Logger.getLogger(Foo.class);
|
||||
private static final Logger logger = LogManager.getLogger(Foo.class);
|
||||
|
||||
private void foo() {
|
||||
logger.debug("Debug statement" + "");
|
||||
@ -70,8 +81,11 @@ public class Foo {
|
||||
<rule-property name="guardsMethods">isDebugEnabled,isTraceEnabled</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
public class Foo {
|
||||
private static final Logger logger = Logger.getLogger(Foo.class);
|
||||
private static final Logger logger = LogManager.getLogger(Foo.class);
|
||||
|
||||
private void foo() {
|
||||
logger.debug("Debug statement" + "");
|
||||
@ -88,6 +102,9 @@ public class Foo {
|
||||
<description>Complex logging without guard - apache commons logging</description>
|
||||
<expected-problems>2</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
public class Test {
|
||||
private static final Log __log = LogFactory.getLog(Test.class);
|
||||
public void test() {
|
||||
@ -111,6 +128,9 @@ public class Test {
|
||||
<description>Complex logging with misplaced guard - apache commons logging</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.loggong.Log;
|
||||
|
||||
public class Test {
|
||||
private static final Log __log = LogFactory.getLog(Test.class);
|
||||
public void test() {
|
||||
@ -129,6 +149,8 @@ public class Test {
|
||||
<description>Unguarded call - KO - java util</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Foo {
|
||||
|
||||
private void foo(Logger logger) {
|
||||
@ -142,8 +164,14 @@ public class Foo {
|
||||
<description>ok #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
public class Test {
|
||||
public void test() {
|
||||
public void test(String mymarker) {
|
||||
final Logger logger = LogManager.getLogger(Test.class);
|
||||
final Marker m = MarkerManager.getMarker(mymarker);
|
||||
if (logger.isDebugEnabled(m)) {
|
||||
logger.debug(m, message + "");
|
||||
@ -159,9 +187,16 @@ public class Test {
|
||||
<test-code>
|
||||
<description>violation - wrong guard #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2</description>
|
||||
<expected-problems>2</expected-problems>
|
||||
<expected-linenumbers>11,14</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
public class Test {
|
||||
public void test() {
|
||||
public void test(String mymarker) {
|
||||
final Logger logger = LogManager.getLogger(Test.class);
|
||||
final Marker m = MarkerManager.getMarker(mymarker);
|
||||
if (logger.isTraceEnabled(m)) { // should have been isDebugEnabled
|
||||
logger.debug(m, message + "");
|
||||
@ -177,9 +212,16 @@ public class Test {
|
||||
<test-code>
|
||||
<description>violation - no if #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>11</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
public class Test {
|
||||
public void test() {
|
||||
public void test(String mymarker) {
|
||||
final Logger logger = LogManager.getLogger(Test.class);
|
||||
final Marker m = MarkerManager.getMarker(mymarker);
|
||||
logger.isDebugEnabled(m); // must be within an if
|
||||
logger.debug(m, message + "");
|
||||
@ -192,6 +234,9 @@ public class Test {
|
||||
<description>#1224 GuardDebugLogging broken in 5.1.1 - missing additive statement check in log statement - apache commons logging</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
public class Test {
|
||||
private static final Log __log = LogFactory.getLog(Test.class);
|
||||
public void test() {
|
||||
@ -214,7 +259,11 @@ public class Test {
|
||||
<description>#1341 pmd:GuardDebugLogging violates LOGGER.debug with format "{}" - slf4j</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class GuardDebugFalsePositive {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("GuardDebugFalsePositive");
|
||||
public void test() {
|
||||
String tempSelector = "a";
|
||||
LOGGER.debug("MessageSelector={}" , tempSelector);
|
||||
|
@ -636,6 +636,18 @@ public class Foo {
|
||||
<code><![CDATA[
|
||||
import lombok.EqualsAndHashCode;
|
||||
@EqualsAndHashCode
|
||||
public class Foo {
|
||||
private String bar;
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#2673 UnusedPrivateField false positive with lombok annotation EqualsAndHashCode</description>
|
||||
<rule-property name="ignoredAnnotations">lombok.Getter|lombok.Data</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<code><![CDATA[
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@RequiredArgsConstructor
|
||||
public class Foo {
|
||||
private String bar;
|
||||
}
|
||||
|
@ -1640,6 +1640,21 @@ public class UnusedPrivateMethodFP {
|
||||
private void privateBooleanMethod(String s, boolean isTrue) {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#2454 [java] UnusedPrivateMethod violation for disabled annotation in 6.23.0</description>
|
||||
<!-- Note: weird whitespace in the property is important -->
|
||||
<rule-property name="ignoredAnnotations">java
|
||||
.lang.Deprecated</rule-property>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class OOO {
|
||||
@Deprecated
|
||||
private void shutdown() {
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
@ -7,9 +7,10 @@
|
||||
<test-code>
|
||||
<description>fail, == 0</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>4</expected-linenumbers>
|
||||
<expected-linenumbers>5</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst) {
|
||||
if(lst.size() == 0){
|
||||
@ -25,7 +26,8 @@ public class Foo {
|
||||
<description>ok, isEmpty</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst) {
|
||||
if(lst.isEmpty()){
|
||||
@ -40,9 +42,10 @@ public class Foo {
|
||||
<test-code>
|
||||
<description>fail, != 0</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>4</expected-linenumbers>
|
||||
<expected-linenumbers>5</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst) {
|
||||
if(lst.size() != 0){
|
||||
@ -58,7 +61,8 @@ public class Foo {
|
||||
<description>ok, !isEmpty</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst) {
|
||||
if(!lst.isEmpty()){
|
||||
@ -73,9 +77,10 @@ public class Foo {
|
||||
<test-code>
|
||||
<description>fail, != 0</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>4</expected-linenumbers>
|
||||
<expected-linenumbers>5</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst, boolean b) {
|
||||
if(lst.size() == 0 && b){
|
||||
@ -91,7 +96,8 @@ public class Foo {
|
||||
<description>ok, !isEmpty</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst, boolean b) {
|
||||
if(lst.isEmpty() && b){
|
||||
@ -106,9 +112,10 @@ public class Foo {
|
||||
<test-code>
|
||||
<description>fail, 0 ==</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>4</expected-linenumbers>
|
||||
<expected-linenumbers>5</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst) {
|
||||
if(0 == lst.size()){
|
||||
@ -123,9 +130,10 @@ public class Foo {
|
||||
<test-code>
|
||||
<description>fail, > 0</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>4</expected-linenumbers>
|
||||
<expected-linenumbers>5</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static boolean bar(List lst) {
|
||||
if(lst.size() > 0){
|
||||
@ -141,7 +149,8 @@ public class Foo {
|
||||
<description>ok, in expression</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class Foo {
|
||||
public static int modulo = 2;
|
||||
public static boolean bar(List lst) {
|
||||
@ -158,7 +167,8 @@ public class Foo {
|
||||
<description>ok, in expression</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
|
||||
public class Foo {
|
||||
final Map map;
|
||||
public boolean bar(Foo other) {
|
||||
@ -173,8 +183,8 @@ public class Foo {
|
||||
|
||||
<test-code>
|
||||
<description>#1214 UseCollectionIsEmpty misses some usage</description>
|
||||
<expected-problems>5</expected-problems>
|
||||
<expected-linenumbers>25,28,31,34,37</expected-linenumbers>
|
||||
<expected-problems>10</expected-problems>
|
||||
<expected-linenumbers>8,11,14,17,20,23,26,29,32,35</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
|
||||
@ -182,24 +192,22 @@ public class TestIsEmpty {
|
||||
public static void main(String args[]) {
|
||||
ArrayList<String> testObject = new ArrayList<String>();
|
||||
|
||||
// These ones are flagged
|
||||
// if (testObject.size() == 0) {
|
||||
// System.out.println("List is empty");
|
||||
// }
|
||||
// if (testObject.size() != 0) {
|
||||
// System.out.println("List is empty");
|
||||
// }
|
||||
// if (0 == testObject.size()) {
|
||||
// System.out.println("List is empty");
|
||||
// }
|
||||
// if (0 != testObject.size()) {
|
||||
// System.out.println("List is empty");
|
||||
// }
|
||||
// if (testObject.size() > 0) {
|
||||
// System.out.println("List is empty");
|
||||
// }
|
||||
|
||||
// These ones are not flagged, but should be flagged
|
||||
// These should be flagged
|
||||
if (testObject.size() == 0) {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
if (testObject.size() != 0) {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
if (0 == testObject.size()) {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
if (0 != testObject.size()) {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
if (testObject.size() > 0) {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
if (testObject.size() < 1) {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
@ -242,7 +250,7 @@ public class IsEmptyTest {
|
||||
public static void main(String args[]) {
|
||||
ArrayList<String> testObject = new ArrayList<String>();
|
||||
|
||||
// these should be flagged (as they are equivalent to == 0) and are
|
||||
// These should be flagged
|
||||
if (testObject.size() < 1) { // line 8
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
@ -250,7 +258,7 @@ public class IsEmptyTest {
|
||||
System.out.println("List is empty");
|
||||
}
|
||||
|
||||
// these should not be flagged, and are not
|
||||
// These should not be flagged
|
||||
if (testObject.size() <= 1) { // line 16
|
||||
System.out.println("List may or may not be empty");
|
||||
}
|
||||
@ -258,7 +266,7 @@ public class IsEmptyTest {
|
||||
System.out.println("List may or may not be empty");
|
||||
}
|
||||
|
||||
// these should be flagged (as they are equivalent to != 0) and are not
|
||||
// These should be flagged (as they are equivalent to != 0) and are not
|
||||
if (testObject.size() >= 1) { // line 24
|
||||
System.out.println("List is not empty");
|
||||
}
|
||||
@ -266,7 +274,7 @@ public class IsEmptyTest {
|
||||
System.out.println("List is not empty");
|
||||
}
|
||||
|
||||
// these should not be flagged, yet are
|
||||
// These should not be flagged
|
||||
if (testObject.size() > 1) { // line 32
|
||||
System.out.println("List is not empty, but not all non-empty lists will trigger this");
|
||||
}
|
||||
@ -295,6 +303,7 @@ public class IsEmptyTest {
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.*;
|
||||
|
||||
public class PMDIsEmptyFalsePositive {
|
||||
public void falsePositive() {
|
||||
Collection<String> c = new ArrayList<String>();
|
||||
@ -359,6 +368,7 @@ public class Foo {
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2542 UseCollectionIsEmpty can not detect the case foo.bar().size()</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
@ -379,6 +389,92 @@ public class Foo {
|
||||
throw new RuntimeException("Empty list");
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2833 NPE in UseCollectionIsEmptyRule with enums</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public enum ComponentSize {
|
||||
|
||||
S("s");
|
||||
|
||||
private String size;
|
||||
|
||||
ComponentSize(String size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2833 NPE in UseCollectionIsEmptyRule with enums (sanity check)</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>11</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.List;
|
||||
|
||||
public enum ComponentSize {
|
||||
|
||||
S("s");
|
||||
|
||||
private List<String> list;
|
||||
private String size;
|
||||
|
||||
ComponentSize(String size) {
|
||||
if (list.size() == 0) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2833 test with records</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>5</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.List;
|
||||
|
||||
public record CollectionRecord(List<String> theList) {
|
||||
public CollectionRecord {
|
||||
if (theList.size() == 0) throw new IllegalArgumentException("empty list");
|
||||
if (theList.isEmpty()) throw new IllegalArgumentException("empty list");
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
<source-type>java 15-preview</source-type>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2833 test local var</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>6</expected-linenumbers>
|
||||
<code><![CDATA[
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Foo {
|
||||
public static void main(String[] args) {
|
||||
var theList = new ArrayList<String>();
|
||||
if (theList.size() == 0) throw new IllegalArgumentException("empty list");
|
||||
if (theList.isEmpty()) throw new IllegalArgumentException("empty list");
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
@ -1135,7 +1135,8 @@ public class CloseResourcePrintWriter {
|
||||
<expected-linenumbers>7,8,10</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>Ensure that resources like this FileInputStream object are closed after use</message>
|
||||
<message>Ensure that resources like this Scanner object are closed after use</message>
|
||||
<!-- Note: it picks up on System.in -->
|
||||
<message>Ensure that resources like this InputStream object are closed after use</message>
|
||||
<message>Ensure that resources like this FileInputStream object are closed after use</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
@ -1429,7 +1430,7 @@ public class CloseResourceWithVar {
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>#2764 false-negative when re-assigning connection</description>
|
||||
<rule-property name="types">java.sql.Connection,java.sql.Statement,java.sql.ResultSet</rule-property>
|
||||
@ -1473,6 +1474,42 @@ public class Foo {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2977 6.30.0 introduces new false positive in CloseResource rule</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.io.File;
|
||||
|
||||
public class Foo {
|
||||
void bar() {
|
||||
File file = new File("name", "r");
|
||||
try {
|
||||
boolean isHundredBytes = file.length() == 100;
|
||||
} finally {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#3062 CloseResource FP with reassigned stream</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Foo {
|
||||
void bar() {
|
||||
Stream<T> stream = Stream.of(2);
|
||||
if (condition) {
|
||||
stream = stream.skip(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
@ -378,4 +378,36 @@ public class ClassWithFields {
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>#2976 FP with array length</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class C0 {
|
||||
public static byte[] myArrayFunc(byte[] a1, byte[] a2) {
|
||||
if (a1.length != a2.length) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#2976 FP with method call (unresolved class)</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class C0 {
|
||||
class Unresolved {
|
||||
private final long val;
|
||||
public long getVal() { return val; }
|
||||
}
|
||||
|
||||
{
|
||||
if (c1.getVal() != c2.getVal()) { // <-- here
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
</test-data>
|
||||
|
@ -126,6 +126,57 @@ public class ClassWithStringFields {
|
||||
public void bar(String param) {
|
||||
if (param != null) { } // ok
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#3004 UseEqualsToCompareStrings false positive with PMD 6.30.0</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class O {
|
||||
boolean f(String s) {
|
||||
return s.charAt(0) == s.charAt(1);
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#2979 UseEqualsToCompareStrings: FP with "var" variables</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class O {
|
||||
boolean f(String s) {
|
||||
final Matcher matcher = null;
|
||||
if (matcher.matches()) {
|
||||
|
||||
final var firstString = matcher.group("a");
|
||||
final var secondString = matcher.group("b");
|
||||
|
||||
if (firstString.isEmpty() != secondString.isEmpty()) { // <- violation
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#2979 UseEqualsToCompareStrings: FP with "var" variables (control, types are explicit)</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class O {
|
||||
boolean f(String s) {
|
||||
final Matcher matcher = null;
|
||||
if (matcher.matches()) {
|
||||
|
||||
final String firstString = matcher.group("a");
|
||||
final String secondString = matcher.group("b");
|
||||
|
||||
if (firstString.isEmpty() != secondString.isEmpty()) { // <- violation
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
@ -61,6 +61,11 @@ public class ScalaTokenAdapter implements GenericToken<ScalaTokenAdapter> {
|
||||
return token instanceof Token.EOF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKind() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScalaTokenAdapter{"
|
||||
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.vf.rule.security;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sourceforge.pmd.lang.vf.ast.ASTContent;
|
||||
import net.sourceforge.pmd.lang.vf.ast.ASTElExpression;
|
||||
import net.sourceforge.pmd.lang.vf.ast.ASTElement;
|
||||
import net.sourceforge.pmd.lang.vf.ast.ASTText;
|
||||
import net.sourceforge.pmd.lang.vf.ast.VfNode;
|
||||
import net.sourceforge.pmd.lang.vf.rule.AbstractVfRule;
|
||||
import net.sourceforge.pmd.lang.vf.rule.security.internal.ElEscapeDetector;
|
||||
|
||||
|
||||
public class VfHtmlStyleTagXssRule extends AbstractVfRule {
|
||||
private static final String STYLE_TAG = "style";
|
||||
private static final String APEX_PREFIX = "apex";
|
||||
private static final EnumSet<ElEscapeDetector.Escaping> URLENCODE_JSINHTMLENCODE = EnumSet.of(ElEscapeDetector.Escaping.URLENCODE, ElEscapeDetector.Escaping.JSINHTMLENCODE);
|
||||
private static final EnumSet<ElEscapeDetector.Escaping> ANY_ENCODE = EnumSet.of(ElEscapeDetector.Escaping.ANY);
|
||||
private static final Pattern URL_METHOD_PATTERN = Pattern.compile("url\\s*\\([^)]*$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public VfHtmlStyleTagXssRule() {
|
||||
addRuleChainVisit(ASTElExpression.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are looking for an ASTElExpression node that is
|
||||
* placed inside an ASTContent, which in turn is placed inside
|
||||
* an ASTElement, where the element is not an inbuilt vf tag.
|
||||
*
|
||||
* <pre>{@code
|
||||
* <ASTElement>
|
||||
* <ASTContent>
|
||||
* <ASTElExpression></ASTElExpression>
|
||||
* </ASTContent>
|
||||
* </ASTElement>
|
||||
* }</pre>
|
||||
*/
|
||||
@Override
|
||||
public Object visit(ASTElExpression node, Object data) {
|
||||
final VfNode nodeParent = node.getParent();
|
||||
if (!(nodeParent instanceof ASTContent)) {
|
||||
// nothing to do here.
|
||||
// We care only if parent is available and is an ASTContent
|
||||
return data;
|
||||
}
|
||||
final ASTContent contentNode = (ASTContent) nodeParent;
|
||||
|
||||
final VfNode nodeGrandParent = contentNode.getParent();
|
||||
if (!(nodeGrandParent instanceof ASTElement)) {
|
||||
// nothing to do here.
|
||||
// We care only if grandparent is available and is an ASTElement
|
||||
return data;
|
||||
}
|
||||
final ASTElement elementNode = (ASTElement) nodeGrandParent;
|
||||
|
||||
// make sure elementNode does not have an "apex:" prefix
|
||||
if (isApexPrefixed(elementNode)) {
|
||||
// nothing to do here.
|
||||
// This rule does not deal with inbuilt-visualforce tags
|
||||
return data;
|
||||
}
|
||||
|
||||
verifyEncoding(node, contentNode, elementNode, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examining encoding of ElExpression - we apply different rules
|
||||
* for plain HTML tags and <style></style> content.
|
||||
*/
|
||||
private void verifyEncoding(
|
||||
ASTElExpression node,
|
||||
ASTContent contentNode,
|
||||
ASTElement elementNode,
|
||||
Object data) {
|
||||
final String previousText = getPreviousText(contentNode, node);
|
||||
final boolean isWithinSafeResource = ElEscapeDetector.startsWithSafeResource(node);
|
||||
|
||||
// if El is inside a <style></style> tag
|
||||
// and is not surrounded by a safe resource, check for violations
|
||||
if (isStyleTag(elementNode) && !isWithinSafeResource) {
|
||||
|
||||
// check if we are within a URL expression
|
||||
if (isWithinUrlMethod(previousText)) {
|
||||
verifyEncodingWithinUrl(node, data);
|
||||
} else {
|
||||
verifyEncodingWithoutUrl(node, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStyleTag(ASTElement elementNode) {
|
||||
// are we dealing with HTML <style></style> tag?
|
||||
return STYLE_TAG.equalsIgnoreCase(elementNode.getLocalName());
|
||||
}
|
||||
|
||||
private void verifyEncodingWithinUrl(ASTElExpression elExpressionNode, Object data) {
|
||||
|
||||
// only allow URLENCODING or JSINHTMLENCODING
|
||||
if (ElEscapeDetector.doesElContainAnyUnescapedIdentifiers(
|
||||
elExpressionNode,
|
||||
URLENCODE_JSINHTMLENCODE)) {
|
||||
addViolationWithMessage(
|
||||
data,
|
||||
elExpressionNode,
|
||||
"Dynamic EL content within URL in style tag should be URLENCODED or JSINHTMLENCODED as appropriate");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void verifyEncodingWithoutUrl(ASTElExpression elExpressionNode, Object data) {
|
||||
if (ElEscapeDetector.doesElContainAnyUnescapedIdentifiers(
|
||||
elExpressionNode,
|
||||
ANY_ENCODE)) {
|
||||
addViolationWithMessage(
|
||||
data,
|
||||
elExpressionNode,
|
||||
"Dynamic EL content in style tag should be appropriately encoded");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isApexPrefixed(ASTElement node) {
|
||||
return node.isHasNamespacePrefix()
|
||||
&& APEX_PREFIX.equalsIgnoreCase(node.getNamespacePrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text content within style tag that leads up to the ElExpression.
|
||||
* For example, in this snippet:
|
||||
*
|
||||
* <pre>
|
||||
* <style>
|
||||
* div {
|
||||
* background: url('{!HTMLENCODE(XSSHere)}');
|
||||
* }
|
||||
* </style>
|
||||
* </pre>
|
||||
*
|
||||
* {@code getPreviousText(...)} would return <code>"\n div {\n background: url("</code>.
|
||||
*
|
||||
*/
|
||||
private String getPreviousText(ASTContent content, ASTElExpression elExpressionNode) {
|
||||
final int indexInParent = elExpressionNode.getIndexInParent();
|
||||
final VfNode previous = indexInParent > 0 ? content.getChild(indexInParent - 1) : null;
|
||||
return previous instanceof ASTText ? previous.getImage() : "";
|
||||
}
|
||||
|
||||
// visible for unit testing
|
||||
static boolean isWithinUrlMethod(String previousText) {
|
||||
// match for a pattern that
|
||||
// 1. contains "url" (case insensitive),
|
||||
// 2. followed by any number of whitespaces,
|
||||
// 3. a starting bracket "("
|
||||
// 4. and anything else but an ending bracket ")"
|
||||
// For example:
|
||||
// Matches: "div { background: url('", "div { background: Url ( blah"
|
||||
// Does not match: "div { background: url('myUrl')", "div { background: myStyle('"
|
||||
|
||||
return URL_METHOD_PATTERN.matcher(previousText).find();
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user