blob: 67546e064fd78f3530cd2067eb0a9e3d9778ae8d [file] [log] [blame]
/*
* Copyright 2003-2014 Dave Griffith, Bas Leijdekkers
*
* 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.siyeh.ig.psiutils;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ControlFlowUtils {
private ControlFlowUtils() {}
public static boolean statementMayCompleteNormally(@Nullable PsiStatement statement) {
if (statement == null) {
return true;
}
if (statement instanceof PsiBreakStatement || statement instanceof PsiContinueStatement ||
statement instanceof PsiReturnStatement || statement instanceof PsiThrowStatement) {
return false;
}
else if (statement instanceof PsiExpressionListStatement || statement instanceof PsiEmptyStatement ||
statement instanceof PsiAssertStatement || statement instanceof PsiDeclarationStatement ||
statement instanceof PsiSwitchLabelStatement || statement instanceof PsiForeachStatement) {
return true;
}
else if (statement instanceof PsiExpressionStatement) {
final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)statement;
final PsiExpression expression = expressionStatement.getExpression();
if (!(expression instanceof PsiMethodCallExpression)) {
return true;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) {
return true;
}
@NonNls final String methodName = method.getName();
if (!methodName.equals("exit")) {
return true;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return true;
}
final String className = aClass.getQualifiedName();
return !"java.lang.System".equals(className);
}
else if (statement instanceof PsiForStatement) {
return forStatementMayCompleteNormally((PsiForStatement)statement);
}
else if (statement instanceof PsiWhileStatement) {
return whileStatementMayCompleteNormally((PsiWhileStatement)statement);
}
else if (statement instanceof PsiDoWhileStatement) {
return doWhileStatementMayCompleteNormally((PsiDoWhileStatement)statement);
}
else if (statement instanceof PsiSynchronizedStatement) {
final PsiCodeBlock body = ((PsiSynchronizedStatement)statement).getBody();
return codeBlockMayCompleteNormally(body);
}
else if (statement instanceof PsiBlockStatement) {
final PsiCodeBlock codeBlock = ((PsiBlockStatement)statement).getCodeBlock();
return codeBlockMayCompleteNormally(codeBlock);
}
else if (statement instanceof PsiLabeledStatement) {
return labeledStatementMayCompleteNormally((PsiLabeledStatement)statement);
}
else if (statement instanceof PsiIfStatement) {
return ifStatementMayCompleteNormally((PsiIfStatement)statement);
}
else if (statement instanceof PsiTryStatement) {
return tryStatementMayCompleteNormally((PsiTryStatement)statement);
}
else if (statement instanceof PsiSwitchStatement) {
return switchStatementMayCompleteNormally((PsiSwitchStatement)statement);
}
else if (statement instanceof PsiTemplateStatement || statement instanceof PsiClassLevelDeclarationStatement) {
return true;
}
else {
assert false : "unknown statement type: " + statement.getClass();
return true;
}
}
private static boolean doWhileStatementMayCompleteNormally(@NotNull PsiDoWhileStatement loopStatement) {
final PsiExpression condition = loopStatement.getCondition();
final Object value = ExpressionUtils.computeConstantExpression(condition);
final PsiStatement body = loopStatement.getBody();
return statementMayCompleteNormally(body) && value != Boolean.TRUE
|| statementIsBreakTarget(loopStatement) || statementContainsContinueToAncestor(loopStatement);
}
private static boolean whileStatementMayCompleteNormally(@NotNull PsiWhileStatement loopStatement) {
final PsiExpression condition = loopStatement.getCondition();
final Object value = ExpressionUtils.computeConstantExpression(condition);
return value != Boolean.TRUE || statementIsBreakTarget(loopStatement) || statementContainsContinueToAncestor(loopStatement);
}
private static boolean forStatementMayCompleteNormally(@NotNull PsiForStatement loopStatement) {
if (statementIsBreakTarget(loopStatement)) {
return true;
}
if (statementContainsContinueToAncestor(loopStatement)) {
return true;
}
final PsiExpression condition = loopStatement.getCondition();
if (condition == null) {
return false;
}
final Object value = ExpressionUtils.computeConstantExpression(condition);
return Boolean.TRUE != value;
}
private static boolean switchStatementMayCompleteNormally(@NotNull PsiSwitchStatement switchStatement) {
if (statementIsBreakTarget(switchStatement)) {
return true;
}
final PsiCodeBlock body = switchStatement.getBody();
if (body == null) {
return true;
}
final PsiStatement[] statements = body.getStatements();
if (statements.length == 0) {
return true;
}
int numCases = 0;
boolean hasDefaultCase = false;
for (PsiStatement statement : statements) {
if (statement instanceof PsiSwitchLabelStatement) {
final PsiSwitchLabelStatement switchLabelStatement = (PsiSwitchLabelStatement)statement;
if (switchLabelStatement.isDefaultCase()) {
hasDefaultCase = true;
}
}
if (statement instanceof PsiBreakStatement) {
final PsiBreakStatement breakStatement = (PsiBreakStatement)statement;
if (breakStatement.getLabelIdentifier() == null) {
return true;
}
}
numCases++;
}
final boolean isEnum = isEnumSwitch(switchStatement);
if (!hasDefaultCase && !isEnum) {
return true;
}
if (!hasDefaultCase && isEnum) {
final PsiExpression expression = switchStatement.getExpression();
if (expression == null) {
return true;
}
final PsiClassType type = (PsiClassType)expression.getType();
if (type == null) {
return true;
}
final PsiClass aClass = type.resolve();
if (aClass == null) {
return true;
}
final PsiField[] fields = aClass.getFields();
int numEnums = 0;
for (final PsiField field : fields) {
final PsiType fieldType = field.getType();
if (fieldType.equals(type)) {
numEnums++;
}
}
if (numEnums != numCases) {
return true;
}
}
return statementMayCompleteNormally(statements[statements.length - 1]);
}
private static boolean isEnumSwitch(PsiSwitchStatement statement) {
final PsiExpression expression = statement.getExpression();
if (expression == null) {
return false;
}
final PsiType type = expression.getType();
if (type == null) {
return false;
}
if (!(type instanceof PsiClassType)) {
return false;
}
final PsiClass aClass = ((PsiClassType)type).resolve();
if (aClass == null) {
return false;
}
return aClass.isEnum();
}
private static boolean tryStatementMayCompleteNormally(@NotNull PsiTryStatement tryStatement) {
final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
if (finallyBlock != null) {
if (!codeBlockMayCompleteNormally(finallyBlock)) {
return false;
}
}
final PsiCodeBlock tryBlock = tryStatement.getTryBlock();
if (codeBlockMayCompleteNormally(tryBlock)) {
return true;
}
final PsiCodeBlock[] catchBlocks = tryStatement.getCatchBlocks();
for (final PsiCodeBlock catchBlock : catchBlocks) {
if (codeBlockMayCompleteNormally(catchBlock)) {
return true;
}
}
return false;
}
private static boolean ifStatementMayCompleteNormally(@NotNull PsiIfStatement ifStatement) {
final PsiExpression condition = ifStatement.getCondition();
final Object value = ExpressionUtils.computeConstantExpression(condition);
final PsiStatement thenBranch = ifStatement.getThenBranch();
final boolean thenCompletesNormally = statementMayCompleteNormally(thenBranch);
if (value == Boolean.TRUE) {
return thenCompletesNormally;
}
final PsiStatement elseBranch = ifStatement.getElseBranch();
final boolean elseCompletesNormally = statementMayCompleteNormally(elseBranch);
if (value == Boolean.FALSE) {
return elseCompletesNormally;
}
return thenCompletesNormally || elseCompletesNormally;
}
private static boolean labeledStatementMayCompleteNormally(@NotNull PsiLabeledStatement labeledStatement) {
final PsiStatement statement = labeledStatement.getStatement();
if (statement == null) {
return false;
}
return statementMayCompleteNormally(statement) || statementIsBreakTarget(statement);
}
public static boolean codeBlockMayCompleteNormally(@Nullable PsiCodeBlock block) {
if (block == null) {
return true;
}
final PsiStatement[] statements = block.getStatements();
for (final PsiStatement statement : statements) {
if (!statementMayCompleteNormally(statement)) {
return false;
}
}
return true;
}
private static boolean statementIsBreakTarget(@NotNull PsiStatement statement) {
final BreakFinder breakFinder = new BreakFinder(statement);
statement.accept(breakFinder);
return breakFinder.breakFound();
}
private static boolean statementContainsContinueToAncestor(@NotNull PsiStatement statement) {
PsiElement parent = statement.getParent();
while (parent instanceof PsiLabeledStatement) {
statement = (PsiStatement)parent;
parent = parent.getParent();
}
final ContinueToAncestorFinder continueToAncestorFinder = new ContinueToAncestorFinder(statement);
statement.accept(continueToAncestorFinder);
return continueToAncestorFinder.continueToAncestorFound();
}
public static boolean statementContainsReturn(@NotNull PsiStatement statement) {
final ReturnFinder returnFinder = new ReturnFinder();
statement.accept(returnFinder);
return returnFinder.returnFound();
}
public static boolean statementIsContinueTarget(@NotNull PsiStatement statement) {
final ContinueFinder continueFinder = new ContinueFinder(statement);
statement.accept(continueFinder);
return continueFinder.continueFound();
}
public static boolean statementContainsSystemExit(@NotNull PsiStatement statement) {
final SystemExitFinder systemExitFinder = new SystemExitFinder();
statement.accept(systemExitFinder);
return systemExitFinder.exitFound();
}
public static boolean elementContainsCallToMethod(PsiElement context, String containingClassName, PsiType returnType,
String methodName, PsiType... parameterTypes) {
final MethodCallFinder methodCallFinder = new MethodCallFinder(containingClassName, returnType, methodName, parameterTypes);
context.accept(methodCallFinder);
return methodCallFinder.containsCallToMethod();
}
public static boolean isInLoop(@NotNull PsiElement element) {
final PsiLoopStatement loopStatement = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class, true, PsiClass.class);
if (loopStatement == null) {
return false;
}
final PsiStatement body = loopStatement.getBody();
if (body == null) {
return false;
}
return PsiTreeUtil.isAncestor(body, element, true);
}
public static boolean isInFinallyBlock(@NotNull PsiElement element) {
PsiElement currentElement = element;
while (true) {
final PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(currentElement, PsiTryStatement.class, true, PsiClass.class, PsiLambdaExpression.class);
if (tryStatement == null) {
return false;
}
final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
if (finallyBlock != null) {
if (PsiTreeUtil.isAncestor(finallyBlock, currentElement, true)) {
final PsiMethod elementMethod = PsiTreeUtil.getParentOfType(currentElement, PsiMethod.class);
final PsiMethod finallyMethod = PsiTreeUtil.getParentOfType(finallyBlock, PsiMethod.class);
return elementMethod != null && elementMethod.equals(finallyMethod);
}
}
currentElement = tryStatement;
}
}
public static boolean isInCatchBlock(@NotNull PsiElement element) {
return PsiTreeUtil.getParentOfType(element, PsiCatchSection.class, true, PsiClass.class) != null;
}
public static boolean isInExitStatement(@NotNull PsiExpression expression) {
return isInReturnStatementArgument(expression) || isInThrowStatementArgument(expression);
}
private static boolean isInReturnStatementArgument(@NotNull PsiExpression expression) {
return PsiTreeUtil.getParentOfType(expression, PsiReturnStatement.class) != null;
}
private static boolean isInThrowStatementArgument(@NotNull PsiExpression expression) {
return PsiTreeUtil.getParentOfType(expression, PsiThrowStatement.class) != null;
}
@Nullable
public static PsiStatement stripBraces(@Nullable PsiStatement statement) {
if (statement instanceof PsiBlockStatement) {
final PsiBlockStatement block = (PsiBlockStatement)statement;
final PsiCodeBlock codeBlock = block.getCodeBlock();
final PsiStatement[] statements = codeBlock.getStatements();
if (statements.length == 1) {
return statements[0];
}
else {
return block;
}
}
else {
return statement;
}
}
public static boolean statementCompletesWithStatement(@NotNull PsiStatement containingStatement, @NotNull PsiStatement statement) {
PsiElement statementToCheck = statement;
while (true) {
if (statementToCheck.equals(containingStatement)) {
return true;
}
final PsiElement container = getContainingStatementOrBlock(statementToCheck);
if (container == null) {
return false;
}
if (container instanceof PsiCodeBlock) {
if (!statementIsLastInBlock((PsiCodeBlock)container, (PsiStatement)statementToCheck)) {
return false;
}
}
if (container instanceof PsiLoopStatement) {
return false;
}
statementToCheck = container;
}
}
public static boolean blockCompletesWithStatement(@NotNull PsiCodeBlock body, @NotNull PsiStatement statement) {
PsiElement statementToCheck = statement;
while (true) {
if (statementToCheck == null) {
return false;
}
final PsiElement container = getContainingStatementOrBlock(statementToCheck);
if (container == null) {
return false;
}
if (container instanceof PsiLoopStatement) {
return false;
}
if (container instanceof PsiCodeBlock) {
if (!statementIsLastInBlock((PsiCodeBlock)container, (PsiStatement)statementToCheck)) {
return false;
}
if (container.equals(body)) {
return true;
}
statementToCheck = PsiTreeUtil.getParentOfType(container, PsiStatement.class);
}
else {
statementToCheck = container;
}
}
}
@Nullable
private static PsiElement getContainingStatementOrBlock(@NotNull PsiElement statement) {
return PsiTreeUtil.getParentOfType(statement, PsiStatement.class, PsiCodeBlock.class);
}
private static boolean statementIsLastInBlock(@NotNull PsiCodeBlock block, @NotNull PsiStatement statement) {
final PsiStatement[] statements = block.getStatements();
for (int i = statements.length - 1; i >= 0; i--) {
final PsiStatement childStatement = statements[i];
if (statement.equals(childStatement)) {
return true;
}
if (!(statement instanceof PsiEmptyStatement)) {
return false;
}
}
return false;
}
public static boolean methodAlwaysThrowsException(@NotNull PsiMethod method) {
final PsiCodeBlock body = method.getBody();
if (body == null) {
return true;
}
final ReturnFinder returnFinder = new ReturnFinder();
body.accept(returnFinder);
if (returnFinder.returnFound()) {
return false;
}
return !codeBlockMayCompleteNormally(body);
}
public static boolean statementContainsNakedBreak(PsiStatement statement) {
if (statement == null) {
return false;
}
final NakedBreakFinder breakFinder = new NakedBreakFinder();
statement.accept(breakFinder);
return breakFinder.breakFound();
}
private static class NakedBreakFinder extends JavaRecursiveElementWalkingVisitor {
private boolean m_found = false;
public boolean breakFound() {
return m_found;
}
@Override
public void visitElement(PsiElement element) {
if (m_found) {
return;
}
super.visitElement(element);
}
@Override
public void visitReferenceExpression(
PsiReferenceExpression expression) {
}
@Override
public void visitBreakStatement(PsiBreakStatement statement) {
if (statement.getLabelIdentifier() != null) {
return;
}
m_found = true;
}
@Override
public void visitDoWhileStatement(PsiDoWhileStatement statement) {
// don't drill down
}
@Override
public void visitForStatement(PsiForStatement statement) {
// don't drill down
}
@Override
public void visitForeachStatement(PsiForeachStatement statement) {
// don't drill down
}
@Override
public void visitWhileStatement(PsiWhileStatement statement) {
// don't drill down
}
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
// don't drill down
}
}
private static class SystemExitFinder extends JavaRecursiveElementWalkingVisitor {
private boolean m_found = false;
public boolean exitFound() {
return m_found;
}
@Override
public void visitClass(@NotNull PsiClass aClass) {
// do nothing to keep from drilling into inner classes
}
@Override
public void visitMethodCallExpression(
@NotNull PsiMethodCallExpression expression) {
if (m_found) {
return;
}
super.visitMethodCallExpression(expression);
final PsiMethod method = expression.resolveMethod();
if (method == null) {
return;
}
@NonNls final String methodName = method.getName();
if (!methodName.equals("exit")) {
return;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return;
}
final String className = aClass.getQualifiedName();
if (!"java.lang.System".equals(className) && !"java.lang.Runtime".equals(className)) {
return;
}
m_found = true;
}
}
private static class ReturnFinder extends JavaRecursiveElementWalkingVisitor {
private boolean m_found = false;
public boolean returnFound() {
return m_found;
}
@Override
public void visitClass(@NotNull PsiClass psiClass) {
// do nothing, to keep drilling into inner classes
}
@Override
public void visitReturnStatement(@NotNull PsiReturnStatement returnStatement) {
if (m_found) {
return;
}
super.visitReturnStatement(returnStatement);
m_found = true;
}
}
private static class BreakFinder extends JavaRecursiveElementWalkingVisitor {
private boolean m_found = false;
private final PsiStatement m_target;
private BreakFinder(@NotNull PsiStatement target) {
m_target = target;
}
public boolean breakFound() {
return m_found;
}
@Override
public void visitBreakStatement(@NotNull PsiBreakStatement statement) {
if (m_found) {
return;
}
super.visitBreakStatement(statement);
final PsiStatement exitedStatement = statement.findExitedStatement();
if (exitedStatement == null) {
return;
}
if (PsiTreeUtil.isAncestor(exitedStatement, m_target, false)) {
m_found = true;
}
}
@Override
public void visitIfStatement(PsiIfStatement statement) {
if (m_found) {
return;
}
final PsiExpression condition = statement.getCondition();
final Object value = ExpressionUtils.computeConstantExpression(condition);
if (Boolean.FALSE != value) {
final PsiStatement thenBranch = statement.getThenBranch();
if (thenBranch != null) {
thenBranch.accept(this);
}
}
if (Boolean.TRUE != value) {
final PsiStatement elseBranch = statement.getElseBranch();
if (elseBranch != null) {
elseBranch.accept(this);
}
}
}
}
private static class ContinueFinder extends JavaRecursiveElementWalkingVisitor {
private boolean m_found = false;
private final PsiStatement m_target;
private ContinueFinder(@NotNull PsiStatement target) {
m_target = target;
}
public boolean continueFound() {
return m_found;
}
@Override
public void visitContinueStatement(@NotNull PsiContinueStatement statement) {
if (m_found) {
return;
}
super.visitContinueStatement(statement);
final PsiStatement continuedStatement = statement.findContinuedStatement();
if (continuedStatement == null) {
return;
}
if (PsiTreeUtil.isAncestor(continuedStatement, m_target, false)) {
m_found = true;
}
}
@Override
public void visitIfStatement(PsiIfStatement statement) {
if (m_found) {
return;
}
final PsiExpression condition = statement.getCondition();
final Object value = ExpressionUtils.computeConstantExpression(condition);
if (Boolean.FALSE != value) {
final PsiStatement thenBranch = statement.getThenBranch();
if (thenBranch != null) {
thenBranch.accept(this);
}
}
if (Boolean.TRUE != value) {
final PsiStatement elseBranch = statement.getElseBranch();
if (elseBranch != null) {
elseBranch.accept(this);
}
}
}
}
private static class MethodCallFinder extends JavaRecursiveElementWalkingVisitor {
private final String containingClassName;
private final PsiType returnType;
private final String methodName;
private final PsiType[] parameterTypeNames;
private boolean containsCallToMethod = false;
MethodCallFinder(String containingClassName, PsiType returnType, String methodName, PsiType... parameterTypeNames) {
this.containingClassName = containingClassName;
this.returnType = returnType;
this.methodName = methodName;
this.parameterTypeNames = parameterTypeNames;
}
@Override
public void visitElement(PsiElement element) {
if (containsCallToMethod) {
return;
}
super.visitElement(element);
}
@Override
public void visitMethodCallExpression(
PsiMethodCallExpression expression) {
if (containsCallToMethod) {
return;
}
super.visitMethodCallExpression(expression);
if (!MethodCallUtils.isCallToMethod(expression, containingClassName, returnType, methodName, parameterTypeNames)) {
return;
}
containsCallToMethod = true;
}
public boolean containsCallToMethod() {
return containsCallToMethod;
}
}
private static class ContinueToAncestorFinder extends JavaRecursiveElementWalkingVisitor {
private final PsiStatement statement;
private boolean found = false;
public ContinueToAncestorFinder(PsiStatement statement) {
this.statement = statement;
}
@Override
public void visitElement(PsiElement element) {
if (found) {
return;
}
super.visitElement(element);
}
@Override
public void visitContinueStatement(
PsiContinueStatement continueStatement) {
if (found) {
return;
}
super.visitContinueStatement(continueStatement);
final PsiIdentifier labelIdentifier = continueStatement.getLabelIdentifier();
if (labelIdentifier == null) {
return;
}
final PsiStatement continuedStatement = continueStatement.findContinuedStatement();
if (continuedStatement == null) {
return;
}
if (PsiTreeUtil.isAncestor(continuedStatement, statement, true)) {
found = true;
}
}
public boolean continueToAncestorFound() {
return found;
}
}
}