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