content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * 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.taobao.android.dx.dex.code;
  
  import com.taobao.android.dx.rop.code.BasicBlock;
  import com.taobao.android.dx.rop.code.BasicBlockList;
  import com.taobao.android.dx.rop.code.RopMethod;
  import com.taobao.android.dx.rop.cst.CstType;
  import com.taobao.android.dx.rop.type.Type;
  import com.taobao.android.dx.rop.type.TypeList;
  import com.taobao.android.dx.util.IntList;
  import java.util.ArrayList;
  import java.util.HashSet;
  
  /**
   * Constructor of {@link CatchTable} instances from {@link RopMethod}
   * and associated data.
   */
  public final class StdCatchBuilder implements CatchBuilder {
      /** the maximum range of a single catch handler, in code units */
      private static final int MAX_CATCH_RANGE = 65535;
  
      /** {@code non-null;} method to build the list for */
      private final RopMethod method;
  
      /** {@code non-null;} block output order */
      private final int[] order;
  
      /** {@code non-null;} address objects for each block */
      private final BlockAddresses addresses;
  
      /**
       * Constructs an instance. It merely holds onto its parameters for
       * a subsequent call to {@link #build}.
       *
       * @param method {@code non-null;} method to build the list for
       * @param order {@code non-null;} block output order
       * @param addresses {@code non-null;} address objects for each block
       */
      public StdCatchBuilder(RopMethod method, int[] order,
              BlockAddresses addresses) {
          if (method == null) {
              throw new NullPointerException("method == null");
          }
  
          if (order == null) {
              throw new NullPointerException("order == null");
          }
  
          if (addresses == null) {
              throw new NullPointerException("addresses == null");
          }
  
          this.method = method;
          this.order = order;
          this.addresses = addresses;
      }
  
      /** {@inheritDoc} */
      public CatchTable build() {
          return build(method, order, addresses);
      }
  
      /** {@inheritDoc} */
      public boolean hasAnyCatches() {
          BasicBlockList blocks = method.getBlocks();
          int size = blocks.size();
  
          for (int i = 0; i < size; i++) {
              BasicBlock block = blocks.get(i);
              TypeList catches = block.getLastInsn().getCatches();
              if (catches.size() != 0) {
                  return true;
              }
          }
  
          return false;
      }
  
      /** {@inheritDoc} */
      public HashSet<Type> getCatchTypes() {
          HashSet<Type> result = new HashSet<Type>(20);
          BasicBlockList blocks = method.getBlocks();
          int size = blocks.size();
  
          for (int i = 0; i < size; i++) {
              BasicBlock block = blocks.get(i);
              TypeList catches = block.getLastInsn().getCatches();
              int catchSize = catches.size();
  
              for (int j = 0; j < catchSize; j++) {
                  result.add(catches.getType(j));
              }
          }
  
          return result;
      }
  
      /**
       * Builds and returns the catch table for a given method.
       *
       * @param method {@code non-null;} method to build the list for
       * @param order {@code non-null;} block output order
       * @param addresses {@code non-null;} address objects for each block
       * @return {@code non-null;} the constructed table
       */
      public static CatchTable build(RopMethod method, int[] order,
              BlockAddresses addresses) {
          int len = order.length;
          BasicBlockList blocks = method.getBlocks();
          ArrayList<CatchTable.Entry> resultList =
              new ArrayList<CatchTable.Entry>(len);
          CatchHandlerList currentHandlers = CatchHandlerList.EMPTY;
          BasicBlock currentStartBlock = null;
          BasicBlock currentEndBlock = null;
  
          for (int i = 0; i < len; i++) {
              BasicBlock block = blocks.labelToBlock(order[i]);
  
              if (!block.canThrow()) {
                  /*
                   * There is no need to concern ourselves with the
                   * placement of blocks that can't throw with respect
                   * to the blocks that *can* throw.
                   */
                  continue;
              }
  
              CatchHandlerList handlers = handlersFor(block, addresses);
  
              if (currentHandlers.size() == 0) {
                  // This is the start of a new catch range.
                  currentStartBlock = block;
                  currentEndBlock = block;
                  currentHandlers = handlers;
                  continue;
              }
  
              if (currentHandlers.equals(handlers)
                      && rangeIsValid(currentStartBlock, block, addresses)) {
                  /*
                   * The block we are looking at now has the same handlers
                   * as the block that started the currently open catch
                   * range, and adding it to the currently open range won't
                   * cause it to be too long.
                   */
                  currentEndBlock = block;
                  continue;
              }
  
              /*
               * The block we are looking at now has incompatible handlers,
               * so we need to finish off the last entry and start a new
               * one. Note: We only emit an entry if it has associated handlers.
               */
              if (currentHandlers.size() != 0) {
                  CatchTable.Entry entry =
                      makeEntry(currentStartBlock, currentEndBlock,
                              currentHandlers, addresses);
                  resultList.add(entry);
              }
  
              currentStartBlock = block;
              currentEndBlock = block;
              currentHandlers = handlers;
          }
  
          if (currentHandlers.size() != 0) {
              // Emit an entry for the range that was left hanging.
              CatchTable.Entry entry =
                  makeEntry(currentStartBlock, currentEndBlock,
                          currentHandlers, addresses);
              resultList.add(entry);
          }
  
          // Construct the final result.
  
          int resultSz = resultList.size();
  
          if (resultSz == 0) {
              return CatchTable.EMPTY;
          }
  
          CatchTable result = new CatchTable(resultSz);
  
          for (int i = 0; i < resultSz; i++) {
              result.set(i, resultList.get(i));
          }
  
          result.setImmutable();
          return result;
      }
  
      /**
       * Makes the {@link CatchHandlerList} for the given basic block.
       *
       * @param block {@code non-null;} block to get entries for
       * @param addresses {@code non-null;} address objects for each block
       * @return {@code non-null;} array of entries
       */
      private static CatchHandlerList handlersFor(BasicBlock block,
              BlockAddresses addresses) {
          IntList successors = block.getSuccessors();
          int succSize = successors.size();
          int primary = block.getPrimarySuccessor();
          TypeList catches = block.getLastInsn().getCatches();
          int catchSize = catches.size();
  
          if (catchSize == 0) {
              return CatchHandlerList.EMPTY;
          }
  
          if (((primary == -1) && (succSize != catchSize))
                  || ((primary != -1) &&
                          ((succSize != (catchSize + 1))
                                  || (primary != successors.get(catchSize))))) {
              /*
               * Blocks that throw are supposed to list their primary
               * successor -- if any -- last in the successors list, but
               * that constraint appears to be violated here.
               */
              throw new RuntimeException(
                      "shouldn't happen: weird successors list");
          }
  
          /*
           * Reduce the effective catchSize if we spot a catch-all that
           * isn't at the end.
           */
          for (int i = 0; i < catchSize; i++) {
              Type type = catches.getType(i);
              if (type.equals(Type.OBJECT)) {
                  catchSize = i + 1;
                  break;
              }
          }
  
          CatchHandlerList result = new CatchHandlerList(catchSize);
  
          for (int i = 0; i < catchSize; i++) {
              CstType oneType = new CstType(catches.getType(i));
              CodeAddress oneHandler = addresses.getStart(successors.get(i));
              result.set(i, oneType, oneHandler.getAddress());
          }
  
          result.setImmutable();
          return result;
      }
  
      /**
       * Makes a {@link CatchTable#Entry} for the given block range and
       * handlers.
       *
       * @param start {@code non-null;} the start block for the range (inclusive)
       * @param end {@code non-null;} the start block for the range (also inclusive)
       * @param handlers {@code non-null;} the handlers for the range
       * @param addresses {@code non-null;} address objects for each block
       */
      private static CatchTable.Entry makeEntry(BasicBlock start,
              BasicBlock end, CatchHandlerList handlers,
              BlockAddresses addresses) {
          /*
           * We start at the *last* instruction of the start block, since
           * that's the instruction that can throw...
           */
          CodeAddress startAddress = addresses.getLast(start);
  
          // ...And we end *after* the last instruction of the end block.
          CodeAddress endAddress = addresses.getEnd(end);
  
          return new CatchTable.Entry(startAddress.getAddress(),
                  endAddress.getAddress(), handlers);
      }
  
      /**
       * Gets whether the address range for the given two blocks is valid
       * for a catch handler. This is true as long as the covered range is
       * under 65536 code units.
       *
       * @param start {@code non-null;} the start block for the range (inclusive)
       * @param end {@code non-null;} the start block for the range (also inclusive)
       * @param addresses {@code non-null;} address objects for each block
       * @return {@code true} if the range is valid as a catch range
       */
      private static boolean rangeIsValid(BasicBlock start, BasicBlock end,
              BlockAddresses addresses) {
          if (start == null) {
              throw new NullPointerException("start == null");
          }
  
          if (end == null) {
              throw new NullPointerException("end == null");
          }
  
          // See above about selection of instructions.
          int startAddress = addresses.getLast(start).getAddress();
          int endAddress = addresses.getEnd(end).getAddress();
  
          return (endAddress - startAddress) <= MAX_CATCH_RANGE;
      }
  }
  