[java-inspections] IDEA-326604 Constant expression can be evaluated using dataflow analysis

GitOrigin-RevId: 5e663ac98c323faf36afcaf18115b6e4036d980d
This commit is contained in:
Tagir Valeev
2023-07-27 16:14:45 +02:00
committed by intellij-monorepo-bot
parent 159dbaaaa8
commit dbf25ab12b
12 changed files with 148 additions and 37 deletions

View File

@@ -0,0 +1,8 @@
// "Compute constant value of 'x1'" "true-preview"
class Test {
void test() {
var x1 = Test.class;
System.out.println(Test.class);
}
}

View File

@@ -0,0 +1,7 @@
// "Compute constant value of 'i'" "true-preview"
class Test {
void test(int i) {
if (i != 10) return;
System.out.println(10);
}
}

View File

@@ -0,0 +1,10 @@
// "Compute constant value of 'x'" "true-preview"
class Test {
enum X {A, B}
void test() {
X x = X.A;
System.out.println(X.A);
}
}

View File

@@ -0,0 +1,6 @@
// "Compute constant value of 'Math.sqrt(...) + 10'" "true-preview"
class Test {
void test() {
double res = 11.414213562373096;
}
}

View File

@@ -1,4 +1,4 @@
// "Compute constant value of '0x7fffffffffffffffL == 0x7ffffffffffffffeL'" "true-preview"
// "Replace with constant value" "true-preview"
class Test {
boolean result = false;
}

View File

@@ -0,0 +1,8 @@
// "Compute constant value of 'x1'" "true-preview"
class Test {
void test() {
var x1 = Test.class;
System.out.println(x<caret>1);
}
}

View File

@@ -0,0 +1,7 @@
// "Compute constant value of 'i'" "true-preview"
class Test {
void test(int i) {
if (i != 10) return;
System.out.println(<caret>i);
}
}

View File

@@ -0,0 +1,10 @@
// "Compute constant value of 'x'" "true-preview"
class Test {
enum X {A, B}
void test() {
X x = X.A;
System.out.println(<caret>x);
}
}

View File

@@ -0,0 +1,6 @@
// "Compute constant value of 'Math.sqrt(...) + 10'" "true-preview"
class Test {
void test() {
double res = Math.<caret>sqrt(2) + 10;
}
}

View File

@@ -1,4 +1,4 @@
// "Compute constant value of '0x7fffffffffffffffL == 0x7ffffffffffffffeL'" "true-preview"
// "Replace with constant value" "true-preview"
class Test {
boolean result = 0x7fffffffffffffffL<caret> == 0x7ffffffffffffffeL;
}

View File

@@ -2259,6 +2259,10 @@ inspection.constant.expression.fix.name.short=Replace with constant value
inspection.constant.expression.fix.name.with.value=Replace ''{0}'' with constant value ''{1}''
inspection.constant.expression.fix.family.name=Compute constant value
inspection.constant.expression.skip.non.literal=Don't report when the expression contains references to defined constants
inspection.constant.expression.report.compile.time=Report only compile time constants
inspection.constant.expression.report.compile.time.description=By default, the inspection can evaluate some constants that involve \
library method calls, or even deduce the constant value based on the surrounding code. \
Check this option to report only compile time constants, according to Java language specification.
inspection.redundant.compare.call.display.name=Redundant 'compare()' method call
inspection.redundant.compare.call.fix.name=Inline 'compare()' call

View File

