IDEADEV-31824 (Incorrect "manual array copy" warning)
[fedora-idea.git] / plugins / InspectionGadgets / src / com / siyeh / ig / performance / ManualArrayCopyInspection.java
blob2ec55f1f7b4dde2fc4d1ca96471777e2ac86f89a
1 /*
2 * Copyright 2003-2008 Dave Griffith, Bas Leijdekkers
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.siyeh.ig.performance;
18 import com.intellij.codeInspection.ProblemDescriptor;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.psi.*;
21 import com.intellij.psi.tree.IElementType;
22 import com.intellij.psi.util.PsiUtil;
23 import com.intellij.util.IncorrectOperationException;
24 import com.siyeh.InspectionGadgetsBundle;
25 import com.siyeh.ig.BaseInspection;
26 import com.siyeh.ig.BaseInspectionVisitor;
27 import com.siyeh.ig.InspectionGadgetsFix;
28 import com.siyeh.ig.psiutils.ExpressionUtils;
29 import com.siyeh.ig.psiutils.ParenthesesUtils;
30 import com.siyeh.ig.psiutils.SideEffectChecker;
31 import com.siyeh.ig.psiutils.VariableAccessUtils;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
36 public class ManualArrayCopyInspection extends BaseInspection {
38 @Override
39 @NotNull
40 public String getDisplayName() {
41 return InspectionGadgetsBundle.message(
42 "manual.array.copy.display.name");
45 @Override
46 public boolean isEnabledByDefault() {
47 return true;
50 @Override
51 @NotNull
52 protected String buildErrorString(Object... infos) {
53 return InspectionGadgetsBundle.message(
54 "manual.array.copy.problem.descriptor");
57 @Override
58 public BaseInspectionVisitor buildVisitor() {
59 return new ManualArrayCopyVisitor();
62 @Override
63 public InspectionGadgetsFix buildFix(Object... infos) {
64 return new ManualArrayCopyFix();
67 private static class ManualArrayCopyFix extends InspectionGadgetsFix {
69 @NotNull
70 public String getName() {
71 return InspectionGadgetsBundle.message(
72 "manual.array.copy.replace.quickfix");
75 @Override
76 public void doFix(Project project, ProblemDescriptor descriptor)
77 throws IncorrectOperationException {
78 final PsiElement forElement = descriptor.getPsiElement();
79 final PsiForStatement forStatement =
80 (PsiForStatement)forElement.getParent();
81 final String newExpression = getSystemArrayCopyText(forStatement);
82 if (newExpression == null) {
83 return;
85 replaceStatement(forStatement, newExpression);
88 @Nullable
89 private static String getSystemArrayCopyText(
90 PsiForStatement forStatement)
91 throws IncorrectOperationException {
92 final PsiExpression condition = forStatement.getCondition();
93 final PsiBinaryExpression binaryExpression =
94 (PsiBinaryExpression)PsiUtil.deparenthesizeExpression(
95 condition);
96 if (binaryExpression == null) {
97 return null;
99 final PsiExpression limit;
100 if (JavaTokenType.LT.equals(
101 binaryExpression.getOperationTokenType())) {
102 limit = binaryExpression.getROperand();
103 } else {
104 limit = binaryExpression.getLOperand();
106 if (limit == null) {
107 return null;
109 final PsiStatement initialization =
110 forStatement.getInitialization();
111 if (initialization == null) {
112 return null;
114 if (!(initialization instanceof PsiDeclarationStatement)) {
115 return null;
117 final PsiDeclarationStatement declaration =
118 (PsiDeclarationStatement)initialization;
119 if (declaration.getDeclaredElements().length != 1) {
120 return null;
122 final PsiLocalVariable variable = (PsiLocalVariable)
123 declaration.getDeclaredElements()[0];
124 final String lengthText = getLengthText(limit, variable);
125 final PsiExpressionStatement body = getBody(forStatement);
126 if (body == null) {
127 return null;
129 final PsiAssignmentExpression assignment =
130 (PsiAssignmentExpression)body.getExpression();
131 final PsiExpression lExpression = assignment.getLExpression();
132 final PsiArrayAccessExpression lhs = (PsiArrayAccessExpression)
133 PsiUtil.deparenthesizeExpression(lExpression);
134 if (lhs == null) {
135 return null;
137 final PsiExpression lArray = lhs.getArrayExpression();
138 final String toArrayText = lArray.getText();
139 final PsiExpression rExpression = assignment.getRExpression();
140 final PsiArrayAccessExpression rhs = (PsiArrayAccessExpression)
141 PsiUtil.deparenthesizeExpression(rExpression);
142 if (rhs == null) {
143 return null;
145 final PsiExpression rArray = rhs.getArrayExpression();
146 final String fromArrayText = rArray.getText();
147 final PsiExpression rhsIndexExpression = rhs.getIndexExpression();
148 final PsiExpression strippedRhsIndexExpression =
149 PsiUtil.deparenthesizeExpression(rhsIndexExpression);
150 final String fromOffsetText =
151 getOffsetText(strippedRhsIndexExpression, variable);
152 final PsiExpression lhsIndexExpression = lhs.getIndexExpression();
153 final PsiExpression strippedLhsIndexExpression =
154 PsiUtil.deparenthesizeExpression(lhsIndexExpression);
155 final String toOffsetText =
156 getOffsetText(strippedLhsIndexExpression, variable);
157 @NonNls final StringBuilder buffer = new StringBuilder(60);
158 buffer.append("System.arraycopy(");
159 buffer.append(fromArrayText);
160 buffer.append(", ");
161 buffer.append(fromOffsetText);
162 buffer.append(", ");
163 buffer.append(toArrayText);
164 buffer.append(", ");
165 buffer.append(toOffsetText);
166 buffer.append(", ");
167 buffer.append(lengthText);
168 buffer.append(");");
169 return buffer.toString();
172 @NonNls @Nullable
173 private static String getLengthText(PsiExpression expression,
174 PsiVariable variable) {
175 expression =
176 PsiUtil.deparenthesizeExpression(expression);
177 if (expression == null) {
178 return null;
180 final PsiExpression initializer = variable.getInitializer();
181 final String expressionText = expression.getText();
182 if (initializer == null) {
183 return expressionText;
185 if (ExpressionUtils.isZero(initializer)) {
186 return expressionText;
188 return expressionText + '-' + initializer.getText();
191 @NonNls @Nullable
192 private static String getOffsetText(PsiExpression expression,
193 PsiLocalVariable variable)
194 throws IncorrectOperationException {
195 if (expression == null) {
196 return null;
198 final String expressionText = expression.getText();
199 final String variableName = variable.getName();
200 if (expressionText.equals(variableName)) {
201 final PsiExpression initialValue = variable.getInitializer();
202 if (initialValue == null) {
203 return null;
205 return initialValue.getText();
207 if (expression instanceof PsiBinaryExpression) {
208 final PsiBinaryExpression binaryExpression =
209 (PsiBinaryExpression)expression;
210 final PsiExpression lhs = binaryExpression.getLOperand();
211 final String lhsText = getOffsetText(lhs, variable);
212 final PsiExpression rhs = binaryExpression.getROperand();
213 final String rhsText = getOffsetText(rhs, variable);
214 final PsiJavaToken sign = binaryExpression.getOperationSign();
215 final IElementType tokenType = sign.getTokenType();
216 if (lhsText == null || lhsText.equals("0")) {
217 if (tokenType.equals(JavaTokenType.MINUS)) {
218 return '-' + rhsText;
220 return rhsText;
222 if (rhsText == null || rhsText.equals("0")) {
223 return lhsText;
225 return collapseConstant(lhsText + sign.getText() + rhsText,
226 variable);
228 return collapseConstant(expression.getText(), variable);
231 private static String collapseConstant(@NonNls String expressionText,
232 PsiElement context)
233 throws IncorrectOperationException {
234 final PsiManager manager = context.getManager();
235 final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
236 final PsiConstantEvaluationHelper evaluationHelper = JavaPsiFacade.getInstance(manager.getProject()).getConstantEvaluationHelper();
237 final PsiExpression fromOffsetExpression =
238 factory.createExpressionFromText(expressionText, context);
239 final Object fromOffsetConstant =
240 evaluationHelper.computeConstantExpression(
241 fromOffsetExpression);
242 if (fromOffsetConstant != null) {
243 return fromOffsetConstant.toString();
244 } else {
245 return expressionText;
249 @Nullable
250 private static PsiExpressionStatement getBody(
251 PsiForStatement forStatement) {
252 PsiStatement body = forStatement.getBody();
253 while (body instanceof PsiBlockStatement) {
254 final PsiBlockStatement blockStatement =
255 (PsiBlockStatement)body;
256 final PsiCodeBlock codeBlock = blockStatement.getCodeBlock();
257 final PsiStatement[] statements = codeBlock.getStatements();
258 body = statements[0];
260 return (PsiExpressionStatement)body;
264 private static class ManualArrayCopyVisitor extends BaseInspectionVisitor {
266 @Override public void visitForStatement(@NotNull PsiForStatement statement) {
267 super.visitForStatement(statement);
268 final PsiStatement initialization =
269 statement.getInitialization();
270 if (!(initialization instanceof PsiDeclarationStatement)) {
271 return;
273 final PsiDeclarationStatement declaration =
274 (PsiDeclarationStatement)initialization;
275 if (declaration.getDeclaredElements().length != 1) {
276 return;
278 final PsiLocalVariable variable = (PsiLocalVariable)
279 declaration.getDeclaredElements()[0];
280 final PsiExpression initialValue = variable.getInitializer();
281 if (initialValue == null) {
282 return;
284 final PsiExpression condition = statement.getCondition();
285 if (!ExpressionUtils.isComparison(condition, variable)) {
286 return;
288 final PsiStatement update = statement.getUpdate();
289 if (!VariableAccessUtils.variableIsIncremented(variable, update)) {
290 return;
292 final PsiStatement body = statement.getBody();
293 if (!bodyIsArrayCopy(body, variable)) {
294 return;
296 registerStatementError(statement);
299 private static boolean bodyIsArrayCopy(PsiStatement body,
300 PsiLocalVariable variable) {
301 if (body instanceof PsiExpressionStatement) {
302 final PsiExpressionStatement exp =
303 (PsiExpressionStatement)body;
304 final PsiExpression expression = exp.getExpression();
305 return expressionIsArrayCopy(expression, variable);
306 } else if (body instanceof PsiBlockStatement) {
307 final PsiBlockStatement blockStatement =
308 (PsiBlockStatement)body;
309 final PsiCodeBlock codeBlock = blockStatement.getCodeBlock();
310 final PsiStatement[] statements = codeBlock.getStatements();
311 return statements.length == 1 &&
312 bodyIsArrayCopy(statements[0], variable);
314 return false;
317 private static boolean expressionIsArrayCopy(
318 @Nullable PsiExpression expression,
319 @NotNull PsiVariable variable) {
320 final PsiExpression strippedExpression =
321 PsiUtil.deparenthesizeExpression(expression);
322 if (strippedExpression == null) {
323 return false;
325 if (!(strippedExpression instanceof PsiAssignmentExpression)) {
326 return false;
328 final PsiAssignmentExpression assignment =
329 (PsiAssignmentExpression)strippedExpression;
330 final PsiJavaToken sign = assignment.getOperationSign();
331 final IElementType tokenType = sign.getTokenType();
332 if (!tokenType.equals(JavaTokenType.EQ)) {
333 return false;
335 final PsiExpression lhs = assignment.getLExpression();
336 if (SideEffectChecker.mayHaveSideEffects(lhs)) {
337 return false;
339 if (!ExpressionUtils.isOffsetArrayAccess(lhs, variable)) {
340 return false;
342 final PsiExpression rhs = assignment.getRExpression();
343 if (rhs == null) {
344 return false;
346 if (SideEffectChecker.mayHaveSideEffects(rhs)) {
347 return false;
349 if (!areExpressionsCopyable(lhs, rhs)) {
350 return false;
352 final PsiType type = lhs.getType();
353 if (type instanceof PsiPrimitiveType) {
354 final PsiExpression strippedLhs =
355 ParenthesesUtils.stripParentheses(lhs);
356 final PsiExpression strippedRhs =
357 ParenthesesUtils.stripParentheses(rhs);
358 if (!areExpressionsCopyable(strippedLhs, strippedRhs)) {
359 return false;
362 return ExpressionUtils.isOffsetArrayAccess(rhs, variable);
365 private static boolean areExpressionsCopyable(
366 @Nullable PsiExpression lhs, @Nullable PsiExpression rhs) {
367 if (lhs == null || rhs == null) {
368 return false;
370 final PsiType lhsType = lhs.getType();
371 if (lhsType == null) {
372 return false;
374 final PsiType rhsType = rhs.getType();
375 if (rhsType == null) {
376 return false;
378 if (lhsType instanceof PsiPrimitiveType) {
379 if (!lhsType.equals(rhsType)) {
380 return false;
382 } else {
383 if (!lhsType.isAssignableFrom(rhsType) ||
384 rhsType instanceof PsiPrimitiveType) {
385 return false;
388 return true;