Merge branch '7.0.x' into update-UseDiamondOperator

This commit is contained in:
Clément Fournier committed 2021-04-30 13:17:23 +02:00
commit 6978862120
19 files changed
+324 -58

No files matched your search

@@ -1,8 +1,8 @@
---
name: Rule violation
about: Let us know about a false positive/false negative
name: Rule violation (false-positive)
about: Let us know about a false-positive (a violation is reported on code that is not problematic)
title: ''
labels: 'a:false-positive, a:false-negative'
labels: 'a:false-positive'
assignees: ''
---
@@ -25,8 +25,6 @@ Please provide the rule name and a link to the rule documentation:
**Expected outcome:**
* Does PMD report a violation, where there shouldn't be one? -> false-positive
* Is PMD missing to report a violation, where there should be one? -> false-negative
PMD reports a violation at line ..., but that's wrong. That's a false positive.
**Running PMD through:** *[CLI | Ant | Maven | Gradle | Designer | Other]*
@@ -0,0 +1,30 @@
---
name: Rule violation (false-negative)
about: Let us know about a false-negative (no violation is reported on problematic code)
title: ''
labels: 'a:false-negative'
assignees: ''
---
<!-- Please, prefix the report title with the language it applies to within brackets, such as [java] or [apex] -->
**Affects PMD Version:**
**Rule:**
Please provide the rule name and a link to the rule documentation:
<https://pmd.github.io/latest/pmd_rules_XXX_XXX.html#XXX>
**Description:**
**Code Sample demonstrating the issue:**
```
```
**Expected outcome:**
PMD should report a violation at line ..., but doesn't. This is a false-negative.
**Running PMD through:** *[CLI | Ant | Maven | Gradle | Designer | Other]*
File renamed without changes.
File renamed without changes.
+3 -2
View File
@@ -23,8 +23,9 @@ layout: default
{% if page.editmepath %}
{% assign editmepath = page.editmepath %}
{% endif %}
<a target="_blank" href="https://github.com/{{site.github_editme_path}}{{editmepath}}" class="btn btn-outline-secondary githubEditButton" role="button"><i class="fab fa-github fa-lg"></i> Edit me</a>
{% unless page.editmepath == false %}
<a target="_blank" href="https://github.com/{{site.github_editme_path}}{{editmepath}}" class="btn btn-outline-secondary githubEditButton" role="button"><i class="fab fa-github fa-lg"></i> Edit me</a>
{% endunless %}
{% endif %}
+1
View File
@@ -157,6 +157,7 @@ The following previously deprecated rules have been finally removed:
* [#3195](https://github.com/pmd/pmd/pull/3195): \[java] Improve rule UnnecessaryReturn to detect more cases
* [#3218](https://github.com/pmd/pmd/pull/3218): \[java] Generalize UnnecessaryCast to flag all unnecessary casts
* [#3221](https://github.com/pmd/pmd/issues/3221): \[java] PrematureDeclaration false positive for unused variables
* [#3238](https://github.com/pmd/pmd/issues/3238): \[java] Improve ExprContext, fix FNs of UnnecessaryCast
* java-errorprone
* [#1005](https://github.com/pmd/pmd/issues/1005): \[java] CloneMethodMustImplementCloneable triggers for interfaces
* [#2532](https://github.com/pmd/pmd/issues/2532): \[java] AvoidDecimalLiteralsInBigDecimalConstructor can not detect the case new BigDecimal(Expression)
+3
View File
@@ -21,6 +21,9 @@ This is a {{ site.pmd.release_type }} release.
### Fixed Issues
* doc
* [#3230](https://github.com/pmd/pmd/issues/3230): \[doc] Remove "Edit me" button for language index pages
### API Changes
### External Contributions
@@ -182,6 +182,7 @@ public class RuleDocGenerator {
lines.add("language_name: " + entry.getKey().getName());
lines.add("permalink: " + LANGUAGE_INDEX_PERMALINK_PATTERN.replace("${language.tersename}", languageTersename));
lines.add("folder: pmd/rules");
lines.add("editmepath: false");
lines.add("---");
lines.add(GENERATED_WARNING_NO_SOURCE);
@@ -5,6 +5,7 @@ summary: Index of all built-in rules available for Java
language_name: Java
permalink: pmd_rules_java.html
folder: pmd/rules
editmepath: false
---
<!-- DO NOT EDIT THIS FILE. This file is generated. -->
## Sample
@@ -9,10 +9,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
import net.sourceforge.pmd.lang.java.types.TypeConversion;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
import net.sourceforge.pmd.lang.java.types.TypesFromReflection;
/**
* Context of an expression. This determines the target type of poly
@@ -87,6 +90,10 @@ public abstract class ExprContext {
return kind == CtxKind.Numeric;
}
public boolean isString() {
return kind == CtxKind.String;
}
boolean canGiveContextToPoly(boolean lambda) {
return true;
}
@@ -103,8 +110,14 @@ public abstract class ExprContext {
return new RegularCtx(targetType, CtxKind.OtherNonPoly);
}
static ExprContext newStringCtx(TypeSystem ts) {
JClassType stringType = (JClassType) TypesFromReflection.fromReflect(String.class, ts);
return new RegularCtx(stringType, CtxKind.String);
}
static ExprContext newNumericContext(JTypeMirror targetType) {
if (targetType.isPrimitive()) {
assert targetType.isNumeric() : targetType;
return new RegularCtx(targetType, CtxKind.Numeric);
}
return RegularCtx.NO_CTX; // error
@@ -152,6 +165,11 @@ public abstract class ExprContext {
public boolean isInvocationContext() {
return true;
}
@Override
public String toString() {
return "InvocCtx{arg=" + arg + ", node=" + node + '}';
}
}
/**
@@ -198,8 +216,13 @@ public abstract class ExprContext {
*/
Numeric,
// todo similarly, string contexts allow a string conversion, which uses
// String::valueOf.
/**
* String contexts, which convert the operand to a string using {@link String#valueOf(Object)},
* or the equivalent for a primitive type. They accept operands of any type.
* This is the context for the operands of a string concatenation expression,
* and for the message of an assert statement.
*/
String,
/** Kind for a standalone ternary (both branches are then in this context). */
Ternary,
@@ -212,10 +235,7 @@ public abstract class ExprContext {
* but not for poly expressions. These do not flow through ternary branches.
* These include:
* <ul>
* <li>TODO String contexts, which convert the operand to a string using {@link String#valueOf(Object)},
* or the equivalent for a primitive type. They accept operands of any type.
* This is the context for the operands of a string concatenation expression,
* and for the message of an assert statement.
* <li>
* <li>Boolean contexts, which unbox their operand to a boolean.
* They accept operands of type boolean or Boolean. This is the
* context for e.g. the condition of an {@code if} statement, an
@@ -261,5 +281,10 @@ public abstract class ExprContext {
public @Nullable JTypeMirror getTargetType() {
return targetType;
}
@Override
public String toString() {
return "RegularCtx{kind=" + kind + ", targetType=" + targetType + '}';
}
}
}
@@ -22,14 +22,13 @@ import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ExprContext.InvocCtx;
import net.sourceforge.pmd.lang.java.ast.ExprContext.RegularCtx;
import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.lang.java.types.JMethodSig;
import net.sourceforge.pmd.lang.java.types.JPrimitiveType;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
import net.sourceforge.pmd.lang.java.types.TypeOps;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
import net.sourceforge.pmd.lang.java.types.TypesFromReflection;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.BranchingMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.FunctionalExprMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror;
@@ -54,10 +53,10 @@ final class PolyResolution {
this.infer = infer;
this.ts = infer.getTypeSystem();
this.exprMirrors = JavaExprMirrors.forTypeResolution(infer);
JClassType stringType = (JClassType) TypesFromReflection.fromReflect(String.class, ts);
booleanCtx = ExprContext.newNonPolyContext(ts.BOOLEAN);
stringCtx = ExprContext.newNonPolyContext(stringType);
intCtx = ExprContext.newNumericContext(ts.INT);
this.stringCtx = ExprContext.newStringCtx(ts);
this.booleanCtx = ExprContext.newNonPolyContext(ts.BOOLEAN);
this.intCtx = ExprContext.newNumericContext(ts.INT);
}
private boolean isPreJava8() {
@@ -94,9 +93,11 @@ final class PolyResolution {
if (isPreJava8()) {
// safe cast because ASTSwitchExpression doesn't exist pre java 13
ASTConditionalExpression conditional = (ASTConditionalExpression) e;
return computeStandaloneConditionalType(this.ts,
conditional.getThenBranch().getTypeMirror(),
conditional.getElseBranch().getTypeMirror());
return computeStandaloneConditionalType(
this.ts,
conditional.getThenBranch().getTypeMirror(),
conditional.getElseBranch().getTypeMirror()
);
}
// Note that this creates expr mirrors for all subexpressions,
@@ -482,14 +483,20 @@ final class PolyResolution {
return booleanCtx; // condition
} else if (papa instanceof ASTConditionalExpression && node.getIndexInParent() != 0) {
if (isPreJava8()) {
return RegularCtx.NO_CTX;
}
assert ((ASTConditionalExpression) papa).isStandalone()
: "Expected standalone ternary, otherwise doesCascadeContext(..) would have returned true";
} else if (papa instanceof ASTConditionalExpression) {
return ExprContext.newStandaloneTernaryCtx(((ASTConditionalExpression) papa).getTypeMirror());
if (node.getIndexInParent() == 0) {
return booleanCtx; // the condition
} else {
// a branch
if (isPreJava8()) {
return RegularCtx.NO_CTX;
}
assert ((ASTConditionalExpression) papa).isStandalone()
: "Expected standalone ternary, otherwise doesCascadeContext(..) would have returned true";
return ExprContext.newStandaloneTernaryCtx(((ASTConditionalExpression) papa).getTypeMirror());
}
} else if (papa instanceof ASTInfixExpression) {
// numeric contexts, maybe
@@ -503,7 +510,7 @@ final class PolyResolution {
case OR:
case XOR:
case AND:
return ctxType == ts.BOOLEAN ? booleanCtx : ExprContext.newNumericContext(ctxType);
return ctxType == ts.BOOLEAN ? booleanCtx : ExprContext.newNumericContext(ctxType); // NOPMD CompareObjectsWithEquals
case LEFT_SHIFT:
case RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT:
@@ -516,11 +523,16 @@ final class PolyResolution {
return ExprContext.newNonPolyContext(otherOperand.getTypeMirror().unbox());
}
return RegularCtx.NO_CTX;
case ADD:
if (TypeTestUtil.isA(String.class, ctxType)) {
// string concat expr
return stringCtx;
}
// fallthrough
case LE:
case GE:
case GT:
case LT:
case ADD:
case SUB:
case MUL:
case DIV:
@@ -546,7 +558,10 @@ final class PolyResolution {
} else if (isPreJava8()) {
// in java < 8, context doesn't go flow through ternaries
return false;
} else if (!internalUse && node instanceof ASTConditionalExpression) {
} else if (!internalUse
&& node instanceof ASTConditionalExpression
&& child.getIndexInParent() != 0) {
// conditional branch
((ASTConditionalExpression) node).getTypeMirror(); // force resolution
return !((ASTConditionalExpression) node).isStandalone();
}
@@ -35,6 +35,7 @@ import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.TypeConversion;
import net.sourceforge.pmd.lang.java.types.TypeOps;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
/**
* Detects casts where the operand is already a subtype of the context
@@ -107,7 +108,7 @@ public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
&& operandType.isSubtypeOf(coercionType);
}
return !isCastDeterminingContext(castExpr, context, coercionType)
return !isCastDeterminingContext(castExpr, context, coercionType, operandType)
&& castIsUnnecessaryToMatchContext(context, coercionType, operandType);
}
@@ -126,14 +127,10 @@ public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
JTypeMirror contextType = context.getTargetType();
if (contextType == null) {
return false; // should not occur in valid code
}
if (!TypeConversion.isConvertibleUsingBoxing(operandType, coercionType)) {
} else if (!TypeConversion.isConvertibleUsingBoxing(operandType, coercionType)) {
// narrowing cast
return false;
}
if (!context.acceptsType(operandType)) {
} else if (!context.acceptsType(operandType)) {
// then removing the cast would produce uncompilable code
return false;
}
@@ -149,30 +146,42 @@ public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
* that the cast is necessary, because there's some primitive conversions
* happening, or some other corner case.
*/
private static boolean isCastDeterminingContext(ASTCastExpression castExpr, ExprContext context, @NonNull JTypeMirror coercionType) {
private static boolean isCastDeterminingContext(ASTCastExpression castExpr, ExprContext context, @NonNull JTypeMirror coercionType, JTypeMirror operandType) {
if (castExpr.getParent() instanceof ASTConditionalExpression && castExpr.getIndexInParent() != 0) {
// a branch of a ternary
return true;
}
if (context.isNumeric() && castExpr.getParent() instanceof ASTInfixExpression) {
} else if (context.isString() && isInfixExprWithOperator(castExpr.getParent(), ADD)) {
// inside string concatenation
return !TypeTestUtil.isA(String.class, JavaRuleUtil.getOtherOperandIfInInfixExpr(castExpr))
&& !TypeTestUtil.isA(String.class, operandType);
} else if (context.isNumeric() && castExpr.getParent() instanceof ASTInfixExpression) {
// numeric expr
ASTInfixExpression parent = (ASTInfixExpression) castExpr.getParent();
if (isInfixExprWithOperator(parent, SHIFT_OPS)) {
// then the cast is determining the width of expr
assert castExpr == parent.getLeftOperand(); // second operand doesn't have a numeric context
return true;
// if so, then the cast is determining the width of expr
// the right operand is always int
if (castExpr == parent.getLeftOperand()) {
return !TypeOps.isStrictSubtype(operandType.unbox(), operandType.getTypeSystem().INT);
} else {
return false;
}
} else if (isInfixExprWithOperator(parent, BINARY_PROMOTED_OPS)) {
ASTExpression otherOperand = JavaRuleUtil.getOtherOperandIfInInfixExpr(castExpr);
JTypeMirror otherType = otherOperand.getTypeMirror();
return otherOperand instanceof ASTCastExpression // remove FPs
// Ie, the type that is taken by the binary promotion
// is the type of the cast, not the type of the operand.
// Eg in
// int i; ((double) i) * i
// the only reason the mult expr has type double is because of the cast
|| TypeOps.isStrictSubtype(otherOperand.getTypeMirror(), coercionType);
// Ie, the type that is taken by the binary promotion
// is the type of the cast, not the type of the operand.
// Eg in
// int i; ((double) i) * i
// the only reason the mult expr has type double is because of the cast
return TypeOps.isStrictSubtype(otherType, coercionType)
// but not for integers strictly smaller than int
&& !TypeOps.isStrictSubtype(otherType.unbox(), otherType.getTypeSystem().INT);
}
}
@@ -212,7 +212,6 @@ public final class TypeSystem {
FLOAT = createPrimitive(PrimitiveTypeKind.FLOAT, Float.class);
DOUBLE = createPrimitive(PrimitiveTypeKind.DOUBLE, Double.class);
// this relies on the fact that setOf always returns immutable sets
BOOLEAN.superTypes = immutableSetOf(BOOLEAN);
CHAR.superTypes = immutableSetOf(CHAR, INT, LONG, FLOAT, DOUBLE);
BYTE.superTypes = immutableSetOf(BYTE, SHORT, INT, LONG, FLOAT, DOUBLE);
@@ -6,9 +6,11 @@
package net.sourceforge.pmd.lang.java.types.internal.infer
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import net.sourceforge.pmd.lang.ast.test.component6
import net.sourceforge.pmd.lang.ast.test.shouldBe
import net.sourceforge.pmd.lang.java.ast.*
import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil
import net.sourceforge.pmd.lang.java.types.STRING
import net.sourceforge.pmd.lang.java.types.parseWithTypeInferenceSpy
import net.sourceforge.pmd.lang.java.types.shouldHaveType
@@ -165,6 +167,23 @@ class ConversionContextTests : ProcessorTestSpec({
}
}
parserTest("Test context of ternary condition") {
val (acu, spy) = parser.parseWithTypeInferenceSpy("""
class Scratch {
static void m(Boolean boxedBool, boolean bool, String str, int[] ints) {
str = (boolean) boxedBool ? "a" : "b";
}
}
""")
val (booleanCast) = acu.descendants(ASTCastExpression::class.java).toList()
spy.shouldBeOk {
booleanCast.conversionContext::getTargetType shouldBe boolean
}
}
parserTest("Test numeric context") {
val (acu, spy) = parser.parseWithTypeInferenceSpy("""
@@ -201,4 +220,30 @@ class ConversionContextTests : ProcessorTestSpec({
}
}
}
parserTest("String contexts") {
val (acu, spy) = parser.parseWithTypeInferenceSpy("""
class Scratch {
static void m(int i) {
eat(" " + i);
eat(i + " ");
eat(" " + " ");
eat(" " + i + i);
}
void eat(Object d) {}
}
""")
val concats = acu.descendants(ASTInfixExpression::class.java).toList()
spy.shouldBeOk {
concats.forEach {
withClue(it) {
JavaRuleUtil.isStringConcatExpr(it) shouldBe true
it.leftOperand.conversionContext::getTargetType shouldBe ts.STRING
it.rightOperand.conversionContext::getTargetType shouldBe ts.STRING
}
}
}
}
})
@@ -344,7 +344,7 @@
| | | +- VariableAccess[@AccessType = "READ", @CompileTimeConstant = "false", @Image = "i", @Name = "i", @ParenthesisDepth = "0", @Parenthesized = "false"]
| | | +- NumericLiteral[@Base = "10", @CompileTimeConstant = "true", @DoubleLiteral = "false", @FloatLiteral = "false", @Image = "10", @IntLiteral = "true", @Integral = "true", @LongLiteral = "false", @ParenthesisDepth = "0", @Parenthesized = "false", @ValueAsDouble = "10.0", @ValueAsFloat = "10.0", @ValueAsInt = "10", @ValueAsLong = "10"]
| | +- ForUpdate[]
| | | +- StatementExpressionList[]
| | | +- StatementExpressionList[@Size = "1"]
| | | +- UnaryExpression[@CompileTimeConstant = "false", @Operator = "++", @ParenthesisDepth = "0", @Parenthesized = "false"]
| | | +- VariableAccess[@AccessType = "WRITE", @CompileTimeConstant = "false", @Image = "i", @Name = "i", @ParenthesisDepth = "0", @Parenthesized = "false"]
| | +- Block[@Size = "0", @containsComment = "false"]
@@ -1075,7 +1075,7 @@
| | +- MethodCall[@CompileTimeConstant = "false", @Image = "hasNext", @MethodName = "hasNext", @ParenthesisDepth = "0", @Parenthesized = "false"]
| | +- ArgumentList[@Size = "0"]
| +- ForUpdate[]
| | +- StatementExpressionList[]
| | +- StatementExpressionList[@Size = "1"]
| | +- UnaryExpression[@CompileTimeConstant = "false", @Operator = "--", @ParenthesisDepth = "0", @Parenthesized = "false"]
| | +- VariableAccess[@AccessType = "WRITE", @CompileTimeConstant = "false", @Image = "i", @Name = "i", @ParenthesisDepth = "0", @Parenthesized = "false"]
| +- ExpressionStatement[]
@@ -618,7 +618,7 @@ class Scratch {
]]></code>
</test-code>
<test-code>
<description>Loops</description>
<description>Conditionals and loop statements</description>
<expected-problems>5</expected-problems>
<code><![CDATA[
class Scratch {
@@ -632,6 +632,30 @@ class Scratch {
}
]]></code>
</test-code>
<test-code>
<description>Conditional expr condition</description>
<expected-problems>1</expected-problems>
<code><![CDATA[
class Scratch {
static void m(Boolean boxedBool, boolean bool, String str, int[] ints) {
str = (boolean) boxedBool ? "a" : "b";
}
}
]]></code>
</test-code>
<test-code>
<description>Necessary cast for condition</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Scratch {
static void m(Object obj) {
// both are necessary
if ((Boolean) obj
|| (boolean) obj);
}
}
]]></code>
</test-code>
<test-code>
<description>Missing context identity cast</description>
<expected-problems>2</expected-problems>
@@ -659,4 +683,118 @@ class Scratch {
}
]]></code>
</test-code>
<test-code>
<description>Casts in shift exprs</description>
<expected-problems>7</expected-problems>
<expected-linenumbers>5,8,9,10,12,13,14</expected-linenumbers>
<code><![CDATA[
class Scratch {
static void m(int i, short s, long l, Integer boxedInt) {
l = (long) i << 2; // "necessary"
l = (long) i << 34; // definitely necessary
l = (long) (i << 34); // unnecessary, l5
i = i << (int) l; // necessary
i = i << (int) boxedInt; // unnecessary
i = (int) i << boxedInt; // unnecessary
i = (int) (i << boxedInt); // unnecessary
i = (int) s << boxedInt; // unnecessary, widened anyway
i = ((int) s) << boxedInt; // unnecessary, same expression with unnecessary parens
i = (int) (s << boxedInt); // unnecessary
}
}
]]></code>
</test-code>
<test-code>
<description>Casts in arithmetic smaller than int </description>
<expected-problems>9</expected-problems>
<code><![CDATA[
class Scratch {
// they're all unecessary
static void m(int i, byte b, char c, short s) {
i = b * (int) b; // 4
i = (int) b * b;
i = b * (int) s; // 6
i = b * (int) c;
i = b * (short) b; // 8
i = i * (short) b;
i = i * (short) s; // 10
i = i * (int) c;
i = i * (char) c; // 11
}
}
]]></code>
</test-code>
<test-code>
<description>Casts in arithmetic with unboxing + widening</description>
<expected-problems>4</expected-problems>
<code><![CDATA[
class Scratch {
static void m(Integer i, Double d, Object lhs) {
lhs = (int) i * 1.0;
lhs = (double) d * 1.01;
lhs = 1.01 * (int) i;
lhs = 1.01 * (double) d;
}
}
]]></code>
</test-code>
<test-code>
<description>"Narrowing" cast from char to short is necessary</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Scratch {
static void m(int i, byte b, char c, short s) {
i = i * (short) c;
}
}
]]></code>
</test-code>
<test-code>
<description>char cast to int in string context is ok</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Scratch {
static void m(String s, char c) {
// the toString is different -> necessary (string contexts aren't implemented yet)
s = "(" + (int) c + ")" + c;
}
}
]]></code>
</test-code>
<test-code>
<description>String cast determining context</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>5</expected-linenumbers>
<code><![CDATA[
class Scratch {
static void m(Object a, Object b) {
String s = a + (String) b // necessary, determines context
+ a + (String) b // technically unnecessary, but narrowing
+ a + (String) null // unnecessary
;
}
}
]]></code>
</test-code>
<test-code>
<description>Cast on both sides of arithmetic</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<expected-messages>
<message>Unnecessary cast (int)</message>
</expected-messages>
<code><![CDATA[
class Scratch {
static void m(short s, Object b) {
b = (int) s
+ (long) s;
}
}
]]></code>
</test-code>
</test-data>
+1 -1
View File
@@ -696,7 +696,7 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
<version>2.6</version> <!-- note: this is the last version compatible with java7 -->
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>