blob: 2abedb0835f900c4559289b3330707a38d88233c [file] [log] [blame]
/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.engine.internal.hint;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.BooleanLiteral;
import com.google.dart.engine.ast.BreakStatement;
import com.google.dart.engine.ast.CatchClause;
import com.google.dart.engine.ast.ConditionalExpression;
import com.google.dart.engine.ast.ContinueStatement;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.ast.IfStatement;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.PropertyAccess;
import com.google.dart.engine.ast.ReturnStatement;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchDefault;
import com.google.dart.engine.ast.SwitchMember;
import com.google.dart.engine.ast.TryStatement;
import com.google.dart.engine.ast.TypeName;
import com.google.dart.engine.ast.WhileStatement;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.PropertyInducingElement;
import com.google.dart.engine.error.HintCode;
import com.google.dart.engine.internal.constant.EvaluationResultImpl;
import com.google.dart.engine.internal.error.ErrorReporter;
import com.google.dart.engine.internal.object.BoolState;
import com.google.dart.engine.internal.object.DartObjectImpl;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.type.Type;
import java.util.ArrayList;
/**
* Instances of the class {@code DeadCodeVerifier} traverse an AST structure looking for cases of
* {@link HintCode#DEAD_CODE}.
*
* @coverage dart.engine.resolver
*/
public class DeadCodeVerifier extends RecursiveAstVisitor<Void> {
/**
* The error reporter by which errors will be reported.
*/
private ErrorReporter errorReporter;
/**
* Create a new instance of the {@link DeadCodeVerifier}.
*
* @param errorReporter the error reporter
*/
public DeadCodeVerifier(ErrorReporter errorReporter) {
this.errorReporter = errorReporter;
}
@Override
public Void visitBinaryExpression(BinaryExpression node) {
Token operator = node.getOperator();
boolean isAmpAmp = operator.getType() == TokenType.AMPERSAND_AMPERSAND;
boolean isBarBar = operator.getType() == TokenType.BAR_BAR;
if (isAmpAmp || isBarBar) {
Expression lhsCondition = node.getLeftOperand();
if (!isDebugConstant(lhsCondition)) {
EvaluationResultImpl lhsResult = getConstantBooleanValue(lhsCondition);
if (lhsResult != null) {
if (lhsResult.getValue().isTrue() && isBarBar) {
// report error on else block: true || !e!
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.getRightOperand());
// only visit the LHS:
safelyVisit(lhsCondition);
return null;
} else if (lhsResult.getValue().isFalse() && isAmpAmp) {
// report error on if block: false && !e!
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.getRightOperand());
// only visit the LHS:
safelyVisit(lhsCondition);
return null;
}
}
}
// How do we want to handle the RHS? It isn't dead code, but "pointless" or "obscure"...
// Expression rhsCondition = node.getRightOperand();
// ValidResult rhsResult = getConstantBooleanValue(rhsCondition);
// if (rhsResult != null) {
// if (rhsResult == ValidResult.RESULT_TRUE && isBarBar) {
// // report error on else block: !e! || true
// errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand());
// // only visit the RHS:
// safelyVisit(rhsCondition);
// return null;
// } else if (rhsResult == ValidResult.RESULT_FALSE && isAmpAmp) {
// // report error on if block: !e! && false
// errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand());
// // only visit the RHS:
// safelyVisit(rhsCondition);
// return null;
// }
// }
}
return super.visitBinaryExpression(node);
}
/**
* For each {@link Block}, this method reports and error on all statements between the end of the
* block and the first return statement (assuming there it is not at the end of the block.)
*
* @param node the block to evaluate
*/
@Override
public Void visitBlock(Block node) {
NodeList<Statement> statements = node.getStatements();
checkForDeadStatementsInNodeList(statements);
return null;
}
@Override
public Void visitConditionalExpression(ConditionalExpression node) {
Expression conditionExpression = node.getCondition();
safelyVisit(conditionExpression);
if (!isDebugConstant(conditionExpression)) {
EvaluationResultImpl result = getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.getValue().isTrue()) {
// report error on else block: true ? 1 : !2!
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.getElseExpression());
safelyVisit(node.getThenExpression());
return null;
} else {
// report error on if block: false ? !1! : 2
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.getThenExpression());
safelyVisit(node.getElseExpression());
return null;
}
}
}
return super.visitConditionalExpression(node);
}
@Override
public Void visitIfStatement(IfStatement node) {
Expression conditionExpression = node.getCondition();
safelyVisit(conditionExpression);
if (!isDebugConstant(conditionExpression)) {
EvaluationResultImpl result = getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.getValue().isTrue()) {
// report error on else block: if(true) {} else {!}
Statement elseStatement = node.getElseStatement();
if (elseStatement != null) {
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, elseStatement);
safelyVisit(node.getThenStatement());
return null;
}
} else {
// report error on if block: if (false) {!} else {}
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.getThenStatement());
safelyVisit(node.getElseStatement());
return null;
}
}
}
return super.visitIfStatement(node);
}
// Do we want to report "pointless" or "obscure" code such as do {} !while (false);!
//@Override
//public Void visitDoStatement(DoStatement node) {
// Expression conditionExpression = node.getCondition();
// ValidResult result = getConstantBooleanValue(conditionExpression);
// if (result != null) {
// if (result == ValidResult.RESULT_FALSE) {
// // report error on if block: do {} !while (false);!
// int whileOffset = node.getWhileKeyword().getOffset();
// int semiColonOffset = node.getSemicolon().getOffset() + 1;
// int length = semiColonOffset - whileOffset;
// errorReporter.reportError(HintCode.DEAD_CODE, whileOffset, length);
// }
// }
// return super.visitDoStatement(node);
//}
@Override
public Void visitSwitchCase(SwitchCase node) {
checkForDeadStatementsInNodeList(node.getStatements());
return super.visitSwitchCase(node);
}
@Override
public Void visitSwitchDefault(SwitchDefault node) {
checkForDeadStatementsInNodeList(node.getStatements());
return super.visitSwitchDefault(node);
}
@Override
public Void visitTryStatement(TryStatement node) {
safelyVisit(node.getBody());
safelyVisit(node.getFinallyBlock());
NodeList<CatchClause> catchClauses = node.getCatchClauses();
int numOfCatchClauses = catchClauses.size();
ArrayList<Type> visitedTypes = new ArrayList<Type>(numOfCatchClauses);
for (int i = 0; i < numOfCatchClauses; i++) {
CatchClause catchClause = catchClauses.get(i);
if (catchClause.getOnKeyword() != null) {
// on-catch clause found, verify that the exception type is not a subtype of a previous
// on-catch exception type
TypeName typeName = catchClause.getExceptionType();
if (typeName != null && typeName.getType() != null) {
Type currentType = typeName.getType();
if (currentType.isObject()) {
// Found catch clause clause that has Object as an exception type, this is equivalent to
// having a catch clause that doesn't have an exception type, visit the block, but
// generate an error on any following catch clauses (and don't visit them).
safelyVisit(catchClause);
if (i + 1 != numOfCatchClauses) {
// this catch clause is not the last in the try statement
CatchClause nextCatchClause = catchClauses.get(i + 1);
CatchClause lastCatchClause = catchClauses.get(numOfCatchClauses - 1);
int offset = nextCatchClause.getOffset();
int length = lastCatchClause.getEnd() - offset;
errorReporter.reportErrorForOffset(
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
offset,
length);
return null;
}
}
for (Type type : visitedTypes) {
if (currentType.isSubtypeOf(type)) {
CatchClause lastCatchClause = catchClauses.get(numOfCatchClauses - 1);
int offset = catchClause.getOffset();
int length = lastCatchClause.getEnd() - offset;
errorReporter.reportErrorForOffset(
HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
offset,
length,
currentType.getDisplayName(),
type.getDisplayName());
return null;
}
}
visitedTypes.add(currentType);
}
safelyVisit(catchClause);
} else {
// Found catch clause clause that doesn't have an exception type, visit the block, but
// generate an error on any following catch clauses (and don't visit them).
safelyVisit(catchClause);
if (i + 1 != numOfCatchClauses) {
// this catch clause is not the last in the try statement
CatchClause nextCatchClause = catchClauses.get(i + 1);
CatchClause lastCatchClause = catchClauses.get(numOfCatchClauses - 1);
int offset = nextCatchClause.getOffset();
int length = lastCatchClause.getEnd() - offset;
errorReporter.reportErrorForOffset(
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
offset,
length);
return null;
}
}
}
return null;
}
@Override
public Void visitWhileStatement(WhileStatement node) {
Expression conditionExpression = node.getCondition();
safelyVisit(conditionExpression);
if (!isDebugConstant(conditionExpression)) {
EvaluationResultImpl result = getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.getValue().isFalse()) {
// report error on if block: while (false) {!}
errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.getBody());
return null;
}
}
}
safelyVisit(node.getBody());
return null;
}
/**
* Given some {@link NodeList} of {@link Statement}s, from either a {@link Block} or
* {@link SwitchMember}, this loops through the list in reverse order searching for statements
* after a return, unlabeled break or unlabeled continue statement to mark them as dead code.
*
* @param statements some ordered list of statements in a {@link Block} or {@link SwitchMember}
*/
private void checkForDeadStatementsInNodeList(NodeList<Statement> statements) {
int size = statements.size();
for (int i = 0; i < size; i++) {
Statement currentStatement = statements.get(i);
safelyVisit(currentStatement);
boolean returnOrBreakingStatement = currentStatement instanceof ReturnStatement
|| (currentStatement instanceof BreakStatement && ((BreakStatement) currentStatement).getLabel() == null)
|| (currentStatement instanceof ContinueStatement && ((ContinueStatement) currentStatement).getLabel() == null);
if (returnOrBreakingStatement && i != size - 1) {
Statement nextStatement = statements.get(i + 1);
Statement lastStatement = statements.get(size - 1);
int offset = nextStatement.getOffset();
int length = lastStatement.getEnd() - offset;
errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length);
return;
}
}
}
/**
* Given some {@link Expression}, this method returns {@link ValidResult#RESULT_TRUE} if it is
* {@code true}, {@link ValidResult#RESULT_FALSE} if it is {@code false}, or {@code null} if the
* expression is not a constant boolean value.
*
* @param expression the expression to evaluate
* @return {@link ValidResult#RESULT_TRUE} if it is {@code true}, {@link ValidResult#RESULT_FALSE}
* if it is {@code false}, or {@code null} if the expression is not a constant boolean
* value
*/
private EvaluationResultImpl getConstantBooleanValue(Expression expression) {
if (expression instanceof BooleanLiteral) {
if (((BooleanLiteral) expression).getValue()) {
return new EvaluationResultImpl(new DartObjectImpl(null, BoolState.from(true)));
} else {
return new EvaluationResultImpl(new DartObjectImpl(null, BoolState.from(false)));
}
}
// Don't consider situations where we could evaluate to a constant boolean expression with the
// ConstantVisitor
// else {
// EvaluationResultImpl result = expression.accept(new ConstantVisitor());
// if (result == ValidResult.RESULT_TRUE) {
// return ValidResult.RESULT_TRUE;
// } else if (result == ValidResult.RESULT_FALSE) {
// return ValidResult.RESULT_FALSE;
// }
// return null;
// }
return null;
}
/**
* Return {@code true} if and only if the passed expression is resolved to a constant variable.
*
* @param expression some conditional expression
* @return {@code true} if and only if the passed expression is resolved to a constant variable
*/
private boolean isDebugConstant(Expression expression) {
Element element = null;
if (expression instanceof Identifier) {
Identifier identifier = (Identifier) expression;
element = identifier.getStaticElement();
} else if (expression instanceof PropertyAccess) {
PropertyAccess propertyAccess = (PropertyAccess) expression;
element = propertyAccess.getPropertyName().getStaticElement();
}
if (element instanceof PropertyAccessorElement) {
PropertyAccessorElement pae = (PropertyAccessorElement) element;
PropertyInducingElement variable = pae.getVariable();
return variable != null && variable.isConst();
}
return false;
}
/**
* If the given node is not {@code null}, visit this instance of the dead code verifier.
*
* @param node the node to be visited
*/
private void safelyVisit(AstNode node) {
if (node != null) {
node.accept(this);
}
}
}