/** Expensive! */
  private void dumpCallGraphReachablesCSV() {
    try {
      FileWriter fw =
          new FileWriter(Project.v().getOutputDir() + File.separator + "reachables-count.csv");

      fw.write("Method,Reachables");

      for (MethodOrMethodContext momc : getReachableMethodContexts()) {
        if ("<clinit>".equals(momc.method().getName())) continue;

        Set<MethodOrMethodContext> c = new HashSet<MethodOrMethodContext>();
        c.add(momc);
        Filter filter = new Filter(noStaticInits);
        // filter on static initializers, hopefully they won't show in the stats,
        // or any calls that they make...
        ReachableMethods rm = new ReachableMethods(callGraph, c.iterator(), filter);
        rm.update();

        QueueReader<MethodOrMethodContext> edges = rm.listener();
        int reachables = 0;
        while (edges.hasNext()) {
          MethodOrMethodContext reachable = edges.next();
          if ("<clinit>".equals(reachable.method().getName())) continue;
          reachables++;
        }

        fw.write(momc + "|" + reachables + "\n");
      }

      fw.close();

    } catch (IOException e) {

    }
  }
  @Override
  protected void runInternal() {
    // don't print crap to screen!
    G.v().out = new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM);
    Scene.v().loadDynamicClasses();

    setSparkPointsToAnalysis();

    // other passes can print crap now
    G.v().out = System.out;

    ptsProvider = (PAG) Scene.v().getPointsToAnalysis();

    typeManager = ptsProvider.getTypeManager();

    // cache the call graph
    callGraph = Scene.v().getCallGraph();

    createNewToAllocMap();

    /*
    for (SootMethod method : getReachableMethods()) {
        Set<MethodOrMethodContext> mcs = getMethodContexts(method);
        if (mcs.size() > 30)
            System.out.println(method + " " + mcs.size());
    }
     */

    // dumpReachablesAndAllocNodes();
    // dumpCallGraphReachablesCSV();
    // dumpOutdegreesCSV();

    if (Config.v().dumpPta) {
      dumpPTA(Project.v().getOutputDir() + File.separator + "pta.txt");
    }

    if (Config.v().dumpCallGraph) {
      // dumpCallGraph(Project.v().getOutputDir() + File.separator + "callgraph.dot");
      String fileName = String.format("callgraph%d.txt", runCount++);
      dumpTextGraph(Project.v().getOutputDir() + File.separator + fileName);
    }

    // System.out.println(SparkEvaluator.v().toString());
  }
  public static void writeReport() {
    // make sure collapsed call graph has been run
    CollaspedCallGraph.v();

    FileWriter fw;
    try {
      String name = "";
      if ("".equals(Config.v().appName)) {
        name += "android-app";
      } else {
        name += Config.v().appName;
      }

      String additionalInfo = Config.v().additionalInfo;
      if (!"".equals(additionalInfo)) {
        additionalInfo = "_" + additionalInfo.replaceAll(" ", "_");
      }

      String fileName =
          name + "_" + getConfiguration().replaceAll(" ", "_") + additionalInfo + "_pta-report.txt";

      fw = new FileWriter(Project.v().getOutputDir() + File.separator + fileName);

      // write configuration details
      fw.write("App Name: " + name + "\n");
      fw.write("Config: " + getConfiguration() + "\n");
      fw.write("Cmdline supplied extra info: " + Config.v().additionalInfo + "\n");

      fw.write(refinementStats.toString());

      // write final run of pta
      fw.write(SparkEvaluator.v().toString());

      // write total lines of code
      fw.write("\nTotal Reachable LOC: " + getReachableLines() + "\n\n");

      // write information flow
      fw.write(infoFlowResults());

      // fw.write(finegrainedFlowResults());

      fw.close();
    } catch (IOException e) {

    }
  }
  private String buildImportantAllocs() {
    StringBuffer sb = new StringBuffer();

    for (SootClass clz : Scene.v().getClasses()) {
      if (Project.v().isSrcClass(clz)
          ||
          /*isImportantJavaAlloc(clz) ||*/
          clz.getName().startsWith(Project.DS_GENERATED_CLASSES_PREFIX)
          || clz.getName().startsWith("droidsafe.runtime")) {
        logger.info("Adding class to important alloc list of spark: {}", clz);
        sb.append(clz + ",");
      }
    }
    String ret = sb.toString();

    ret = ret.substring(0, ret.length() - 1);

    return ret;
  }
  private void dumpOutdegreesCSV() {
    try {
      FileWriter fw =
          new FileWriter(Project.v().getOutputDir() + File.separator + "reachables-outdegree.csv");

      fw.write("Method,Outdegree");

      for (MethodOrMethodContext momc : getReachableMethodContexts()) {
        int outdegree = 0;
        Iterator<Edge> edges = callGraph.edgesOutOf(momc);
        while (edges.hasNext()) {
          edges.next();
          outdegree++;
        }

        fw.write(momc + "|" + outdegree + "\n");
      }

      fw.close();

    } catch (IOException e) {

    }
  }
  private void dumpReachablesAndAllocNodes() {
    try {
      FileWriter fw =
          new FileWriter(Project.v().getOutputDir() + File.separator + "spark-dump.log");

      fw.write("# Reachable Method Contexts:\n\n");

      for (MethodOrMethodContext momc : getReachableMethodContexts()) {
        fw.write(momc + "\n\n");
      }

      fw.write("\n\n# AllocNodes: \n\n");
      Iterator<AllocNode> nodes = ptsProvider.getAllocNodeNumberer().iterator();
      while (nodes.hasNext()) {
        AllocNode node = nodes.next();
        fw.write(node + "\n\n");
      }

      fw.close();

    } catch (IOException e) {

    }
  }
  private static String infoFlowResults() {
    Hierarchy hierarchy = Scene.v().getActiveHierarchy();
    SootClass throwable = Scene.v().getSootClass("java.lang.Throwable");

    StringBuffer buf = new StringBuffer();

    // count number of flows
    // have to map it down to invoke statement because of context

    // key is invoke of sink -> sources
    Map<InvokeExpr, Set<Stmt>> invokeToSourcesMem = new HashMap<InvokeExpr, Set<Stmt>>();
    // key is invoke of sink -> sources
    Map<InvokeExpr, Set<Stmt>> invokeToSourcesRec = new HashMap<InvokeExpr, Set<Stmt>>();
    // key is invoke of sink -> sources
    Map<InvokeExpr, Set<Stmt>> invokeToSourcesArgs = new HashMap<InvokeExpr, Set<Stmt>>();
    // key is invoke of sink -> sources
    Map<InvokeExpr, Set<Stmt>> invokeToSourcesArgsPrecise = new HashMap<InvokeExpr, Set<Stmt>>();

    for (Map.Entry<Method, List<Method>> block :
        RCFGToSSL.v().getSpec().getEventBlocks().entrySet()) {
      // only count events in src classes, not in libraries
      boolean inSrc = false;
      for (IAllocNode recNode : block.getKey().getReceiverAllocNodes()) {
        if (recNode.getType() instanceof RefType) {
          SootClass clz = ((RefType) recNode.getType()).getSootClass();
          if (Project.v().isSrcClass(clz)) {
            inSrc = true;
            break;
          }
        }
      }

      if (!inSrc) continue;

      for (Method oe : block.getValue()) {
        if (oe.getSinkInfoKinds().size() > 0 && oe.getSourcesInfoKinds().size() > 0) {

          // only count sensitive sinks
          Stmt sinkInvoke = JimpleRelationships.v().getEnclosingStmt(oe.getInvokeExpr());
          if (!InfoKind.callsSensitiveSink(sinkInvoke)) continue;

          // we have a sink with connected sources
          InvokeExpr ie = oe.getInvokeExpr();

          // get args
          for (int i = 0; i < oe.getNumArgs(); i++) {

            Type formalArgType = oe.getActualArgType(i);
            // ignore method arguments that have a declared type of throwable or a subclass of
            // throwable
            if (formalArgType instanceof RefType
                && !((RefType) formalArgType).getSootClass().isInterface()
                && hierarchy.isClassSubclassOfIncluding(
                    ((RefType) formalArgType).getSootClass(), throwable)) continue;

            for (Map.Entry<InfoKind, Set<Stmt>> flows :
                oe.getArgSourceInfoUnitsConservative(i).entrySet()) {
              for (Stmt source : flows.getValue()) {
                if (InfoKind.callsSensitiveSource(source)) {
                  if (!invokeToSourcesArgs.containsKey(ie)) {
                    invokeToSourcesArgs.put(ie, new HashSet<Stmt>());
                  }
                  invokeToSourcesArgs.get(ie).add(source);
                }
              }
            }

            for (Map.Entry<InfoKind, Set<Stmt>> flows :
                oe.getArgSourceInfoUnitsPrecise(i).entrySet()) {
              for (Stmt source : flows.getValue()) {
                if (InfoKind.callsSensitiveSource(source)) {
                  if (!invokeToSourcesArgsPrecise.containsKey(ie)) {
                    invokeToSourcesArgsPrecise.put(ie, new HashSet<Stmt>());
                  }
                  invokeToSourcesArgsPrecise.get(ie).add(source);
                }
              }
            }
          }
          // get receiver
          for (Map.Entry<InfoKind, Set<Stmt>> flows : oe.getReceiverSourceInfoUnits().entrySet()) {
            // ignore all non-critical flows
            for (Stmt source : flows.getValue()) {
              if (InfoKind.callsSensitiveSource(source)) {
                if (!invokeToSourcesRec.containsKey(ie)) {
                  invokeToSourcesRec.put(ie, new HashSet<Stmt>());
                }
                invokeToSourcesRec.get(ie).add(source);
              }
            }
          }
          // get method accesses
          for (Map.Entry<InfoKind, Set<Stmt>> flows : oe.getMethodInfoUnits().entrySet()) {
            // ignore all non-critical flows
            for (Stmt source : flows.getValue()) {
              if (InfoKind.callsSensitiveSource(source)) {
                if (!invokeToSourcesMem.containsKey(ie)) {
                  invokeToSourcesMem.put(ie, new HashSet<Stmt>());
                }

                invokeToSourcesMem.get(ie).add(source);
              }
            }
          }
        }
      }
    }

    // count number of flows
    int flowsIntoSinksArgs = 0;
    int flowsIntoSinksArgsPrecise = 0;
    int flowsIntoSinksMem = 0;
    int flowsIntoSinksRec = 0;

    try {
      for (Map.Entry<InvokeExpr, Set<Stmt>> sink : invokeToSourcesArgs.entrySet()) {
        flowsIntoSinksArgs += sink.getValue().size();
      }

      for (Map.Entry<InvokeExpr, Set<Stmt>> sink : invokeToSourcesArgsPrecise.entrySet()) {
        flowsIntoSinksArgsPrecise += sink.getValue().size();
      }

      for (Map.Entry<InvokeExpr, Set<Stmt>> sink : invokeToSourcesMem.entrySet()) {
        flowsIntoSinksMem += sink.getValue().size();
      }

      for (Map.Entry<InvokeExpr, Set<Stmt>> sink : invokeToSourcesRec.entrySet()) {
        flowsIntoSinksRec += sink.getValue().size();
      }

    } catch (Exception e) {

    }

    buf.append("Info Flow Time Sec: " + infoFlowTimeSec + "\n");

    buf.append("Flows into sinks (Args): " + flowsIntoSinksArgs + "\n");
    buf.append("Flows into sinks (Args, Precise): " + flowsIntoSinksArgsPrecise + "\n");
    buf.append("Flows into sinks (Mem): " + flowsIntoSinksMem + "\n");
    buf.append("Flows into sinks (Rec): " + flowsIntoSinksRec + "\n");

    buf.append(reachableSinksSources());

    return buf.toString();
  }
  @Override
  public void tranformsInvoke(
      SootMethod containingMthd, SootMethod callee, InvokeExpr invokeExpr, Stmt stmt, Body body) {

    if (!Project.v().isSrcClass(containingMthd.getDeclaringClass())) {
      return;
    }

    if (modified.contains(stmt)) {
      return;
    }
    modified.add(stmt);

    IntentResolutionStats.v().contentProviderOps++;

    Value lvalue = null;
    if (stmt instanceof AssignStmt) {
      lvalue = ((AssignStmt) stmt).getLeftOp();
    }

    Set<SootField> targetCPFields = new LinkedHashSet<SootField>();

    boolean resolved = true;

    for (IAllocNode node : PTABridge.v().getPTSetIns(invokeExpr.getArg(0))) {
      resolved = addToTargets(node, targetCPFields, stmt);

      if (!resolved) {
        UnresolvedICC.v().addInfo(stmt, callee, "Unresolved URI for Content Provider");
        // can break here because we added all possible content provider destinations
        IntentResolutionStats.v().contentProviderOpsUnresolvedUri++;
        break;
      }
    }

    // for each field of harness that is a content provider
    for (SootField cpField : targetCPFields) {
      SootClass cpClass = ((RefType) cpField.getType()).getSootClass();
      SootMethod target = cpClass.getMethod(callee.getSubSignature());

      // create local and add to body
      Local local = Jimple.v().newLocal("_$contentprovider_local_" + localID++, cpField.getType());
      body.getLocals().add(local);

      // set field of cp to local [local = harness.contentproviderfield]
      // set local to field
      Stmt localAssign =
          Jimple.v().newAssignStmt(local, Jimple.v().newStaticFieldRef(cpField.makeRef()));
      // insert before original statement
      body.getUnits().insertBefore(localAssign, stmt);

      InvokeExpr newInvoke =
          Jimple.v().newVirtualInvokeExpr(local, target.makeRef(), invokeExpr.getArgs());

      // create statement to invoke
      Stmt toInsert = null;
      if (lvalue == null) {
        // original call not in an assign;
        toInsert = Jimple.v().newInvokeStmt(newInvoke);
      } else {
        // original call in an assign
        toInsert = Jimple.v().newAssignStmt(lvalue, newInvoke);
      }
      // insert after original statement just to have all locals assigned in a block
      body.getUnits().insertAfter(toInsert, stmt);
      logger.info(
          "Adding {} call to ContentProvider {} in method {}",
          callee.getSubSignature(),
          cpClass,
          containingMthd);

      // ignore generated calls in rcfg
      RCFG.v().ignoreInvokeForOutputEvents(toInsert);
    }

    // if resolved and in app target, then don't report
    if (resolved) {
      IntentResolutionStats.v().contentProviderOpsResolvedUri++;
      if (targetCPFields.size() > 0) {
        RCFG.v().ignoreInvokeForOutputEvents(stmt);
        IntentResolutionStats.v().contentProviderOpsInAppTotalTargets += targetCPFields.size();
      } else {
        IntentResolutionStats.v().contentProviderOpsInterAppTarget++;
      }
    }
  }