[java-inspections] IDEA-311273 'Explicit array filling' could process a case when array length is a variable

GitOrigin-RevId: cdf0c90b6844a750935198fa1efeb3c8e8af103b
This commit is contained in:
Mikhail Pyltsin
2023-01-26 13:59:59 +00:00
committed by intellij-monorepo-bot
parent ab16ae81bc
commit 8edd7f0dd1
9 changed files with 152 additions and 16 deletions
@@ -53,10 +53,10 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
CountingLoop loop = CountingLoop.from(statement);
if (loop == null || loop.isIncluding() || loop.isDescending()) return;
if (!ExpressionUtils.isZero(loop.getInitializer())) return;
IndexedContainer container = IndexedContainer.fromLengthExpression(loop.getBound());
if (container == null || !(container.getQualifier().getType() instanceof PsiArrayType)) return;
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(ControlFlowUtils.stripBraces(statement.getBody()));
if (assignment == null) return;
IndexedContainer container = getContainer(loop, assignment);
if (container == null || !(container.getQualifier().getType() instanceof PsiArrayType)) return;
PsiExpression index = container.extractIndexFromGetExpression(assignment.getLExpression());
if (!ExpressionUtils.isReferenceTo(index, loop.getCounter())) return;
PsiExpression rValue = assignment.getRExpression();
@@ -80,7 +80,7 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
registerProblem(statement, true);
}
private boolean isChangedInLoop(@NotNull CountingLoop loop, @NotNull PsiExpression rValue) {
private static boolean isChangedInLoop(@NotNull CountingLoop loop, @NotNull PsiExpression rValue) {
if (VariableAccessUtils.collectUsedVariables(rValue).contains(loop.getCounter()) ||
SideEffectChecker.mayHaveSideEffects(rValue)) {
return true;
@@ -90,7 +90,7 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
.anyMatch(call -> !ClassUtils.isImmutable(call.getType()) && !ConstructionUtils.isEmptyArrayInitializer(call));
}
private boolean isDefaultValue(@NotNull PsiExpression expression, @Nullable Object defaultValue, @Nullable PsiType lType) {
private static boolean isDefaultValue(@NotNull PsiExpression expression, @Nullable Object defaultValue, @Nullable PsiType lType) {
if (ExpressionUtils.isNullLiteral(expression) && defaultValue == null) return true;
Object constantValue = ExpressionUtils.computeConstantExpression(expression);
PsiType rType = expression.getType();
@@ -102,9 +102,9 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
return constantValue != null && constantValue.equals(defaultValue);
}
private boolean isFilledWithDefaultValues(@NotNull PsiExpression expression,
@NotNull PsiForStatement statement,
@Nullable Object defaultValue) {
private static 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);
@@ -129,7 +129,7 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
}
@Nullable
private ControlFlow createControlFlow(@NotNull PsiCodeBlock block) {
private static ControlFlow createControlFlow(@NotNull PsiCodeBlock block) {
try {
return ControlFlowFactory.getInstance(block.getProject())
.getControlFlow(block, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
@@ -139,10 +139,10 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
}
}
private PsiElement @Nullable [] getDefs(@NotNull PsiCodeBlock block,
@NotNull PsiVariable arrayVar,
@NotNull PsiReferenceExpression arrayRef,
@Nullable Object defaultValue) {
private static PsiElement @Nullable [] 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++) {
@@ -165,7 +165,7 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
return expressions;
}
private boolean isNewArrayCreation(@Nullable PsiExpression expression, @Nullable Object defaultValue) {
private static boolean isNewArrayCreation(@Nullable PsiExpression expression, @Nullable Object defaultValue) {
PsiExpression arrInitExpr = PsiUtil.skipParenthesizedExprDown(expression);
PsiNewExpression newExpression = tryCast(arrInitExpr, PsiNewExpression.class);
PsiArrayInitializerExpression initializer;
@@ -181,7 +181,7 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
}
@Nullable
private Set<Integer> getDefsOffsets(@NotNull ControlFlow flow, PsiElement @NotNull [] defs) {
private static Set<Integer> getDefsOffsets(@NotNull ControlFlow flow, PsiElement @NotNull [] defs) {
Set<Integer> set = new HashSet<>();
for (PsiElement def : defs) {
int start = flow.getStartOffset(def);
@@ -256,10 +256,10 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
if (statement == null) return;
CountingLoop loop = CountingLoop.from(statement);
if (loop == null) return;
IndexedContainer container = IndexedContainer.fromLengthExpression(loop.getBound());
if (container == null) return;
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(ControlFlowUtils.stripBraces(statement.getBody()));
if (assignment == null) return;
IndexedContainer container = getContainer(loop, assignment);
if (container == null) return;
PsiExpression rValue = assignment.getRExpression();
if (rValue == null) return;
CommentTracker ct = new CommentTracker();
@@ -288,4 +288,16 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
return TypeConversionUtil.isAssignable(assignTo, rType) ? "" : "(" + elementType.getCanonicalText() + ")";
}
}
@Nullable
private static IndexedContainer getContainer(CountingLoop loop, PsiAssignmentExpression assignment) {
IndexedContainer container = IndexedContainer.fromLengthExpression(loop.getBound());
if (container == null) {
if (!(assignment.getLExpression() instanceof PsiArrayAccessExpression arrayAccessExpression)) {
return null;
}
container = IndexedContainer.arrayContainerWithBound(arrayAccessExpression, loop.getBound());
}
return container;
}
}
@@ -0,0 +1,10 @@
// "Remove 'for' statement" "true"
class Test {
public static int[] init(int n, boolean b) {
int[] data = new int[n];
data[n - 1] = 6;
return data;
}
}
@@ -0,0 +1,12 @@
// "Replace loop with 'Arrays.setAll()' method call" "true"
import java.util.Arrays;
class Test {
public static Object[] init(int n, boolean b) {
Object[] data = new Object[n];
Arrays.setAll(data, j -> (j / 2 + n == 0) ? "1" : new Object());
return data;
}
}
@@ -0,0 +1,13 @@
// "Remove 'for' statement" "false"
class Test {
public static int[] init(int n, boolean b) {
int[] data = new int[n];
data = new int[n + 1];
for (<caret>int j = 0; j < n; j++) {
data[j] = 0;
}
return data;
}
}
@@ -0,0 +1,13 @@
// "Remove 'for' statement" "true"
class Test {
public static int[] init(int n, boolean b) {
int[] data = new int[n];
for (<caret>int j = 0; j < n; j++) {
data[j] = 0;
}
data[n - 1] = 6;
return data;
}
}
@@ -0,0 +1,12 @@
// "Replace loop with 'Arrays.setAll()' method call" "true"
class Test {
public static Object[] init(int n, boolean b) {
Object[] data = new Object[n];
for (<caret>int j = 0; j < n; j++) {
data[j] = (j / 2 + n == 0) ? "1" : new Object();
}
return data;
}
}
@@ -0,0 +1,13 @@
// "Remove 'for' statement" "false"
class Test {
public static int[] init(int n, boolean b) {
int[] data = new int[n];
n = 10;
for (<caret>int j = 0; j < n; j++) {
data[j] = 0;
}
return data;
}
}
@@ -1032,6 +1032,15 @@ public final class ExpressionUtils {
return tryCast(referenceExpression.resolve(), PsiLocalVariable.class);
}
@Contract("null -> null")
@Nullable
public static PsiVariable resolveVariable(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
PsiReferenceExpression referenceExpression = tryCast(expression, PsiReferenceExpression.class);
if(referenceExpression == null) return null;
return tryCast(referenceExpression.resolve(), PsiVariable.class);
}
public static boolean isOctalLiteral(PsiLiteralExpression literal) {
final PsiType type = literal.getType();
if (!PsiType.INT.equals(type) && !PsiType.LONG.equals(type)) {
@@ -104,6 +104,48 @@ public abstract class IndexedContainer {
return null;
}
/**
* Use to create IndexedContainer for the next case:
* <pre>{@code
* int[] newArray = new int[arrayLength];
* for (int i=0; i < arrayLength; i++) {
* newArray[i] = 0;
* }
* }</pre>
* Additionally, the method that newArray and arrayLength are not reassigned
*
* @param arrayAccessExpression expression to create an IndexedContainer from
* @param bound reference to arrayLength
* @return newly created IndexedContainer or null if it is impossible to resolve it
*/
@Nullable
public static IndexedContainer arrayContainerWithBound(@NotNull PsiArrayAccessExpression arrayAccessExpression,
@NotNull PsiExpression bound) {
PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression();
if (arrayExpression instanceof PsiReferenceExpression reference &&
reference.resolve() instanceof PsiVariable arrayVariable) {
PsiExpression initializer = arrayVariable.getInitializer();
if (!(initializer instanceof PsiNewExpression newExpression)) {
return null;
}
PsiExpression[] dimensions = newExpression.getArrayDimensions();
if (dimensions.length != 1) {
return null;
}
PsiExpression dimension = dimensions[0];
PsiVariable dimensionVariable = ExpressionUtils.resolveVariable(dimension);
PsiVariable boundVariable = ExpressionUtils.resolveVariable(bound);
if (dimensionVariable == null || boundVariable == null || !dimensionVariable.isEquivalentTo(boundVariable)) {
return null;
}
if ((VariableAccessUtils.variableIsAssigned(dimensionVariable)) ||
(VariableAccessUtils.variableIsAssigned(arrayVariable))) {
return null;
}
}
return new ArrayIndexedContainer(arrayExpression);
}
static class ArrayIndexedContainer extends IndexedContainer {
ArrayIndexedContainer(@NotNull PsiExpression qualifier) {
super(qualifier);