IntegerMultiplicationImplicitCastToLong: added quick-fix (IDEA-206024)

This commit is contained in:
Artemiy Sartakov
2019-02-01 15:10:48 +07:00
parent 1bc7264b58
commit 4b5b6574aa
10 changed files with 237 additions and 13 deletions

View File

@@ -0,0 +1,13 @@
// "Fix all 'Integer multiplication or shift implicitly cast to long' problems in file" "true"
class Test {
void test(int a, int b) {
long c = a * 2L;
long d = 2L * a;
long d1 = -2L * a;
long e = (long) a * b;
long f = (long) a * b * 2; // should be converted to (long) a * b * 2 or 2L * a * b (but not a*b*2L: in this case a*b would still be integer)
long g = (long) a << 2;
long h = 2L << a;
long i = (2L) * a;
}
}

View File

@@ -0,0 +1,13 @@
// "Fix all 'Integer multiplication or shift implicitly cast to long' problems in file" "true"
class Test {
void test (int i1, int i2, int i3) {
long a = (long) (((long) i1 * i2) * i3);
long b = (int) ((i1 * i2) * i3); // shouldn't convert because result is still casted to int
long c = ((long) (int) i1 * i2 * i3);
long d = ((long) i1 * i2 * i3); // fix is not needed at all
long e = (long) ((long) i1 * i2);
long f = (i2) * (long) i1; // fix is not needed at all
long g = (long) i1 * ((long) i2 * i3);
}
}

View File

