| /* |
| * 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); |
| } |
| } |