ObjectAllocationInLoop: support new object returning methods; minor improvements

Fixes IDEA-184914 Object allocation in loop doesn't detect LocalDate.now()
This commit is contained in:
Tagir Valeev
2018-05-30 12:17:09 +07:00
parent 6f47af8ed4
commit cd56cab1bf
4 changed files with 101 additions and 72 deletions
@@ -1027,6 +1027,7 @@ public.field.accessed.in.synchronized.context.problem.descriptor=Non-private fie
field.accessed.synchronized.and.unsynchronized.problem.descriptor=Field <code>#ref</code> is accessed in both synchronized and unsynchronized contexts #loc
extended.for.statement.problem.descriptor=Extended <code>#ref</code> statement #loc
object.allocation.in.loop.problem.descriptor=Object allocation <code>new #ref()</code> in loop #loc
object.allocation.in.loop.problem.indirect.descriptor=Indirect object allocation via <code>#ref()</code> call in loop #loc
instantiating.object.to.get.class.object.problem.descriptor=Instantiating object to get Class object #loc
field.may.be.static.problem.descriptor=Field <code>#ref</code> may be 'static' #loc
method.may.be.static.problem.descriptor=Method <code>#ref()</code> may be 'static' #loc
@@ -15,14 +15,21 @@
*/
package com.siyeh.ig.performance;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.util.ObjectUtils.tryCast;
public class ObjectAllocationInLoopInspection extends BaseInspection {
@Override
@@ -35,8 +42,10 @@ public class ObjectAllocationInLoopInspection extends BaseInspection {
@Override
@NotNull
protected String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message(
"object.allocation.in.loop.problem.descriptor");
boolean direct = (boolean)infos[0];
return InspectionGadgetsBundle.message(direct
? "object.allocation.in.loop.problem.descriptor"
: "object.allocation.in.loop.problem.indirect.descriptor");
}
@Override
@@ -44,90 +53,64 @@ public class ObjectAllocationInLoopInspection extends BaseInspection {
return new ObjectAllocationInLoopsVisitor();
}
private static class ObjectAllocationInLoopsVisitor
extends BaseInspectionVisitor {
private static class ObjectAllocationInLoopsVisitor extends BaseInspectionVisitor {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
super.visitMethodCallExpression(call);
PsiMethod method = call.resolveMethod();
if (method != null) {
List<StandardMethodContract> contracts = JavaMethodContractUtil.getMethodContracts(method);
ContractReturnValue value = JavaMethodContractUtil.getNonFailingReturnValue(contracts);
if (ContractReturnValue.returnNew().equals(value) && isPerformedRepeatedlyInLoop(call)) {
registerMethodCallError(call, false);
}
}
}
@Override
public void visitNewExpression(@NotNull PsiNewExpression expression) {
super.visitNewExpression(expression);
if (!ControlFlowUtils.isInLoop(expression)) {
return;
if (isPerformedRepeatedlyInLoop(expression)) {
registerNewExpressionError(expression, true);
}
if (ControlFlowUtils.isInExitStatement(expression)) {
return;
}
final PsiStatement newExpressionStatement =
PsiTreeUtil.getParentOfType(expression, PsiStatement.class);
if (newExpressionStatement == null) {
return;
}
final PsiStatement parentStatement =
PsiTreeUtil.getParentOfType(newExpressionStatement,
PsiStatement.class);
if (!ControlFlowUtils.statementMayCompleteNormally(
parentStatement)) {
return;
}
if (isAllocatedOnlyOnce(expression)) {
return;
}
registerNewExpressionError(expression);
}
private static boolean isAllocatedOnlyOnce(
PsiNewExpression expression) {
final PsiElement parent = expression.getParent();
if (!(parent instanceof PsiAssignmentExpression)) {
private static boolean isPerformedRepeatedlyInLoop(@NotNull PsiExpression expression) {
if (!ControlFlowUtils.isInLoop(expression)) return false;
if (ControlFlowUtils.isInExitStatement(expression)) return false;
final PsiStatement newExpressionStatement = PsiTreeUtil.getParentOfType(expression, PsiStatement.class);
if (newExpressionStatement == null) return false;
final PsiStatement parentStatement = PsiTreeUtil.getParentOfType(newExpressionStatement, PsiStatement.class);
if (!ControlFlowUtils.statementMayCompleteNormally(parentStatement)) return false;
return !isAllocatedOnlyOnce(expression);
}
private static boolean isAllocatedOnlyOnce(PsiExpression expression) {
final PsiAssignmentExpression assignment =
PsiTreeUtil.getParentOfType(expression, PsiAssignmentExpression.class, true, PsiStatement.class);
if (assignment == null) return false;
final PsiReferenceExpression assignedRef = tryCast(assignment.getLExpression(), PsiReferenceExpression.class);
if (assignedRef == null) return false;
// to support cases like if(foo == null) foo = new Foo(new Bar());
if (assignment.getRExpression() != expression &&
NullnessUtil.getExpressionNullness(assignment.getRExpression()) != Nullness.NOT_NULL) {
return false;
}
final PsiAssignmentExpression assignmentExpression =
(PsiAssignmentExpression)parent;
final PsiExpression lExpression =
assignmentExpression.getLExpression();
if (!(lExpression instanceof PsiReferenceExpression)) {
return false;
final PsiIfStatement ifStatement = PsiTreeUtil.getParentOfType(assignment, PsiIfStatement.class);
if (ifStatement == null) return false;
boolean equals;
if (PsiTreeUtil.isAncestor(ifStatement.getThenBranch(), assignment, true)) {
equals = true;
}
final PsiIfStatement ifStatement =
PsiTreeUtil.getParentOfType(assignmentExpression,
PsiIfStatement.class);
if (ifStatement == null) {
return false;
}
final PsiExpression condition = ifStatement.getCondition();
if (!(condition instanceof PsiBinaryExpression)) {
return false;
}
final PsiBinaryExpression binaryExpression =
(PsiBinaryExpression)condition;
if (binaryExpression.getOperationTokenType() !=
JavaTokenType.EQEQ) {
return false;
}
final PsiReferenceExpression referenceExpression =
(PsiReferenceExpression)lExpression;
final PsiExpression lhs = binaryExpression.getLOperand();
final PsiExpression rhs = binaryExpression.getROperand();
if (lhs instanceof PsiLiteralExpression) {
if (!"null".equals(lhs.getText())) {
return false;
}
if (!(rhs instanceof PsiReferenceExpression)) {
return false;
}
return referenceExpression.getText().equals(rhs.getText());
}
else if (rhs instanceof PsiLiteralExpression) {
if (!"null".equals(rhs.getText())) {
return false;
}
if (!(lhs instanceof PsiReferenceExpression)) {
return false;
}
return referenceExpression.getText().equals(lhs.getText());
else if (PsiTreeUtil.isAncestor(ifStatement.getElseBranch(), assignment, true)) {
equals = false;
}
else {
return false;
}
final PsiExpression condition = ifStatement.getCondition();
PsiReferenceExpression nullCheckedRef = ExpressionUtils.getReferenceExpressionFromNullComparison(condition, equals);
return nullCheckedRef != null && PsiEquivalenceUtil.areElementsEquivalent(nullCheckedRef, assignedRef);
}
}
}
@@ -1,5 +1,7 @@
package com.siyeh.igtest.performance.object_allocation_in_loop;
import java.util.regex.*;
class ObjectAllocationInLoop {
void m() {
@@ -7,4 +9,39 @@ class ObjectAllocationInLoop {
new <warning descr="Object allocation 'new Object()' in loop">Object</warning>();
}
}
void m1() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
if(sb != null) {
sb.append(i);
} else {
sb = new StringBuilder(String.valueOf(i));
}
}
}
void m2() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
if (sb == null) {
sb = new StringBuilder(String.valueOf(i));
}
else {
sb.append(i);
}
}
}
boolean checkPatterns(String[] patterns) {
for (String pattern : patterns) {
try {
Pattern.<warning descr="Indirect object allocation via 'compile()' call in loop">compile</warning>(pattern);
}
catch (PatternSyntaxException exception) {
return false;
}
}
return true;
}
}
@@ -16,7 +16,9 @@
package com.siyeh.ig.performance;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightInspectionTestCase;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
@@ -27,5 +29,11 @@ public class ObjectAllocationInLoopInspectionTest extends LightInspectionTestCas
return new ObjectAllocationInLoopInspection();
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_9;
}
public void testObjectAllocationInLoop() { doTest(); }
}