Java inspection: Don't apply chained assignments in "Move return to computation" (IDEA-121153)

This commit is contained in:
Pavel Dolgov
2016-09-06 19:05:05 +03:00
parent 1fea799671
commit 68276bafcf
27 changed files with 105 additions and 153 deletions
@@ -58,7 +58,7 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
final PsiCodeBlock returnScope = (PsiCodeBlock)returnParent;
final PsiStatement[] statements = returnScope.getStatements();
if (statements.length != 0 && statements[statements.length - 1] == returnStatement) {
PsiStatement refactoredStatement = getPrevNonEmptyStatement(returnStatement, new THashSet<>());
PsiStatement refactoredStatement = getPrevNonEmptyStatement(returnStatement, null);
if (refactoredStatement != null) {
final PsiExpression returnValue = returnStatement.getReturnValue();
if (returnValue instanceof PsiReferenceExpression) {
@@ -82,7 +82,7 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
if (variable instanceof PsiLocalVariable) {
final PsiElement variableScope = RefactoringUtil.getVariableScope((PsiLocalVariable)variable);
if (variableScope instanceof PsiCodeBlock) {
return ((PsiCodeBlock)variableScope);
return (PsiCodeBlock)variableScope;
}
}
else if (variable instanceof PsiParameter) {
@@ -101,6 +101,44 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
return null;
}
/**
* Detect the case like the following:
* <pre>
* int result = size;
* result = 31 * result + width;
* result = 31 * result + height;
* return result;
* </pre>
*/
private static boolean hasChainedAssignmentsInScope(@NotNull ControlFlow flow, @NotNull PsiVariable variable,
@NotNull PsiReturnStatement returnStatement,
@NotNull PsiCodeBlock variableScope) {
for (PsiStatement statement = getPrevNonEmptyStatement(returnStatement, null);
statement != null;
statement = getPrevNonEmptyStatement(statement, null)) {
if (statement instanceof PsiExpressionStatement) {
PsiExpressionStatement expressionStatement = (PsiExpressionStatement)statement;
PsiExpression expression = expressionStatement.getExpression();
if (expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression;
if (isVariableUsed(flow, assignmentExpression.getLExpression(), variable) &&
isVariableUsed(flow, assignmentExpression.getRExpression(), variable)) {
return true;
}
}
}
}
return false;
}
private static boolean isVariableUsed(@NotNull ControlFlow flow, @Nullable PsiElement element, @NotNull PsiVariable variable) {
if (element == null) return false;
int startOffset = flow.getStartOffset(element);
int endOffset = flow.getEndOffset(element);
if (startOffset < 0 || endOffset < 0) return true;
return ControlFlowUtil.isVariableUsed(flow, startOffset, endOffset, variable);
}
private static boolean isApplicable(@NotNull ReturnContext context) {
final ControlFlow flow = createControlFlow(context);
return flow != null && isApplicable(flow, context);
@@ -126,6 +164,10 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
final int returnEndOffset = flow.getEndOffset(context.returnStatement);
if (returnStartOffset < 0 || returnEndOffset < 0) return false;
if (hasChainedAssignmentsInScope(flow, context.returnedVariable, context.returnStatement, context.variableScope)) {
return false;
}
if (context.returnScope != context.variableScope &&
ControlFlowUtil.hasObservableThrowExitPoints(flow, flowStart, flowEnd,
new PsiElement[]{context.refactoredStatement}, context.variableScope)) {
@@ -174,68 +216,6 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
context.returnStatement.delete();
}
private static void inlineReturnedValue(Mover mover, ReturnContext context) {
List<Instruction> instructions = mover.flow.getInstructions();
PsiAssignmentExpression assignment = null;
for (int i = 0; i < instructions.size(); i++) {
Instruction instruction = instructions.get(i);
if (instruction instanceof WriteVariableInstruction && ((WriteVariableInstruction)instruction).variable == mover.resultVariable) {
PsiElement element = mover.flow.getElement(i);
PsiElement parent = element.getParent();
if (element instanceof PsiAssignmentExpression && parent instanceof PsiExpressionStatement) {
if (!mover.replaceInline.contains(element)) {
if (assignment != null) {
return;
}
assignment = (PsiAssignmentExpression)element;
}
}
else if (!(getNearestEnclosingStatement(element) instanceof PsiDeclarationStatement)) {
return;
}
}
}
PsiExpression localInitializer = context.returnedVariable instanceof PsiLocalVariable ?
context.returnedVariable.getInitializer() : null;
if (assignment != null && localInitializer == null) {
PsiExpression rExpression = assignment.getRExpression();
replaceReturnedValue(rExpression, context.returnStatement, mover.flow);
}
else if (assignment == null && localInitializer != null) {
replaceReturnedValue(localInitializer, context.returnStatement, mover.flow);
}
}
private static void replaceReturnedValue(PsiExpression newReturnValue, PsiReturnStatement returnStatement, ControlFlow flow) {
PsiExpression returnValue = returnStatement.getReturnValue();
if (returnValue != null &&
(ExpressionUtils.computeConstantExpression(newReturnValue) != null || isUnchangedReferenceToLocal(newReturnValue, flow))) {
returnValue.replace(newReturnValue);
}
}
private static boolean isUnchangedReferenceToLocal(PsiExpression expression, ControlFlow flow) {
if (expression instanceof PsiReferenceExpression) {
PsiReferenceExpression referenceExpression = (PsiReferenceExpression)expression;
if (!referenceExpression.isQualified()) {
PsiElement resolved = referenceExpression.resolve();
if (resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) {
if (((PsiVariable)resolved).hasModifierProperty(PsiModifier.FINAL)) {
return true;
}
for (Instruction instruction : flow.getInstructions()) {
if (instruction instanceof WriteVariableInstruction && ((WriteVariableInstruction)instruction).variable == resolved) {
return false;
}
}
return true;
}
}
}
return false;
}
private static void inlineAssignment(PsiAssignmentExpression assignmentExpression, PsiReturnStatement returnStatement) {
PsiElement assignmentParent = assignmentExpression.getParent();
LOG.assertTrue(assignmentParent instanceof PsiExpressionStatement, "PsiExpressionStatement");
@@ -374,7 +354,7 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
return false;
}
PsiCodeBlock finallyBlock = targetStatement.getFinallyBlock();
if (finallyBlock != null && usesVariable(finallyBlock)) {
if (finallyBlock != null && isVariableUsed(flow, finallyBlock, resultVariable)) {
return false;
}
boolean allCatchesReturn = true;
@@ -423,15 +403,6 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
}
}
private boolean usesVariable(@NotNull PsiElement element) {
int startOffset = flow.getStartOffset(element);
int endOffset = flow.getEndOffset(element);
if (startOffset < 0 || endOffset < 0) {
return true;
}
return ControlFlowUtil.isVariableUsed(flow, startOffset, endOffset, resultVariable);
}
private static boolean isAlwaysTrue(@Nullable PsiExpression condition, boolean nullIsTrue) {
if(condition == null) return nullIsTrue;
return ExpressionUtils.computeConstantExpression(condition) == Boolean.TRUE;
@@ -467,7 +438,7 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
}
}
private static PsiStatement getPrevNonEmptyStatement(@NotNull PsiElement psiElement, @NotNull Set<PsiElement> skippedEmptyStatements) {
private static PsiStatement getPrevNonEmptyStatement(@NotNull PsiElement psiElement, @Nullable Set<PsiElement> skippedEmptyStatements) {
if (!(psiElement.getParent() instanceof PsiCodeBlock)) {
return null;
}
@@ -477,14 +448,14 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
skipped.add(prevStatement);
prevStatement = PsiTreeUtil.getPrevSiblingOfType(prevStatement, PsiStatement.class);
}
if (prevStatement != null) {
if (prevStatement != null && skippedEmptyStatements != null) {
skippedEmptyStatements.addAll(skipped);
}
return prevStatement;
}
@Nullable
private static PsiStatement getNearestEnclosingStatement(@NotNull PsiElement element) {
private static PsiStatement getNearestEnclosingStatement(@Nullable PsiElement element) {
return element instanceof PsiStatement ? (PsiStatement)element : PsiTreeUtil.getParentOfType(element, PsiStatement.class);
}
@@ -492,17 +463,17 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
@NotNull PsiReturnStatement returnStatement,
@NotNull PsiVariable variable) {
String name = variable.getName();
holder.registerProblem(returnStatement, InspectionsBundle.message("inspection.return.separated.from.computation.descriptor", name),
new VariableFix(name, variable instanceof PsiParameter));
PsiElement returnElement = returnStatement.getFirstChild();
holder.registerProblem(returnElement instanceof PsiKeyword ? returnElement : returnStatement,
InspectionsBundle.message("inspection.return.separated.from.computation.descriptor", name),
new VariableFix(name));
}
private static class VariableFix implements LocalQuickFix {
private String myName;
private boolean myIsParameter;
public VariableFix(String name, boolean isParameter) {
public VariableFix(String name) {
myName = name;
myIsParameter = isParameter;
}
@Nls
@@ -521,7 +492,7 @@ public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocal
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
PsiElement element = getNearestEnclosingStatement(descriptor.getPsiElement());
if (element instanceof PsiReturnStatement) {
doApply(((PsiReturnStatement)element));
}
@@ -0,0 +1,13 @@
// "Move 'return' to computation of the value of 'result'" "false"
class T {
int size;
int width;
int height;
public int hashCode() {
int result = size;
result = 31 * result + width;
result = 31 * result + height;
ret<caret>urn result;
}
}
@@ -11,7 +11,7 @@ class T {
}
}
}
<warning descr="Return separated from computation of value of 's'">return s;</warning>
<warning descr="Return separated from computation of value of 's'">return</warning> s;
}
private static BufferedReader open() throws FileNotFoundException {
return null;
@@ -9,7 +9,7 @@ class T {
break;
}
} while (true);
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
<warning descr="Return separated from computation of value of 'r'">return</warning> r;
}
boolean hasNext() {
@@ -7,6 +7,6 @@ class T {
break;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -0,0 +1,12 @@
class T {
int size;
int width;
int height;
public int hashCode() {
int result = size;
result = 31 * result + width;
result = 31 * result + height;
return result;
}
}
@@ -3,6 +3,6 @@ class T {
int n = 0;
if (b) n = 1;
else n = 2;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -3,6 +3,6 @@ class T {
int n = 0;
if (b) System.out.println("yes");
else n = 2;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -3,6 +3,6 @@ class T {
int n = 0;
if (b) n = 1;
else System.out.println("no");
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -7,6 +7,6 @@ class T {
if (b) break myLabel;
n = 2;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -8,6 +8,6 @@ class T {
break myLabel;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -6,6 +6,6 @@ class T {
n = i;
if (a[0] == 0) break myLabel;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -4,6 +4,6 @@ class T {
myLabel:
if (b) n = 1;
else break myLabel;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -4,6 +4,6 @@ class T {
{
n = 1;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -5,6 +5,6 @@ class T {
n = 1;
System.out.println();
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -6,6 +6,6 @@ class T {
n = 1;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -5,6 +5,6 @@ class T {
if (b) n = 1;
else n = 2;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -5,6 +5,6 @@ class T {
if (b) n = 1;
}
else n = 2;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -6,7 +6,7 @@ class T {
try (BufferedReader r = open()) {
s = r.readLine();
}
<warning descr="Return separated from computation of value of 's'">return s;</warning>
<warning descr="Return separated from computation of value of 's'">return</warning> s;
}
private static BufferedReader open() throws FileNotFoundException {
@@ -12,7 +12,7 @@ class T {
i = j + 1;
}
while (i >= 0);
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
<warning descr="Return separated from computation of value of 'r'">return</warning> r;
}
boolean hasNext() {
@@ -7,6 +7,6 @@ class T {
break;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -7,6 +7,6 @@ class T {
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
<warning descr="Return separated from computation of value of 'r'">return</warning> r;
}
}
@@ -2,6 +2,6 @@ class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
<warning descr="Return separated from computation of value of 'n'">return</warning> n;
}
}
@@ -8,7 +8,7 @@ class T {
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
<warning descr="Return separated from computation of value of 'r'">return</warning> r;
}
boolean hasNext() {
@@ -10,6 +10,6 @@ class T {
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
<warning descr="Return separated from computation of value of 'r'">return</warning> r;
}
}
@@ -1,48 +0,0 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.intermediaryVariable.ReturnSeparatedFromComputationInspection;
import org.jetbrains.annotations.NotNull;
/**
* @author Pavel.Dolgov
*/
public class ReturnSeparatedFromComputationFix2Test extends LightQuickFixTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new ReturnSeparatedFromComputationInspection()};
}
public void testAssert() {
doTest();
}
public void testBreakFromLoopInTryWithResources() {
doTest();
}
private void doTest() {
doSingleTest(getTestName(false) +".java");
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/returnSeparatedFromComputation";
}
}
@@ -128,6 +128,10 @@ public class ReturnSeparatedFromComputationTest extends LightCodeInsightFixtureT
doTest();
}
public void testHashCode() {
doTest();
}
private void doTest() {
myFixture.enableInspections(new ReturnSeparatedFromComputationInspection());