@@ -0,0 +1,12 @@
// "Fix all 'Integer multiplication or shift implicitly cast to long' problems in file" "true"
class Test {
void test(int a, int b) {
long c = a * ((long) a * b);
long d = (a * (b * (a * (b * 2L)));
long e = -(-a * -((long) -a * -b));
long f = a * ((a == 2) ? (long) b * a : a);
long g = a + (a + ((long) (a + b) * (a + b)));
long h = (long) a << (a * b);
long i = ((long) a * b) << (b * a);
}
}

View File

@@ -0,0 +1,13 @@
// "Fix all 'Integer multiplication or shift implicitly cast to long' problems in file" "true"
class Test {
void test(int a, int b) {
long c = <caret>a * 2;
long d = 2 * a;
long d1 = -2 * a;
long e = a * b;
long f = a * b * 2; // should be converted to (long) a * b * 2 or 2L * a * b (but not a*b*2L: in this case a*b would still be integer)
long g = a << 2;
long h = 2 << a;
long i = (2) * a;
}
}

View File

@@ -0,0 +1,13 @@
// "Fix all 'Integer multiplication or shift implicitly cast to long' problems in file" "true"
class Test {
void test (int i1, int i2, int i3) {
long a = (long) ((<caret>i1 * i2) * i3);
long b = (int) ((i1 * i2) * i3); // shouldn't convert because result is still casted to int
long c = ((int) i1 * i2 * i3);
long d = ((long) i1 * i2 * i3); // fix is not needed at all
long e = (long) (i1 * i2);
long f = (i2) * (long) i1; // fix is not needed at all
long g = (long) i1 * (i2 * i3);
}
}

View File

@@ -0,0 +1,12 @@
// "Fix all 'Integer multiplication or shift implicitly cast to long' problems in file" "true"
class Test {
void test(int a, int b) {
long c = a * (<caret>a * b);
long d = (a * (b * (a * (b * 2)));
long e = -(-a * -(-a * -b));
long f = a * ((a == 2) ? b * a : a);
long g = a + (a + ((a + b) * (a + b)));
long h = a << (a * b);
long i = (a * b) << (b * a);
}
}

View File

@@ -1420,6 +1420,7 @@ notify.without.corresponding.wait.display.name='notify()' without corresponding
notify.without.corresponding.wait.problem.descriptor=Call to <code>#ref</code> without corresponding <code>wait()</code> #loc
integer.multiplication.implicit.cast.to.long.display.name=Integer multiplication or shift implicitly cast to long
integer.multiplication.implicit.cast.to.long.problem.descriptor=#ref: integer multiplication implicitly cast to long #loc
integer.multiplication.implicit.cast.to.long.quickfix=Cast to long
integer.shift.implicit.cast.to.long.problem.descriptor=#ref: integer shift implicitly cast to long #loc
integer.multiplication.implicit.cast.to.long.option=<html>Ignore expressions where it's proven statically that overflow is impossible</html>
wait.or.await.without.timeout.display.name='wait()' or 'await()' without timeout

View File

@@ -15,10 +15,12 @@
*/
package com.siyeh.ig.numeric;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.ConstantEvaluationOverflowException;
@@ -27,12 +29,17 @@ import com.intellij.psi.util.TypeConversionUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.ExpectedTypeUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -64,6 +71,12 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe
"integer.multiplication.implicit.cast.to.long.display.name");
}
@Nullable
@Override
protected InspectionGadgetsFix buildFix(Object... infos) {
return new IntegerMultiplicationImplicitCastToLongInspectionFix();
}
@Override
@NotNull
protected String buildErrorString(Object... infos) {
@@ -88,6 +101,109 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe
return new IntegerMultiplicationImplicitlyCastToLongVisitor();
}
private static boolean isNonLongInteger(PsiType type) {
if (type == null) return false;
final String text = type.getCanonicalText();
return s_typesToCheck.contains(text);
}
/**
* Checks whether one of operands of polyadic expression itself is polyadic expression with multiplication operator.
* For shift operations only first operand is considered.
*/
private static boolean hasInnerMultiplication(@NotNull PsiPolyadicExpression expression) {
final IElementType tokenType = expression.getOperationTokenType();
if (isShiftToken(tokenType)) {
return hasMultiplication(expression.getOperands()[0]);
}
return Arrays.stream(expression.getOperands()).anyMatch(operand -> hasMultiplication(operand));
}
private static boolean hasMultiplication(PsiExpression expression) {
expression = PsiUtil.deparenthesizeExpression(expression);
if (expression instanceof PsiPrefixExpression) {
return hasMultiplication(((PsiPrefixExpression)expression).getOperand());
}
if (expression instanceof PsiPolyadicExpression) {
final PsiPolyadicExpression polyExpr = (PsiPolyadicExpression)expression;
final IElementType tokenType = polyExpr.getOperationTokenType();
if (tokenType == JavaTokenType.ASTERISK) {
return true;
}
return hasInnerMultiplication(polyExpr);
}
if (expression instanceof PsiConditionalExpression) {
final PsiConditionalExpression ternary = (PsiConditionalExpression)expression;
return hasMultiplication(ternary.getThenExpression()) || hasMultiplication(ternary.getElseExpression());
}
return false;
}
private static boolean isShiftToken(IElementType tokenType) {
return tokenType.equals(JavaTokenType.LTLT) ||
tokenType.equals(JavaTokenType.GTGT) ||
tokenType.equals(JavaTokenType.GTGTGT);
}
private static class IntegerMultiplicationImplicitCastToLongInspectionFix extends InspectionGadgetsFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("integer.multiplication.implicit.cast.to.long.quickfix");
}
@Override
protected void doFix(Project project, ProblemDescriptor descriptor) {
final PsiPolyadicExpression expression = (PsiPolyadicExpression)descriptor.getPsiElement();
final PsiExpression[] operands = expression.getOperands();
if (operands.length < 2) {
return;
}
final PsiExpression exprToCast;
if (operands.length > 2 || expression.getOperationTokenType() == JavaTokenType.LTLT) {
exprToCast = operands[0];
}
else {
exprToCast = Arrays.stream(operands)
.map(operand -> PsiUtil.deparenthesizeExpression(operand))
.filter(operand -> operand instanceof PsiLiteralExpression ||
operand instanceof PsiPrefixExpression && ((PsiPrefixExpression)operand).getOperand() instanceof PsiLiteral)
.findFirst()
.orElse(operands[0]);
}
addCast(exprToCast);
}
private static void addCast(@NotNull PsiExpression expression) {
if (expression instanceof PsiPrefixExpression) {
final PsiExpression operand = ((PsiPrefixExpression)expression).getOperand();
if (operand instanceof PsiLiteralExpression) expression = operand;
}
final String replacementText;
if (expression instanceof PsiLiteralExpression) {
replacementText = expression.getText() + "L";
}
else {
replacementText = "(long)" + expression.getText();
}
PsiReplacementUtil.replaceExpression(expression, replacementText);
}
}
private class IntegerMultiplicationImplicitlyCastToLongVisitor
extends BaseInspectionVisitor {
@@ -102,20 +218,24 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe
if (!isNonLongInteger(type)) {
return;
}
if (hasInnerMultiplication(expression)) {
return;
}
PsiExpression[] operands = expression.getOperands();
if (operands.length < 2 || expression.getLastChild() instanceof PsiErrorElement) {
return;
}
final PsiExpression context = getContainingExpression(expression);
PsiExpression context = getContainingExpression(expression);
if (context == null) return;
if (!PsiType.LONG.equals(context.getType()) &&
!PsiType.LONG.equals(ExpectedTypeUtils.findExpectedType(context, true))) {
return;
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(context.getParent());
if (parent instanceof PsiTypeCastExpression) {
PsiType castType = ((PsiTypeCastExpression)parent).getType();
if (isNonLongInteger(castType)) return;
if (PsiType.LONG.equals(castType)) context = (PsiExpression)parent;
}
if (!PsiType.LONG.equals(context.getType()) &&
!PsiType.LONG.equals(ExpectedTypeUtils.findExpectedType(context, true))) {
return;
}
if (ignoreNonOverflowingCompileTimeConstants) {
try {
@@ -177,8 +297,9 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe
PsiExpression expression) {
final PsiElement parent = expression.getParent();
if (parent instanceof PsiPolyadicExpression && TypeConversionUtil.isNumericType(((PsiPolyadicExpression)parent).getType())) {
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
if (!tokenType.equals(JavaTokenType.LTLT) && !tokenType.equals(JavaTokenType.GTGT) && !tokenType.equals(JavaTokenType.GTGTGT)) {
final PsiPolyadicExpression polyParent = (PsiPolyadicExpression)parent;
final IElementType tokenType = polyParent.getOperationTokenType();
if (!isShiftToken(tokenType) || expression == polyParent.getOperands()[0]) {
return getContainingExpression((PsiExpression)parent);
}
}
@@ -189,11 +310,5 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe
}
return expression;
}
private boolean isNonLongInteger(PsiType type) {
if (type == null) return false;
final String text = type.getCanonicalText();
return s_typesToCheck.contains(text);
}
}
}

View File

@@ -5,10 +5,19 @@ public class IntegerMultiplicationImplicitCastToLong {
// Shift operations are not subjected to binary promotion (JLS 15.19), operands are promoted separately using unary promotion rules
long l = 1L << (step * 8);
}
void leftArgOfShift(int i) {
long l = (<warning descr="i * i: integer multiplication implicitly cast to long">i * i</warning>) << 8;
}
void testConcat(int step) {
String res = "Result: "+(1L + <warning descr="1000*step: integer multiplication implicitly cast to long">1000*step</warning>);
}
void reportOnlyInnerMultInMultOrShift(int i) {
long l = (i * (<warning descr="i * i: integer multiplication implicitly cast to long">i * i</warning>));
long l1 = (<warning descr="i * i: integer multiplication implicitly cast to long">i * i</warning>) << 1;
}
public void foo() {
int x = 65336;
@@ -58,4 +67,8 @@ public class IntegerMultiplicationImplicitCastToLong {
int explicitCast(int a, long b) {
return (int)(a * 300 + b);
}
long longCastAfterOverflow(int a, int b, int c) {
return (long) (<warning descr="a * b * c: integer multiplication implicitly cast to long">a * b * c</warning>);
}
}

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.numeric;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import org.jetbrains.annotations.NotNull;
public class IntegerMultiplicationImplicitCastToLongInspectionFixTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new IntegerMultiplicationImplicitCastToLongInspection()};
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/implicitCastToLong";
}
}