blob: 808131f1ac4c5961e8334266e7f5270d5dae8da2 [file] [log] [blame]
/*
* Copyright 2007-2008 Dave Griffith
*
* 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 org.jetbrains.plugins.groovy.codeInspection.bugs;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.utils.BoolUtils;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrCondition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
@SuppressWarnings({"OverlyComplexClass"})
class RecursionUtils {
private RecursionUtils() {
super();
}
public static boolean statementMayReturnBeforeRecursing(
@Nullable GrStatement statement, GrMethod method) {
if (statement == null) {
return true;
}
if (statement instanceof GrBreakStatement ||
statement instanceof GrContinueStatement ||
statement instanceof GrThrowStatement ||
statement instanceof GrExpression ||
statement instanceof GrAssertStatement ||
statement instanceof GrVariableDeclaration) {
return false;
} else if (statement instanceof GrReturnStatement) {
final GrReturnStatement returnStatement =
(GrReturnStatement) statement;
final GrExpression returnValue = returnStatement.getReturnValue();
if (returnValue != null) {
if (expressionDefinitelyRecurses(returnValue, method)) {
return false;
}
}
return true;
} else if (statement instanceof GrForStatement) {
return forStatementMayReturnBeforeRecursing(
(GrForStatement) statement, method);
} else if (statement instanceof GrWhileStatement) {
return whileStatementMayReturnBeforeRecursing(
(GrWhileStatement) statement, method);
} else if (statement instanceof GrSynchronizedStatement) {
final GrCodeBlock body = ((GrSynchronizedStatement) statement)
.getBody();
return codeBlockMayReturnBeforeRecursing(body, method, false);
} else if (statement instanceof GrBlockStatement) {
final GrBlockStatement blockStatement =
(GrBlockStatement) statement;
final GrCodeBlock codeBlock = blockStatement.getBlock();
return codeBlockMayReturnBeforeRecursing(codeBlock, method, false);
} else if (statement instanceof GrIfStatement) {
return ifStatementMayReturnBeforeRecursing(
(GrIfStatement) statement, method);
} else if (statement instanceof GrTryCatchStatement) {
return tryStatementMayReturnBeforeRecursing(
(GrTryCatchStatement) statement, method);
} else if (statement instanceof GrSwitchStatement) {
return switchStatementMayReturnBeforeRecursing(
(GrSwitchStatement) statement, method);
} else {
// unknown statement type
return true;
}
}
private static boolean whileStatementMayReturnBeforeRecursing(
GrWhileStatement loopStatement, GrMethod method) {
final GrCondition condition = loopStatement.getCondition();
if (!(condition instanceof GrExpression)) {
return false;
}
if (expressionDefinitelyRecurses((GrExpression) condition, method)) {
return false;
}
final GrStatement body = loopStatement.getBody();
return statementMayReturnBeforeRecursing(body, method);
}
private static boolean forStatementMayReturnBeforeRecursing(
GrForStatement loopStatement, GrMethod method) {
final GrForClause forClause = loopStatement.getClause();
if (forClause != null) {
final GrVariable var = forClause.getDeclaredVariable();
if (var != null) {
final GrExpression initializer = var.getInitializerGroovy();
if (expressionDefinitelyRecurses(initializer, method)) {
return false;
}
}
}
final GrStatement body = loopStatement.getBody();
return statementMayReturnBeforeRecursing(body, method);
}
private static boolean switchStatementMayReturnBeforeRecursing(
GrSwitchStatement switchStatement, GrMethod method) {
final GrCaseSection[] caseSections = switchStatement.getCaseSections();
for (GrCaseSection caseSection : caseSections) {
final GrStatement[] statements = caseSection.getStatements();
for (final GrStatement statement : statements) {
if (statementMayReturnBeforeRecursing(statement, method)) {
return true;
}
}
}
return false;
}
private static boolean tryStatementMayReturnBeforeRecursing(
GrTryCatchStatement tryStatement, GrMethod method) {
final GrFinallyClause finallyBlock = tryStatement.getFinallyClause();
if (finallyBlock != null) {
final GrOpenBlock body = finallyBlock.getBody();
if (codeBlockMayReturnBeforeRecursing(body, method,
false)) {
return true;
}
if (codeBlockDefinitelyRecurses(body, method)) {
return false;
}
}
final GrCodeBlock tryBlock = tryStatement.getTryBlock();
if (codeBlockMayReturnBeforeRecursing(tryBlock, method, false)) {
return true;
}
final GrCatchClause[] catchBlocks = tryStatement.getCatchClauses();
for (final GrCatchClause catchBlock : catchBlocks) {
if (codeBlockMayReturnBeforeRecursing(catchBlock.getBody(), method, false)) {
return true;
}
}
return false;
}
private static boolean ifStatementMayReturnBeforeRecursing(
GrIfStatement ifStatement, GrMethod method) {
GrExpression condition = ifStatement.getCondition();
if (condition == null) return false;
if (expressionDefinitelyRecurses(condition, method)) {
return false;
}
final GrStatement thenBranch = ifStatement.getThenBranch();
if (statementMayReturnBeforeRecursing(thenBranch, method)) {
return true;
}
final GrStatement elseBranch = ifStatement.getElseBranch();
return elseBranch != null &&
statementMayReturnBeforeRecursing(elseBranch, method);
}
private static boolean codeBlockMayReturnBeforeRecursing(
@Nullable GrCodeBlock block, GrMethod method, boolean endsInImplicitReturn) {
if (block == null) {
return true;
}
final GrStatement[] statements = block.getStatements();
for (final GrStatement statement : statements) {
if (statementMayReturnBeforeRecursing(statement, method)) {
return true;
}
if (statementDefinitelyRecurses(statement, method)) {
return false;
}
}
return endsInImplicitReturn;
}
public static boolean methodMayRecurse(@NotNull GrMethod method) {
final RecursionVisitor recursionVisitor = new RecursionVisitor(method);
method.accept(recursionVisitor);
return recursionVisitor.isRecursive();
}
private static boolean expressionDefinitelyRecurses(@Nullable GrExpression exp,
GrMethod method) {
if (exp == null) {
return false;
}
if (exp instanceof GrLiteral) {
return false;
}
if (exp instanceof GrMethodCallExpression) {
return methodCallExpressionDefinitelyRecurses(
(GrMethodCallExpression) exp, method);
}
if (exp instanceof GrNewExpression) {
return callExpressionDefinitelyRecurses(
(GrNewExpression) exp, method);
}
if (exp instanceof GrAssignmentExpression) {
return assignmentExpressionDefinitelyRecurses(
(GrAssignmentExpression) exp, method);
}
if (exp instanceof GrArrayDeclaration) {
return arrayInitializerExpressionDefinitelyRecurses(
(GrArrayDeclaration) exp, method);
}
if (exp instanceof GrTypeCastExpression) {
return typeCastExpressionDefinitelyRecurses(
(GrTypeCastExpression) exp, method);
}
if (exp instanceof GrIndexProperty) {
return arrayAccessExpressionDefinitelyRecurses((GrIndexProperty) exp, method);
}
if (exp instanceof GrUnaryExpression) {
return unaryExpressionDefinitelyRecurses(
(GrUnaryExpression) exp, method);
}
if (exp instanceof GrBinaryExpression) {
return binaryExpressionDefinitelyRecurses(
(GrBinaryExpression) exp, method);
}
if (exp instanceof GrInstanceOfExpression) {
return instanceOfExpressionDefinitelyRecurses(
(GrInstanceOfExpression) exp, method);
}
if (exp instanceof GrElvisExpression) {
return elvisExpressionDefinitelyRecurses(
(GrElvisExpression) exp, method);
}
if (exp instanceof GrConditionalExpression) {
return conditionalExpressionDefinitelyRecurses(
(GrConditionalExpression) exp, method);
}
if (exp instanceof GrParenthesizedExpression) {
return parenthesizedExpressionDefinitelyRecurses(
(GrParenthesizedExpression) exp, method);
}
if (exp instanceof GrReferenceExpression) {
return referenceExpressionDefinitelyRecurses(
(GrReferenceExpression) exp, method);
}
return false;
}
private static boolean conditionalExpressionDefinitelyRecurses(
GrConditionalExpression expression, GrMethod method) {
final GrExpression condExpression = expression.getCondition();
if (expressionDefinitelyRecurses(condExpression, method)) {
return true;
}
final GrExpression thenExpression = expression.getThenBranch();
final GrExpression elseExpression = expression.getElseBranch();
return expressionDefinitelyRecurses(thenExpression, method)
&& expressionDefinitelyRecurses(elseExpression, method);
}
private static boolean elvisExpressionDefinitelyRecurses(
GrElvisExpression expression, GrMethod method) {
final GrExpression condExpression = expression.getCondition();
return expressionDefinitelyRecurses(condExpression, method);
}
private static boolean binaryExpressionDefinitelyRecurses(
GrBinaryExpression expression, GrMethod method) {
final GrExpression lhs = expression.getLeftOperand();
if (expressionDefinitelyRecurses(lhs, method)) {
return true;
}
final IElementType tokenType = expression.getOperationTokenType();
if (GroovyTokenTypes.mLAND.equals(tokenType) ||
GroovyTokenTypes.mLOR.equals(tokenType)) {
return false;
}
final GrExpression rhs = expression.getRightOperand();
return expressionDefinitelyRecurses(rhs, method);
}
private static boolean arrayAccessExpressionDefinitelyRecurses(
GrIndexProperty expression, GrMethod method) {
final GrExpression arrayExp = expression.getInvokedExpression();
return expressionDefinitelyRecurses(arrayExp, method);
}
private static boolean arrayInitializerExpressionDefinitelyRecurses(
GrArrayDeclaration expression, GrMethod method) {
final GrExpression[] initializers = expression.getBoundExpressions();
for (final GrExpression initializer : initializers) {
if (expressionDefinitelyRecurses(initializer, method)) {
return true;
}
}
return false;
}
private static boolean unaryExpressionDefinitelyRecurses(
GrUnaryExpression expression, GrMethod method) {
final GrExpression operand = expression.getOperand();
return expressionDefinitelyRecurses(operand, method);
}
private static boolean instanceOfExpressionDefinitelyRecurses(
GrInstanceOfExpression expression, GrMethod method) {
final GrExpression operand = expression.getOperand();
return expressionDefinitelyRecurses(operand, method);
}
private static boolean parenthesizedExpressionDefinitelyRecurses(
GrParenthesizedExpression expression, GrMethod method) {
final GrExpression innerExpression = expression.getOperand();
return expressionDefinitelyRecurses(innerExpression, method);
}
private static boolean referenceExpressionDefinitelyRecurses(
GrReferenceExpression expression, GrMethod method) {
final GrExpression qualifierExpression =
expression.getQualifierExpression();
if (qualifierExpression != null) {
return expressionDefinitelyRecurses(qualifierExpression, method);
}
return false;
}
private static boolean typeCastExpressionDefinitelyRecurses(
GrTypeCastExpression expression, GrMethod method) {
final GrExpression operand = expression.getOperand();
return expressionDefinitelyRecurses(operand, method);
}
private static boolean assignmentExpressionDefinitelyRecurses(
GrAssignmentExpression assignmentExpression, GrMethod method) {
final GrExpression rhs = assignmentExpression.getRValue();
final GrExpression lhs = assignmentExpression.getLValue();
return expressionDefinitelyRecurses(rhs, method) ||
expressionDefinitelyRecurses(lhs, method);
}
private static boolean callExpressionDefinitelyRecurses(GrCallExpression exp,
GrMethod method) {
final GrArgumentList argumentList = exp.getArgumentList();
if (argumentList != null) {
final GrExpression[] args = argumentList.getExpressionArguments();
for (final GrExpression arg : args) {
if (expressionDefinitelyRecurses(arg, method)) {
return true;
}
}
final GrNamedArgument[] namedArgs = argumentList.getNamedArguments();
for (final GrNamedArgument arg : namedArgs) {
if (expressionDefinitelyRecurses(arg.getExpression(), method)) {
return true;
}
}
}
return false;
}
private static boolean methodCallExpressionDefinitelyRecurses(
GrMethodCallExpression exp, GrMethod method) {
final GrExpression invoked = exp.getInvokedExpression();
if (invoked instanceof GrReferenceExpression) {
final GrReferenceExpression methodExpression = (GrReferenceExpression) invoked;
final PsiMethod referencedMethod = exp.resolveMethod();
if (referencedMethod == null) {
return false;
}
final GrExpression qualifier =
methodExpression.getQualifierExpression();
if (referencedMethod.equals(method)) {
if (method.hasModifierProperty(PsiModifier.STATIC) ||
method.hasModifierProperty(PsiModifier.PRIVATE)) {
return true;
}
if (qualifier == null || qualifier instanceof GrReferenceExpression && PsiUtil.isThisReference(qualifier)) {
return true;
}
}
if (expressionDefinitelyRecurses(qualifier, method)) {
return true;
}
}
return callExpressionDefinitelyRecurses(exp, method);
}
private static boolean statementDefinitelyRecurses(@Nullable GrStatement statement,
GrMethod method) {
if (statement == null) {
return false;
}
if (statement instanceof GrBreakStatement ||
statement instanceof GrContinueStatement ||
statement instanceof GrThrowStatement ||
statement instanceof GrAssertStatement) {
return false;
} else if (statement instanceof GrExpression) {
final GrExpression expression =
(GrExpression) statement;
return expressionDefinitelyRecurses(expression, method);
} else if (statement instanceof GrVariableDeclaration) {
final GrVariableDeclaration declaration =
(GrVariableDeclaration) statement;
final GrVariable[] declaredElements =
declaration.getVariables();
for (final GrVariable variable : declaredElements) {
final GrExpression initializer = (GrExpression) variable.getInitializer();
if (expressionDefinitelyRecurses(initializer, method)) {
return true;
}
}
return false;
} else if (statement instanceof GrReturnStatement) {
final GrReturnStatement returnStatement =
(GrReturnStatement) statement;
final GrExpression returnValue = returnStatement.getReturnValue();
if (returnValue != null) {
if (expressionDefinitelyRecurses(returnValue, method)) {
return true;
}
}
return false;
} else if (statement instanceof GrForStatement) {
return forStatementDefinitelyRecurses((GrForStatement)
statement, method);
} else if (statement instanceof GrWhileStatement) {
return whileStatementDefinitelyRecurses(
(GrWhileStatement) statement, method);
} else if (statement instanceof GrSynchronizedStatement) {
final GrCodeBlock body = ((GrSynchronizedStatement) statement)
.getBody();
return codeBlockDefinitelyRecurses(body, method);
} else if (statement instanceof GrBlockStatement) {
final GrCodeBlock codeBlock = ((GrBlockStatement) statement).getBlock();
return codeBlockDefinitelyRecurses(codeBlock, method);
} else if (statement instanceof GrIfStatement) {
return ifStatementDefinitelyRecurses(
(GrIfStatement) statement, method);
} else if (statement instanceof GrTryCatchStatement) {
return tryStatementDefinitelyRecurses(
(GrTryCatchStatement) statement, method);
} else if (statement instanceof GrSwitchStatement) {
return switchStatementDefinitelyRecurses(
(GrSwitchStatement) statement, method);
} else {
// unknown statement type
return false;
}
}
private static boolean switchStatementDefinitelyRecurses(GrSwitchStatement switchStatement, GrMethod method) {
final GrExpression switchExpression = switchStatement.getCondition();
return expressionDefinitelyRecurses(switchExpression, method);
}
private static boolean tryStatementDefinitelyRecurses(
GrTryCatchStatement tryStatement, GrMethod method) {
final GrCodeBlock tryBlock = tryStatement.getTryBlock();
if (codeBlockDefinitelyRecurses(tryBlock, method)) {
return true;
}
final GrFinallyClause finallyBlock = tryStatement.getFinallyClause();
if (finallyBlock == null) {
return false;
}
return codeBlockDefinitelyRecurses(finallyBlock.getBody(), method);
}
private static boolean codeBlockDefinitelyRecurses(GrCodeBlock block,
GrMethod method) {
if (block == null) {
return false;
}
final GrStatement[] statements = block.getStatements();
for (final GrStatement statement : statements) {
if (statementDefinitelyRecurses(statement, method)) {
return true;
}
}
return false;
}
private static boolean ifStatementDefinitelyRecurses(
GrIfStatement ifStatement, GrMethod method) {
final GrExpression condition = ifStatement.getCondition();
if (condition == null) return false;
if (expressionDefinitelyRecurses(condition, method)) {
return true;
}
final GrStatement thenBranch = ifStatement.getThenBranch();
final GrStatement elseBranch = ifStatement.getElseBranch();
if (thenBranch == null || elseBranch == null) {
return false;
}
return statementDefinitelyRecurses(thenBranch, method) &&
statementDefinitelyRecurses(elseBranch, method);
}
private static boolean forStatementDefinitelyRecurses(GrForStatement forStatement, GrMethod method) {
final GrForClause clause = forStatement.getClause();
if (clause == null) return false;
final GrVariable var = clause.getDeclaredVariable();
if (var != null) {
final GrExpression initializer = var.getInitializerGroovy();
if (expressionDefinitelyRecurses(initializer, method)) {
return true;
}
}
return false;
}
private static boolean whileStatementDefinitelyRecurses(
GrWhileStatement whileStatement, GrMethod method) {
final GrExpression condition = (GrExpression) whileStatement.getCondition();
if (expressionDefinitelyRecurses(condition, method)) {
return true;
}
if (BoolUtils.isTrue(condition)) {
final GrStatement body = whileStatement.getBody();
return statementDefinitelyRecurses(body, method);
}
return false;
}
public static boolean methodDefinitelyRecurses(
@NotNull GrMethod method) {
final GrCodeBlock body = method.getBlock();
if (body == null) {
return false;
}
return !codeBlockMayReturnBeforeRecursing(body, method, true);
}
}