/**
   * Clone given method into this class with given name. Update necessary state of prior analyses.
   */
  private SootMethod cloneMethod(SootMethod ancestorM, String cloneName) {
    // check if we are cloning a method multiple times
    if (clonedToOriginal.containsValue(ancestorM)) {
      logger.error("Cloning method twice: {}", ancestorM);
      droidsafe.main.Main.exit(1);
    }

    SootMethod newMeth =
        new SootMethod(
            cloneName,
            ancestorM.getParameterTypes(),
            ancestorM.getReturnType(),
            ancestorM.getModifiers(),
            ancestorM.getExceptions());

    // System.out.printf("\tAdding method %s.\n", ancestorM);
    // register method
    methods.addMethod(newMeth);
    clazz.addMethod(newMeth);

    clonedToOriginal.put(newMeth, ancestorM);

    API.v().cloneMethodClassifications(ancestorM, newMeth);

    // clone body
    Body newBody = (Body) ancestorM.retrieveActiveBody().clone();
    newMeth.setActiveBody(newBody);

    JSAStrings.v().updateJSAResults(ancestorM.retrieveActiveBody(), newBody);

    return newMeth;
  }
  /**
   * Resolve the concrete target of a special invoke using our modified semantics for special invoke
   * expression.
   */
  private SootMethod resolveSpecialInvokeTarget(SpecialInvokeExpr si) {
    SootMethod target = null;

    try {
      target = SootUtils.resolve(si.getMethodRef());
    } catch (CannotFindMethodException e) {
      logger.error("Cannot find concrete method target for special invoke: {}", si);
      return null;
    }

    String targetSubSig = target.getSubSignature();

    SootClass current = target.getDeclaringClass();

    while (true) {
      if (current.declaresMethod(targetSubSig)) {
        return current.getMethod(targetSubSig);
      }

      // not a match in current, try superclass on next loop
      if (current.hasSuperclass()) current = current.getSuperclass();
      else {
        logger.error("Cannot find concrete method target for special invoke: {}", si);
        droidsafe.main.Main.exit(1);
        return null;
      }
    }
  }
  /**
   * Clone non-static ancestor methods that are not hidden by virtual dispatch and that are
   * reachable based on a pta run.
   */
  private void cloneReachableNonHiddenAncestorMethods(SootClass ancestor) {
    if (ClassCloner.isClonedClass(ancestor)) {
      logger.error("Cloning method from clone: {}", ancestor);
      droidsafe.main.Main.exit(1);
    }

    // create all methods, cloning body, replacing instance field refs
    for (SootMethod ancestorM : ancestor.getMethods()) {
      if (ancestorM.isAbstract()
          || ancestorM.isPhantom()
          || !ancestorM.isConcrete()
          || SootUtils.isRuntimeStubMethod(ancestorM)) continue;

      // never clone static methods
      if (ancestorM.isStatic()) continue;

      // clone only reachable methods
      if (!cloneAllMethods && !PTABridge.v().getReachableMethods().contains(ancestorM)) continue;

      // check if this method already exists
      if (containsMethod(ancestorM.getSignature())) {
        // System.out.printf("\tAlready contains method %s.\n", ancestorM);
        continue;
      }

      // turn off final for ancestor methods
      if (ancestorM.isFinal()) ancestorM.setModifiers(ancestorM.getModifiers() ^ Modifier.FINAL);

      cloneMethod(ancestorM, ancestorM.getName());
    }
  }
  @Override
  public Set<? extends IAllocNode> getPTSet(Value val, Context context) {
    // handle case for insensitive run
    if (k == 0) return getPTSetIns(val);

    final Set<AllocNode> allocNodes = new LinkedHashSet<AllocNode>();
    final Type filteringType = val.getType();

    PointsToSetInternal pts = null;

    try {
      if (val instanceof InstanceFieldRef) {
        final InstanceFieldRef ifr = (InstanceFieldRef) val;
        pts =
            (PointsToSetInternal)
                ptsProvider.reachingObjects(context, (Local) ifr.getBase(), ifr.getField());
      } else if (val instanceof ArrayRef) {
        ArrayRef arrayRef = (ArrayRef) val;
        pts =
            (PointsToSetInternal)
                ptsProvider.reachingObjectsOfArrayElement(
                    ptsProvider.reachingObjects(context, (Local) arrayRef.getBase()));
      } else if (val instanceof Local) {
        pts = (PointsToSetInternal) ptsProvider.reachingObjects(context, (Local) val);
      } else if (val instanceof StaticFieldRef) {
        SootField field = ((StaticFieldRef) val).getField();
        pts = (PointsToSetInternal) ptsProvider.reachingObjects(field);
      } else if (val instanceof NullConstant) {
        return allocNodes;
      } else {
        logger.error("Unknown reference type for insenstive search: {} {}", val, val.getClass());
        droidsafe.main.Main.exit(1);
      }

      // visit internal points to set and grab all allocnodes
      pts.forall(
          new P2SetVisitor() {
            public void visit(Node n) {
              if (typeManager.castNeverFails(n.getType(), filteringType))
                allocNodes.add((AllocNode) n);
            }
          });

    } catch (Exception e) {
      logger.info("Some sort of error getting context insensitive points to set for {}", val, e);
      // e.printStackTrace();
    }

    return allocNodes;
  }
  @Override
  public Set<? extends IAllocNode> getPTSet(IAllocNode node, final SootField f) {
    if (f.isStatic()) {
      logger.error("Cannot call getPTSet(node, field) with static field: {}", f);
      droidsafe.main.Main.exit(1);
    }
    final Type filteringType = f.getType();

    final Set<AllocNode> allocNodes = new LinkedHashSet<AllocNode>();

    HashPointsToSet pointsToSet = new HashPointsToSet(node.getType(), ptsProvider);
    pointsToSet.add((AllocNode) node);

    ((PointsToSetInternal) ptsProvider.reachingObjects(pointsToSet, f))
        .forall(
            new P2SetVisitor() {
              @Override
              public void visit(Node node) {
                if (typeManager.castNeverFails(node.getType(), filteringType))
                  allocNodes.add((AllocNode) node);
              }
            });

    /*
    PointsToSetInternal bases = (PointsToSetInternal)ptsProvider.getSetFactory().newSet(node.getType(), ptsProvider);
    bases.add(node);

    final PointsToSetInternal pts = ptsProvider.getSetFactory().newSet(
        (f instanceof SootField) ? ((SootField)f).getType() : null, ptsProvider );
    bases.forall( new P2SetVisitor() {
        public final void visit( Node n ) {
            Node nDotF = ((AllocNode) n).dot( f );
            if(nDotF != null) pts.addAll( nDotF.getP2Set(), null );
        }} );

    //visit internal points to set and grab all allocnodes
    pts.forall(new P2SetVisitor() {
        public void visit(Node n) {
            allocNodes.add((AllocNode)n);
        }
    });
     */
    return (Set<? extends IAllocNode>) allocNodes;
  }
  /** Create the bi map of NewExpr <-> AllocNode */
  private void createNewToAllocMap() {
    newToAllocNodeMap = HashBiMap.create();
    allAllocNodes = new LinkedHashSet<AllocNode>();

    Map<SootClass, Integer> nodeCount = new LinkedHashMap<SootClass, Integer>();

    int realSize = 0;

    for (AllocNode node : ptsProvider.getAllocNodes()) {
      if (!(node instanceof InsensitiveAllocNode)) {
        logger.error("Found non-insensitive node in ptsProvider.getAllocNodes()");
        droidsafe.main.Main.exit(1);
      }

      InsensitiveAllocNode insNode = (InsensitiveAllocNode) node;

      newToAllocNodeMap.put(node.getNewExpr(), insNode);
      realSize++;
      allAllocNodes.add(node);

      // countNode(nodeCount, node);

      for (Map.Entry<Context, ObjectSensitiveAllocNode> entry :
          insNode.getContextNodeMap().entrySet()) {
        allAllocNodes.add(entry.getValue());
        // countNode(nodeCount, node);
      }
    }

    System.out.println("Alloc node size (insensitive objects): " + realSize);

    /* used to print a sorted list of alloc nodes created
    Map<SootClass, Integer> sortedNodeCount = SootUtils.sortByValue(nodeCount);
    for (Map.Entry<SootClass, Integer> entry : sortedNodeCount.entrySet()) {
        System.out.println(entry.getValue() + " " + entry.getKey());
    }
     */
  }