private Area compareSubGraphsEx(ControlFlowGraph graph,
                                  BasicBlock startSample,
                                  Set<BasicBlock> catchBlocks,
                                  BasicBlock startCatch,
                                  int finallyType,
                                  Map<BasicBlock, Boolean> mapLast,
                                  boolean skippedFirst) {
    class BlockStackEntry {
      public final BasicBlock blockCatch;
      public final BasicBlock blockSample;

      // TODO: correct handling (merging) of multiple paths
      public final List<int[]> lstStoreVars;

      BlockStackEntry(BasicBlock blockCatch, BasicBlock blockSample, List<int[]> lstStoreVars) {
        this.blockCatch = blockCatch;
        this.blockSample = blockSample;
        this.lstStoreVars = new ArrayList<>(lstStoreVars);
      }
    }

    List<BlockStackEntry> stack = new LinkedList<>();

    Set<BasicBlock> setSample = new HashSet<>();

    Map<String, BasicBlock[]> mapNext = new HashMap<>();

    stack.add(new BlockStackEntry(startCatch, startSample, new ArrayList<>()));

    while (!stack.isEmpty()) {
      BlockStackEntry entry = stack.remove(0);
      BasicBlock blockCatch = entry.blockCatch;
      BasicBlock blockSample = entry.blockSample;

      boolean isFirstBlock = !skippedFirst && blockCatch == startCatch;
      boolean isLastBlock = mapLast.containsKey(blockCatch);
      boolean isTrueLastBlock = isLastBlock && mapLast.get(blockCatch);

      int compareType = (isFirstBlock ? 1 : 0) | (isTrueLastBlock ? 2 : 0);
      if (!compareBasicBlocksEx(graph, blockCatch, blockSample, compareType, finallyType, entry.lstStoreVars)) {
        return null;
      }

      if (blockSample.getSuccessors().size() != blockCatch.getSuccessors().size()) {
        return null;
      }

      setSample.add(blockSample);

      // direct successors
      for (int i = 0; i < blockCatch.getSuccessors().size(); i++) {
        BasicBlock sucCatch = blockCatch.getSuccessors().get(i);
        BasicBlock sucSample = blockSample.getSuccessors().get(i);

        if (catchBlocks.contains(sucCatch) && !setSample.contains(sucSample)) {
          stack.add(new BlockStackEntry(sucCatch, sucSample, entry.lstStoreVars));
        }
      }

      // exception successors
      if (isLastBlock && blockSample.getSeq().isEmpty()) {
        // do nothing, blockSample will be removed anyway
      }
      else {
        if (blockCatch.getSuccessorExceptions().size() == blockSample.getSuccessorExceptions().size()) {
          for (int i = 0; i < blockCatch.getSuccessorExceptions().size(); i++) {
            BasicBlock sucCatch = blockCatch.getSuccessorExceptions().get(i);
            BasicBlock sucSample = blockSample.getSuccessorExceptions().get(i);

            String excCatch = graph.getExceptionRange(sucCatch, blockCatch).getUniqueExceptionsString();
            String excSample = graph.getExceptionRange(sucSample, blockSample).getUniqueExceptionsString();

            // FIXME: compare handlers if possible
            if (Objects.equals(excCatch, excSample)) {
              if (catchBlocks.contains(sucCatch) && !setSample.contains(sucSample)) {
                List<int[]> lst = entry.lstStoreVars;

                if (sucCatch.getSeq().length() > 0 && sucSample.getSeq().length() > 0) {
                  Instruction instrCatch = sucCatch.getSeq().getInstr(0);
                  Instruction instrSample = sucSample.getSeq().getInstr(0);

                  if (instrCatch.opcode == CodeConstants.opc_astore &&
                      instrSample.opcode == CodeConstants.opc_astore) {
                    lst = new ArrayList<>(lst);
                    lst.add(new int[]{instrCatch.operand(0), instrSample.operand(0)});
                  }
                }

                stack.add(new BlockStackEntry(sucCatch, sucSample, lst));
              }
            }
            else {
              return null;
            }
          }
        }
        else {
          return null;
        }
      }

      if (isLastBlock) {
        Set<BasicBlock> successors = new HashSet<>(blockSample.getSuccessors());
        successors.removeAll(setSample);

        for (BlockStackEntry stackEntry : stack) {
          successors.remove(stackEntry.blockSample);
        }

        for (BasicBlock successor : successors) {
          if (graph.getLast() != successor) { // FIXME: why?
            mapNext.put(blockSample.id + "#" + successor.id, new BasicBlock[]{blockSample, successor, isTrueLastBlock ? successor : null});
          }
        }
      }
    }

    return new Area(startSample, setSample, getUniqueNext(graph, new HashSet<>(mapNext.values())));
  }
private Area compareSubGraphsEx(ControlFlowGraph graph,
                                  BasicBlock startSample,
                                  Set<BasicBlock> catchBlocks,
                                  BasicBlock startCatch,
                                  int finallyType,
                                  Map<BasicBlock, Boolean> mapLast,
                                  boolean skippedFirst)