Merge branch '7.0.x' into update-UseDiamondOperator
This commit is contained in:
commit
6978862120
19 files changed
+324
-58
No files matched your search
+4
-6
@@ -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.
File renamed without changes.
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
+29
-20
@@ -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);
|
||||
|
||||
+45
@@ -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"]
|
||||
|
||||
+1
-1
@@ -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[]
|
||||
|
||||
+139
-1
@@ -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>
|
||||
Reference in new issue
Block a user