Package com.google.dart.engine.internal.hint

Source Code of com.google.dart.engine.internal.hint.DeadCodeVerifier

/*
* 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.ValidResult;
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)) {
        ValidResult lhsResult = getConstantBooleanValue(lhsCondition);
        if (lhsResult != null) {
          if (lhsResult.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.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)) {
      ValidResult result = getConstantBooleanValue(conditionExpression);
      if (result != null) {
        if (result.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)) {
      ValidResult result = getConstantBooleanValue(conditionExpression);
      if (result != null) {
        if (result.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)) {
      ValidResult result = getConstantBooleanValue(conditionExpression);
      if (result != null) {
        if (result.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 ValidResult getConstantBooleanValue(Expression expression) {
    if (expression instanceof BooleanLiteral) {
      if (((BooleanLiteral) expression).getValue()) {
        return new ValidResult(new DartObjectImpl(null, BoolState.from(true)));
      } else {
        return new ValidResult(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);
    }
  }

}
TOP

Related Classes of com.google.dart.engine.internal.hint.DeadCodeVerifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.