Merge pull request #3364 from oowekyala:new-rule-UnnecessaryConversion

[java] New rule UnnecessaryBoxing #3364
This commit is contained in:
Andreas Dangel committed 2021-07-24 11:09:36 +02:00
commit 8b6ee1c8c2
9 files changed
+397

No files matched your search

+1
View File
@@ -106,6 +106,7 @@
<rule ref="category/java/codestyle.xml/UnnecessaryAnnotationValueElement"/>
<rule ref="category/java/codestyle.xml/UnnecessaryCast"/>
<rule ref="category/java/codestyle.xml/UnnecessaryConstructor"/>
<rule ref="category/java/codestyle.xml/UnnecessaryBoxing"/>
<rule ref="category/java/codestyle.xml/UnnecessaryFullyQualifiedName"/>
<!-- <rule ref="category/java/codestyle.xml/UnnecessaryImport"/> -->
<rule ref="category/java/codestyle.xml/UnnecessaryLocalBeforeReturn"/>
+7
View File
@@ -74,8 +74,15 @@ The default version is always ES6.
#### New Rules
##### Apex
* The Apex rule {% rule "apex/design/UnusedMethod" %} finds unused methods in your code.
##### Java
* {% rule "java/codestyle/UnnecessaryBoxing" %} reports boxing and unboxing
conversions that may be made implicit.
#### Changed Rules
##### Java
@@ -10,5 +10,7 @@ This ruleset contains links to rules that are new in PMD v7.0.0
<rule ref="category/apex/design.xml/UnusedMethod"/>
<rule ref="category/java/codestyle.xml/UnnecessaryBoxing"/>
</ruleset>
@@ -0,0 +1,168 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.codestyle;
import static net.sourceforge.pmd.util.CollectionUtil.setOf;
import java.util.Set;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTList;
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
import net.sourceforge.pmd.lang.java.types.JMethodSig;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
import net.sourceforge.pmd.lang.java.types.TypePrettyPrint;
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
/**
*
*/
public class UnnecessaryBoxingRule extends AbstractJavaRulechainRule {
private static final Set<String> INTERESTING_NAMES = setOf(
"valueOf",
"booleanValue",
"charValue",
"byteValue",
"shortValue",
"intValue",
"longValue",
"floatValue",
"doubleValue"
);
public UnnecessaryBoxingRule() {
super(ASTMethodCall.class, ASTConstructorCall.class);
}
@Override
public Object visit(ASTConstructorCall node, Object data) {
if (node.getTypeMirror().isBoxedPrimitive()) {
ASTExpression arg = ASTList.singleOrNull(node.getArguments());
if (arg == null) {
return null;
}
JTypeMirror argT = arg.getTypeMirror();
if (argT.isPrimitive()) {
checkBox((RuleContext) data, "boxing", node, arg, node.getMethodType().getFormalParameters().get(0));
}
}
return null;
}
@Override
public Object visit(ASTMethodCall node, Object data) {
if (INTERESTING_NAMES.contains(node.getMethodName())) {
OverloadSelectionResult overload = node.getOverloadSelectionInfo();
if (overload.isFailed()) {
return null;
}
JMethodSig m = overload.getMethodType();
boolean isValueOf = "valueOf".equals(node.getMethodName());
ASTExpression qualifier = node.getQualifier();
if (isValueOf && isWrapperValueOf(m)) {
checkBox((RuleContext) data, "boxing", node, node.getArguments().get(0), m.getFormalParameters().get(0));
} else if (!isValueOf && isUnboxingCall(m) && qualifier != null) {
checkBox((RuleContext) data, "unboxing", node, qualifier, qualifier.getTypeMirror());
}
}
return null;
}
private boolean isUnboxingCall(JMethodSig m) {
return !m.isStatic() && m.getDeclaringType().isBoxedPrimitive() && m.getArity() == 0;
}
private boolean isWrapperValueOf(JMethodSig m) {
return m.isStatic()
&& m.getArity() == 1
&& m.getDeclaringType().isBoxedPrimitive()
&& m.getFormalParameters().get(0).isPrimitive();
}
private void checkBox(
RuleContext rctx,
String opKind,
ASTExpression conversionExpr,
ASTExpression convertedExpr,
JTypeMirror conversionInput
) {
// the conversion looks like
// CTX _ = conversion(sourceExpr)
// we have the following data flow:
// sourceExpr -> convInput -> convOutput -> ctx
// 1 2 3
// where 1 and 3 are implicit conversions which we assume are
// valid because the code should compile.
// we want to report a violation if this is equivalent to
// sourceExpr -> ctx
// which basically means testing that convInput -> convOutput
// may be performed implicitly.
// We cannot just test compatibility of the source to the ctx,
// because of situations like
// int i = integer.byteValue()
// where the conversion actually truncates the input value.
JTypeMirror sourceType = convertedExpr.getTypeMirror();
JTypeMirror conversionOutput = conversionExpr.getTypeMirror();
ExprContext ctx = conversionExpr.getConversionContext();
JTypeMirror ctxType = ctx.getTargetType();
if (ctxType == null && conversionExpr instanceof InvocationNode) {
ctxType = conversionOutput;
}
if (ctxType != null) {
if (isImplicitlyConvertible(conversionInput, conversionOutput)) {
boolean simpleConv = isReferenceSubtype(sourceType, conversionInput);
final String reason;
if (simpleConv && conversionInput.unbox().equals(conversionOutput)) {
reason = "explicit unboxing";
} else if (simpleConv && conversionInput.box().equals(conversionOutput)) {
reason = "explicit boxing";
} else if (sourceType.equals(conversionOutput)) {
reason = "boxing of boxed value";
} else {
if (sourceType.equals(ctxType)) {
reason = opKind;
} else {
reason = "explicit conversion from " + TypePrettyPrint.prettyPrintWithSimpleNames(sourceType) + " to " + TypePrettyPrint.prettyPrintWithSimpleNames(ctxType);
}
}
addViolation(rctx, conversionExpr, reason);
}
}
}
private boolean isImplicitlyConvertible(JTypeMirror i, JTypeMirror o) {
return i.box().isSubtypeOf(o.box())
|| i.unbox().isSubtypeOf(o.unbox());
}
/**
* Whether {@code S <: T}, but ignoring primitive widening.
* {@code isReferenceSubtype(int, double) == false} even though
* {@code int.isSubtypeOf(double)}.
*/
private static boolean isReferenceSubtype(JTypeMirror s, JTypeMirror t) {
return s.isPrimitive() ? t.equals(s)
: s.isSubtypeOf(t);
}
}
@@ -31,6 +31,10 @@ public final class TypePrettyPrint {
return prettyPrint(t, new TypePrettyPrinter());
}
public static @NonNull String prettyPrintWithSimpleNames(@NonNull JTypeVisitable t) {
return prettyPrint(t, new TypePrettyPrinter().useSimpleNames(true));
}
public static String prettyPrint(@NonNull JTypeVisitable t, TypePrettyPrinter prettyPrinter) {
t.acceptVisitor(PrettyPrintVisitor.INSTANCE, prettyPrinter);
return prettyPrinter.consumeResult();
@@ -1477,6 +1477,36 @@ public class Foo {
</example>
</rule>
<rule name="UnnecessaryBoxing"
language="java"
since="7.0.0"
minimumLanguageVersion="1.5"
message="Unnecessary {0}"
class="net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryBoxingRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#unnecessaryboxing">
<description>
Reports explicit boxing and unboxing conversions that may safely be removed,
either because they would be inserted by the compiler automatically,
or because they're semantically a noop (eg unboxing a value to rebox it immediately).
Note that this only handles boxing and unboxing conversions occurring through
calls to `valueOf` or one of the `intValue`, `byteValue`, etc. methods. Casts
that command a conversion are reported by {% rule UnnecessaryCast %} instead.
</description>
<priority>3</priority>
<example><![CDATA[
{
// Instead of
Integer integer = Integer.valueOf(2);
// you may just write
Integer integer = 2;
int i = integer.intValue(); // similarly for unboxing
}
]]>
</example>
</rule>
<!-- This is only restricted to java 5+ because the rule doesn't support
the type system pre-java5, where there were no autoboxing conversions. -->
<rule name="UnnecessaryCast"
@@ -104,6 +104,7 @@
<!-- <rule ref="category/java/codestyle.xml/PrematureDeclaration" /> -->
<!-- <rule ref="category/java/codestyle.xml/TooManyStaticImports" /> -->
<rule ref="category/java/codestyle.xml/UnnecessaryAnnotationValueElement"/>
<!-- <rule ref="category/java/codestyle.xml/UnnecessaryBoxing" /> -->
<!-- <rule ref="category/java/codestyle.xml/UnnecessaryCast" /> -->
<rule ref="category/java/codestyle.xml/UnnecessaryConstructor"/>
<rule ref="category/java/codestyle.xml/UnnecessaryFullyQualifiedName"/>
@@ -0,0 +1,11 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.codestyle;
import net.sourceforge.pmd.testframework.PmdRuleTst;
public class UnnecessaryBoxingTest extends PmdRuleTst {
// no additional unit tests
}
@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data
xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd">
<test-code>
<description>pos - new Integer(int)</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>2</expected-linenumbers>
<code><![CDATA[
public class Foo {
Integer i = new Integer(42);
}
]]></code>
</test-code>
<test-code>
<description>neg - new Integer(String)</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
Integer i = new Integer("42");
}
]]></code>
</test-code>
<test-code>
<description>neg - Integer.valueOf(String)</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
Integer i = Integer.valueOf("42");
}
]]></code>
</test-code>
<test-code>
<description>pos - Integer.valueOf(int)</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>2</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit boxing</message>
</expected-messages>
<code><![CDATA[
public class Foo {
Integer i = Integer.valueOf(42);
}
]]></code>
</test-code>
<test-code>
<description>pos - new Integer(int)</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>2</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit boxing</message>
</expected-messages>
<code><![CDATA[
public class Foo {
Integer i = new Integer(42);
}
]]></code>
</test-code>
<test-code>
<description>pos - new Integer(int) with widening</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit conversion from char to Integer</message>
</expected-messages>
<code><![CDATA[
public class Foo {{
char c = 0;
Integer i = new Integer(c);
}}
]]></code>
</test-code>
<test-code>
<description>pos - new Integer(int) assigned to object</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit boxing</message>
</expected-messages>
<code><![CDATA[
public class Foo {{
char c = 0;
Object obj = new Integer(2);
}}
]]></code>
</test-code>
<test-code>
<description>pos - unboxing</description>
<expected-problems>3</expected-problems>
<expected-linenumbers>3,4,5</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit unboxing</message>
<message>Unnecessary explicit conversion from Integer to double</message>
<message>Unnecessary explicit conversion from Integer to double</message>
</expected-messages>
<code><![CDATA[
public class Foo {
void fun(Integer c) {
int i0 = c.intValue();
double d = c.doubleValue();
d = c.longValue();
}
}
]]></code>
</test-code>
<test-code>
<description>neg - unboxing to smaller type</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
void fun(Integer c) {
byte i2 = c.byteValue(); // necessary
short i = c.shortValue(); // necessary
int i = c.byteValue(); // necessary, because it truncates the value before widening it.
}
}
]]></code>
</test-code>
<test-code>
<description>pos - unboxing to smaller type</description>
<expected-problems>5</expected-problems>
<expected-linenumbers>5,6,7,8,9</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit boxing</message>
<message>Unnecessary explicit unboxing</message>
<message>Unnecessary explicit unboxing</message>
<message>Unnecessary explicit boxing</message>
<message>Unnecessary boxing of boxed value</message>
</expected-messages>
<code><![CDATA[
class Scratch {
public static void main(String[] args) {
Integer integer = 2; // ok
Object a = Integer.valueOf(2); // explicit boxing where the value would be autoboxed
int b = integer.intValue(); // explicit unboxing where the value would be auto-unboxed
Object c = integer.intValue(); // unboxing where the value is immediately reboxed
int i = Integer.valueOf(0); // boxing where the value is immediately unboxed
Integer.valueOf(integer); // boxing of already boxed value
}
}
]]></code>
</test-code>
<test-code>
<!-- Casts are left to UnnecessaryCast -->
<description>Unnecessary (primitive -> primitive) casts</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>5</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit boxing</message>
</expected-messages>
<code><![CDATA[
class Scratch {
public static void main(String[] args) {
Integer integer = 2; // ok
long a = (int) Integer.valueOf(2); // explicit boxing where the value would be autoboxed
int b = (int) 2; // ignored, left to UnnecessaryCast
long c = (int) 4; // widening can be implicit
double c = (int) 4; // widening can be implicit
double c = (int) 4.0; // cast is narrowing so necessary
}
}
]]></code>
</test-code>
</test-data>