ExplicitArrayFillingInspection: remove redundant filling in inner loops (IDEA-222832)

GitOrigin-RevId: f1ed7412c1431d6e810491a954563316fcdb582c
This commit is contained in:
Artemiy Sartakov
2019-09-17 10:02:31 +00:00
committed by intellij-monorepo-bot
parent c2737face2
commit 4ef8d9f6fb
8 changed files with 126 additions and 83 deletions
@@ -6,14 +6,13 @@ import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.JavaFeature;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
@@ -23,6 +22,7 @@ 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;
import java.util.function.Predicate;
@@ -61,9 +61,8 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
PsiExpression rValue = assignment.getRExpression();
if (rValue == null) return;
if (!isChangedInLoop(loop, rValue)) {
if (!ControlFlowUtils.isInLoop(statement) &&
isDefaultValueAssigned(assignment, rValue) &&
isFilledWithDefaultValues(container.getQualifier(), statement)) {
Object defaultValue = PsiTypesUtil.getDefaultValue(assignment.getType());
if (isDefaultValue(rValue, defaultValue) && isFilledWithDefaultValues(container.getQualifier(), statement, defaultValue)) {
holder.registerProblem(statement, getRange(statement, ProblemHighlightType.WARNING),
InspectionsBundle.message("inspection.explicit.array.filling.redundant.loop.description"),
QuickFixFactory.getInstance().createDeleteFix(statement));
@@ -88,88 +87,98 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
.anyMatch(call -> !ClassUtils.isImmutable(call.getType()) && !ConstructionUtils.isEmptyArrayInitializer(call));
}
private boolean isDefaultValueAssigned(@NotNull PsiAssignmentExpression assignment, @NotNull PsiExpression rhs) {
Object defaultValue = PsiTypesUtil.getDefaultValue(assignment.getType());
if (ExpressionUtils.isNullLiteral(rhs) && defaultValue == null) return true;
Object constantValue = ExpressionUtils.computeConstantExpression(rhs);
private boolean isDefaultValue(@NotNull PsiExpression expression, @Nullable Object defaultValue) {
if (ExpressionUtils.isNullLiteral(expression) && defaultValue == null) return true;
Object constantValue = ExpressionUtils.computeConstantExpression(expression);
return constantValue != null && constantValue.equals(defaultValue);
}
private boolean isFilledWithDefaultValues(@NotNull PsiExpression expression, @NotNull PsiForStatement forStatement) {
private boolean isFilledWithDefaultValues(@NotNull PsiExpression expression,
@NotNull PsiForStatement statement,
@Nullable Object defaultValue) {
PsiReferenceExpression arrayRef = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiReferenceExpression.class);
if (arrayRef == null) return false;
PsiVariable arrayVar = tryCast(arrayRef.resolve(), PsiVariable.class);
if (arrayVar == null) return false;
PsiCodeBlock block = tryCast(PsiUtil.getVariableCodeBlock(arrayVar, null), PsiCodeBlock.class);
if (block == null) return false;
Set<PsiStatement> defs = getDefsStatements(DefUseUtil.getDefs(block, arrayVar, arrayRef));
ControlFlow flow = createControlFlow(block);
if (flow == null) return false;
int statementStart = flow.getStartOffset(statement);
if (statementStart == -1) return false;
int statementEnd = flow.getEndOffset(statement);
if (statementEnd == -1) return false;
PsiElement[] defs = getDefs(block, arrayVar, arrayRef, defaultValue);
if (defs == null) return false;
return !isUsedBetween(forStatement, defs, arrayVar, block);
}
private boolean isUsedBetween(@NotNull PsiElement ref, @NotNull Set<PsiStatement> defs,
@NotNull PsiVariable arrayVar, @NotNull PsiCodeBlock block) {
Ref<Boolean> isUsed = Ref.create(false);
block.accept(new JavaRecursiveElementWalkingVisitor() {
boolean inContext;
@Override
public void visitStatement(PsiStatement statement) {
if (defs.contains(statement)) {
inContext = true;
}
else {
if (statement == ref) {
inContext = false;
stopWalking();
}
super.visitStatement(statement);
}
}
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
if (inContext && reference.isReferenceTo(arrayVar)) {
isUsed.set(true);
stopWalking();
}
super.visitReferenceElement(reference);
}
});
return isUsed.get();
Set<Integer> exclude = getDefsOffsets(flow, defs);
if (exclude == null) return false;
for (int i = statementStart; i < statementEnd; i++) {
exclude.add(i);
}
return Arrays.stream(defs)
.map(def -> flow.getEndOffset(def))
.noneMatch(offset -> ControlFlowUtils.isVariableReferencedBeforeStatementEntry(flow, offset + 1, statement, arrayVar, exclude));
}
@Nullable
private Set<PsiStatement> getDefsStatements(@NotNull PsiElement[] defs) {
Set<PsiStatement> statements = new HashSet<>();
for (PsiElement def : defs) {
private ControlFlow createControlFlow(@NotNull PsiCodeBlock block) {
try {
return ControlFlowFactory.getInstance(block.getProject())
.getControlFlow(block, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
}
catch (AnalysisCanceledException ignored) {
return null;
}
}
@Nullable
private PsiElement[] getDefs(@NotNull PsiCodeBlock block,
@NotNull PsiVariable arrayVar,
@NotNull PsiReferenceExpression arrayRef,
@Nullable Object defaultValue) {
PsiElement[] defs = DefUseUtil.getDefs(block, arrayVar, arrayRef);
PsiExpression[] expressions = new PsiExpression[defs.length];
for (int i = 0; i < defs.length; i++) {
PsiElement def = defs[i];
PsiVariable variable = tryCast(def, PsiVariable.class);
if (variable != null) {
PsiExpression initializer = variable.getInitializer();
if (initializer == null || !isNewArrayCreation(initializer)) return null;
PsiDeclarationStatement declaration = PsiTreeUtil.getParentOfType(initializer, PsiDeclarationStatement.class);
if (declaration == null) return null;
statements.add(declaration);
if (!isNewArrayCreation(initializer, defaultValue)) return null;
expressions[i] = initializer;
continue;
}
PsiAssignmentExpression assignment = PsiTreeUtil.getParentOfType(def, PsiAssignmentExpression.class);
if (assignment != null) {
if (!isNewArrayCreation(assignment.getRExpression())) return null;
PsiExpressionStatement expressionStatement = PsiTreeUtil.getParentOfType(assignment, PsiExpressionStatement.class);
if (expressionStatement == null) return null;
statements.add(expressionStatement);
if (!isNewArrayCreation(assignment.getRExpression(), defaultValue)) return null;
expressions[i] = assignment;
continue;
}
return null;
}
return statements;
return expressions;
}
private boolean isNewArrayCreation(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
return expression == null || expression instanceof PsiNewExpression;
private boolean isNewArrayCreation(@Nullable PsiExpression expression, @Nullable Object defaultValue) {
PsiNewExpression newExpression = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiNewExpression.class);
if (newExpression == null) return false;
PsiArrayInitializerExpression initializer = newExpression.getArrayInitializer();
if (initializer == null) return true;
return Arrays.stream(initializer.getInitializers()).allMatch(init -> isDefaultValue(init, defaultValue));
}
@Nullable
private Set<Integer> getDefsOffsets(@NotNull ControlFlow flow, @NotNull PsiElement[] defs) {
Set<Integer> set = new HashSet<>();
for (PsiElement def : defs) {
int start = flow.getStartOffset(def);
if (start == -1) return null;
int end = flow.getEndOffset(def);
if (end == -1) return null;
for (int i = start; i < end; i++) {
set.add(i);
}
}
return set;
}
private void registerProblem(@NotNull PsiForStatement statement, boolean isSetAll) {
@@ -0,0 +1,15 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
import java.util.Arrays;
public class Test {
public static int[] init(boolean b) {
int[] arr = new int[10];
if (b) {
arr = new int[]{1, 2, 3, 4, 5};
}
Arrays.fill(arr, 0);
return arr;
}
}
@@ -1,6 +1,4 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
import java.util.Arrays;
// "Delete element" "true"
public class Test {
@@ -8,9 +6,7 @@ public class Test {
int[] data = new int[n];
int i = 0;
while (i < 3) {
Arrays.fill(data, 0);
if (i < 2) data[n - 1] = 6;
i++;
i++;
}
return data;
}
@@ -2,8 +2,11 @@
public class Test {
public static int[] init(int[] arr) {
public static int[] init(int[] arr, boolean b) {
arr = new int[10];
if (b) {
arr = new int[20];
}
return arr;
}
}
@@ -0,0 +1,15 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
public class Test {
public static int[] init(boolean b) {
int[] arr = new int[10];
if (b) {
arr = new int[]{1, 2, 3, 4, 5};
}
for (<caret>int i = 0; i < arr.length; i++) {
arr[i] = 0;
}
return arr;
}
}
@@ -1,4 +1,4 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
// "Delete element" "true"
public class Test {
@@ -9,7 +9,6 @@ public class Test {
for (<caret>int j = 0; j < data.length; j++) {
data[j] = 0;
}
if (i < 2) data[n - 1] = 6;
i++;
}
return data;
@@ -2,8 +2,11 @@
public class Test {
public static int[] init(int[] arr) {
public static int[] init(int[] arr, boolean b) {
arr = new int[10];
if (b) {
arr = new int[20];
}
for (<caret>int i = 0; i < arr.length; i++) {
arr[i] = 0;
}
@@ -33,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.siyeh.ig.psiutils.ControlFlowUtils.InitializerUsageStatus.*;
@@ -773,16 +774,18 @@ public class ControlFlowUtils {
* Back-edges are also considered, so the actual place where it referenced might be outside of
* (start, loop entry) interval.
*
* @param flow ControlFlow to analyze
* @param start start point
* @param flow ControlFlow to analyze
* @param start start point
* @param statement loop to check
* @param variable variable to analyze
* @param variable variable to analyze
* @param excluded instructions to exclude
* @return true if variable can be referenced between start point and statement entry
*/
private static boolean isVariableReferencedBeforeStatementEntry(@NotNull ControlFlow flow,
final int start,
final PsiStatement statement,
@NotNull PsiVariable variable) {
public static boolean isVariableReferencedBeforeStatementEntry(@NotNull ControlFlow flow,
final int start,
final PsiStatement statement,
@NotNull PsiVariable variable,
@NotNull Set<Integer> excluded) {
final int statementStart = flow.getStartOffset(statement);
final int statementEnd = flow.getEndOffset(statement);
@@ -800,19 +803,19 @@ public class ControlFlowUtils {
int to = edge.myTo;
if(referenced.get(from)) {
// jump to the loop start from within the loop is not considered as loop entry
if(to == statementStart && (from < statementStart || from >= statementEnd)) {
if (to == statementStart && (from < statementStart || from >= statementEnd)) {
return true;
}
if(!referenced.get(to)) {
if (!referenced.get(to)) {
referenced.set(to);
changed = true;
}
continue;
}
if(ControlFlowUtil.isVariableAccess(flow, from, variable)) {
if (ControlFlowUtil.isVariableAccess(flow, from, variable) && !excluded.contains(from)) {
referenced.set(from);
referenced.set(to);
if(to == statementStart) return true;
if (to == statementStart) return true;
changed = true;
}
}
@@ -845,7 +848,7 @@ public class ControlFlowUtils {
}
int start = controlFlow.getEndOffset(var.getInitializer())+1;
int stop = controlFlow.getStartOffset(statement);
if(isVariableReferencedBeforeStatementEntry(controlFlow, start, statement, var)) return UNKNOWN;
if (isVariableReferencedBeforeStatementEntry(controlFlow, start, statement, var, Collections.emptySet())) return UNKNOWN;
if (!ControlFlowUtil.isValueUsedWithoutVisitingStop(controlFlow, start, stop, var)) return AT_WANTED_PLACE_ONLY;
return var.hasModifierProperty(PsiModifier.FINAL) ? UNKNOWN : AT_WANTED_PLACE;
}