content: /*
   * Copyright 2016 HuntBugs contributors
   * 
   * 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 one.util.huntbugs.detect;
  
  import java.util.ArrayList;
  import java.util.BitSet;
  import java.util.List;
  import java.util.stream.Collectors;
  
  import com.strobel.assembler.metadata.MethodDefinition;
  import com.strobel.assembler.metadata.TypeDefinition;
  import com.strobel.decompiler.ast.AstCode;
  import com.strobel.decompiler.ast.CaseBlock;
  import com.strobel.decompiler.ast.Condition;
  import com.strobel.decompiler.ast.Expression;
  import com.strobel.decompiler.ast.Node;
  import com.strobel.decompiler.ast.Switch;
  
  import one.util.huntbugs.registry.MethodContext;
  import one.util.huntbugs.registry.anno.AstNodes;
  import one.util.huntbugs.registry.anno.AstVisitor;
  import one.util.huntbugs.registry.anno.MethodVisitor;
  import one.util.huntbugs.registry.anno.WarningDefinition;
  import one.util.huntbugs.util.Equi;
  import one.util.huntbugs.util.NodeChain;
  import one.util.huntbugs.util.Nodes;
  import one.util.huntbugs.warning.Role.LocationRole;
  import one.util.huntbugs.warning.WarningAnnotation;
  
  /**
   * @author Tagir Valeev
   */
  @WarningDefinition(category = "RedundantCode", name = "SameBranchesIf", maxScore = 70)
  @WarningDefinition(category = "RedundantCode", name = "SameBranchesTernary", maxScore = 70)
  @WarningDefinition(category = "RedundantCode", name = "SameBranchesSwitch", maxScore = 70)
  @WarningDefinition(category = "RedundantCode", name = "SameBranchesSwitchDefault", maxScore = 70)
  @WarningDefinition(category = "RedundantCode", name = "EmptyBranch", maxScore = 25)
  public class SameBranches {
      private static final LocationRole SAME_BRANCH = LocationRole.forName("SAME_BRANCH");
      private static final LocationRole DEFAULT_BRANCH = LocationRole.forName("DEFAULT_BRANCH");
      
      @MethodVisitor
      public boolean check(MethodDefinition md, TypeDefinition td) {
          // Skip some autogenerated methods
          if(md.getName().equals("yyparse") && md.getReturnType().getInternalName().equals("java/lang/Object"))
              return false;
          if(md.getName().startsWith("jj") && td.getName().endsWith("TokenManager"))
              return false;
          if(md.getName().equals("getNextToken") && md.getSignature().equals("()I"))
              return false;
          return true;
      }
      
      @AstVisitor
      public void visit(Node node, NodeChain nc, MethodContext mc) {
          if (node instanceof Condition) {
              Condition cond = (Condition) node;
              if (Equi.equiBlocks(cond.getTrueBlock(), cond.getFalseBlock())) {
                  if (cond.getTrueBlock() == null || cond.getTrueBlock().getBody().isEmpty()) {
                      mc.report("EmptyBranch", 0, cond.getCondition());
  
                  } else {
                      // Work-around procyon bug in string switch reconstruction
                      if(cond.getTrueBlock().getBody().size() == 1 && Nodes.isOp(cond.getTrueBlock().getBody().get(0), AstCode.LoopOrSwitchBreak)
                              && nc.getNode() instanceof CaseBlock)
                          return;
                      mc.report("SameBranchesIf", computePriority(cond.getTrueBlock(), 2), cond.getTrueBlock(),
                          SAME_BRANCH.create(mc, cond.getFalseBlock()));
                  }
              }
          }
          if (node instanceof Switch) {
              Switch sw = (Switch) node;
              List<CaseBlock> blocks = sw.getCaseBlocks().stream().filter(
                  cb -> nonFallThrough(cb.getBody()) && !Nodes.isEmptyOrBreak(cb)).collect(Collectors.toList());
              BitSet marked = new BitSet();
              boolean hasDefault = false;
              List<WarningAnnotation<?>> eqLocations = new ArrayList<>();
              for (int i = 0; i < blocks.size(); i++) {
                  if (marked.get(i))
                      continue;
                  for (int j = i + 1; j < blocks.size(); j++) {
                      if (Equi.equiBlocks(blocks.get(i), blocks.get(j))) {
                          marked.set(j);
                          LocationRole role = SAME_BRANCH;
                          if(blocks.get(j).isDefault()) {
                              role = DEFAULT_BRANCH;
                              hasDefault = true;
                          }
                          eqLocations.add(role.create(mc, blocks.get(j)));
                      }
                  }
                  if (!eqLocations.isEmpty()) {
                      int n = eqLocations.size() + (hasDefault ? 0 : 1);
                      if (n > 3)
                          n = (n - 3) / 2 + 3;
                      CaseBlock block = blocks.get(i);
                      mc.report(hasDefault ? "SameBranchesSwitchDefault" : "SameBranchesSwitch", computePriority(block, n), block,
                          eqLocations);
                      eqLocations.clear();
                      hasDefault = false;
                  }
              }
          }
      }
  
      @AstVisitor(nodes=AstNodes.EXPRESSIONS)
      public void visitExpr(Expression expr, MethodContext mc) {
          if (expr.getCode() == AstCode.TernaryOp
              && Equi.equiExpressions(expr.getArguments().get(1), expr.getArguments().get(2))) {
              mc.report("SameBranchesTernary", computePriority(expr.getArguments().get(1), 30), expr);
          }
      }
  
      private static int computePriority(Node block, int n) {
          int codeSize = Nodes.estimateCodeSize(block);
          return Math.max(0, 55 - ((int) (Math.sqrt(n * codeSize) * 5)));
      }
  
      // This check is not complete: it's still possible that it will return false for nonFallThrough
      private static boolean nonFallThrough(List<Node> body) {
          if (body.isEmpty())
              return false;
          Node last = body.get(body.size() - 1);
          return Nodes.isOp(last, AstCode.LoopOrSwitchBreak) || Nodes.isOp(last, AstCode.Return)
              || Nodes.isOp(last, AstCode.LoopContinue) || Nodes.isOp(last, AstCode.AThrow);
      }
  }
  