Merge branch 'master' into pmd/7.0.x

This commit is contained in:
Andreas Dangel
2021-01-21 21:00:42 +01:00
36 changed files with 1623 additions and 292 deletions

View File

@ -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>

View File

@ -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"

View File

@ -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 %}

View File

@ -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;
}
}
};
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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>

View File

@ -68,6 +68,11 @@ public class BaseTokenFilterTest {
public int getEndColumn() {
return 0;
}
@Override
public int getKind() {
return 0;
}
}
static class StringTokenManager implements TokenManager<StringToken> {

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View 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;
}

View 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

View 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

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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())) {

View File

@ -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());

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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>

View File

@ -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, &gt; 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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{"

View File

@ -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>
* &lt;style>
* div {
* background: url('{!HTMLENCODE(XSSHere)}');
* }
* &lt;/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