@@ -4,6 +4,7 @@ package com.siyeh.ig.style;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
@@ -18,21 +19,29 @@ import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
public class ConstantExpressionInspection extends AbstractBaseJavaLocalInspectionTool {
private static final int MAX_RESULT_LENGTH_TO_DISPLAY = 40;
private static final int MAX_EXPRESSION_LENGTH = 200;
public boolean skipIfContainsReferenceExpression = false;
public boolean reportOnlyCompileTimeConstants = false;
@Override
public @NotNull OptPane getOptionsPane() {
return OptPane.pane(
OptPane.checkbox("skipIfContainsReferenceExpression",
InspectionGadgetsBundle.message("inspection.constant.expression.skip.non.literal")));
InspectionGadgetsBundle.message("inspection.constant.expression.skip.non.literal")),
OptPane.checkbox("reportOnlyCompileTimeConstants",
InspectionGadgetsBundle.message("inspection.constant.expression.report.compile.time"))
.description(InspectionGadgetsBundle.message("inspection.constant.expression.report.compile.time.description")));
}
@NotNull
@@ -40,51 +49,78 @@ public class ConstantExpressionInspection extends AbstractBaseJavaLocalInspectio
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitUnaryExpression(@NotNull PsiUnaryExpression expression) {
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
handle(expression);
}
@Override
public void visitPolyadicExpression(@NotNull PsiPolyadicExpression expression) {
public void visitExpression(@NotNull PsiExpression expression) {
handle(expression);
}
void handle(PsiExpression expression) {
private void handle(@NotNull PsiExpression expression) {
// inspection disabled for long expressions because of performance issues on
// relatively common large string expressions.
if (expression.getTextLength() > MAX_EXPRESSION_LENGTH) return;
if (expression.getType() == null) return;
if (!PsiUtil.isConstantExpression(expression)) return;
final PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiExpression && PsiUtil.isConstantExpression((PsiExpression)parent)) return;
Object value = computeConstant(expression);
if (value == null) return;
if (value instanceof PsiField && !(value instanceof PsiEnumConstant)) return;
if (value instanceof PsiElement e && expression instanceof PsiReferenceExpression ref && ref.isReferenceTo(e)) return;
String valueText = getValueText(value);
if (valueText == null || expression.textMatches(valueText)) return;
String message = valueText.length() > MAX_RESULT_LENGTH_TO_DISPLAY ?
InspectionGadgetsBundle.message("inspection.constant.expression.display.name") :
InspectionGadgetsBundle.message("inspection.constant.expression.message", valueText);
if (skipIfContainsReferenceExpression && hasReferences(expression)) {
if (isOnTheFly) {
holder.registerProblem(expression, message, ProblemHighlightType.INFORMATION,
new ComputeConstantValueFix(expression, valueText));
}
}
else {
holder.registerProblem(expression, message,
new ComputeConstantValueFix(expression, valueText));
}
}
@Nullable
private Object computeConstant(PsiExpression expression) {
if (expression.getTextLength() > MAX_EXPRESSION_LENGTH) return null;
if (expression.getType() == null) return null;
Object value = computeValue(expression);
if (value == null) return null;
final PsiExpression parent = getParentExpression(expression);
if (parent != null && computeValue(parent) != null) return null;
return value;
}
private Object computeValue(PsiExpression expression) {
if (expression instanceof PsiClassObjectAccessExpression) return null;
try {
final Object value = ExpressionUtils.computeConstantExpression(expression, true);
if (value != null) {
String valueText = getValueText(value);
if (!expression.textMatches(valueText)) {
String message = valueText.length() > MAX_RESULT_LENGTH_TO_DISPLAY ?
InspectionGadgetsBundle.message("inspection.constant.expression.display.name") :
InspectionGadgetsBundle.message("inspection.constant.expression.message", valueText);
if (skipIfContainsReferenceExpression &&
hasReferences(expression)) {
if (isOnTheFly) {
holder.registerProblem(expression, message, ProblemHighlightType.INFORMATION,
new ComputeConstantValueFix(expression, valueText));
}
}
else {
holder.registerProblem(expression, message,
new ComputeConstantValueFix(expression, valueText));
}
}
Object value = ExpressionUtils.computeConstantExpression(expression, true);
if (value != null && !(value instanceof Enum<?>)) {
return value;
}
}
catch (ConstantEvaluationOverflowException ignore) {
return null;
}
if (reportOnlyCompileTimeConstants) return null;
if (SideEffectChecker.mayHaveSideEffects(expression)) return null;
return CommonDataflow.computeValue(expression);
}
@Nullable
private static PsiExpression getParentExpression(PsiExpression expression) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiExpressionList || parent instanceof PsiTemplate) {
parent = parent.getParent();
}
return tryCast(parent, PsiExpression.class);
}
private static boolean hasReferences(@NotNull PsiExpression expression) {
return PsiTreeUtil.getChildOfAnyType(expression, PsiReferenceExpression.class) != null;
return expression instanceof PsiReferenceExpression ||
PsiTreeUtil.getChildOfType(expression, PsiReferenceExpression.class) != null;
}
};
}
@@ -102,7 +138,7 @@ public class ConstantExpressionInspection extends AbstractBaseJavaLocalInspectio
@NotNull
@Override
public String getName() {
if (myValueText.length() < MAX_RESULT_LENGTH_TO_DISPLAY) {
if (myText.length() < MAX_RESULT_LENGTH_TO_DISPLAY) {
return InspectionGadgetsBundle.message("inspection.constant.expression.fix.name", myText);
}
return InspectionGadgetsBundle.message("inspection.constant.expression.fix.name.short");
@@ -118,9 +154,7 @@ public class ConstantExpressionInspection extends AbstractBaseJavaLocalInspectio
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
final PsiExpression expression = (PsiExpression)element;
final Object value = ExpressionUtils.computeConstantExpression(expression);
@NonNls final String newExpression = getValueText(value);
PsiReplacementUtil.replaceExpression(expression, newExpression, new CommentTracker());
PsiReplacementUtil.replaceExpression(expression, myValueText, new CommentTracker());
}
}
@@ -172,6 +206,17 @@ public class ConstantExpressionInspection extends AbstractBaseJavaLocalInspectio
else if (value == null) {
newExpression = "null";
}
else if (value instanceof PsiField field) {
PsiClass containingClass = field.getContainingClass();
if (containingClass == null) return null;
return containingClass.getQualifiedName() + "." + field.getName();
}
else if (value instanceof PsiType) {
if (value instanceof PsiClassType clsType) {
return clsType.getCanonicalText() + ".class";
}
return null;
}
else {
newExpression = String.valueOf(value);
}