private static String reachableSinksSources() {
    StringBuffer buf = new StringBuffer();
    Set<Stmt> sinks = new HashSet<Stmt>();
    Set<Stmt> sources = new HashSet<Stmt>();

    for (SootMethod method : CollaspedCallGraph.v().getAllMethods()) {
      for (CallToTarget apiCall : CollaspedCallGraph.v().getAPICallTargets(method)) {
        if (API.v().hasSourceInfoKind(apiCall.getTarget())
            && InfoKind.callsSensitiveSource(apiCall.getStmt())) {
          sources.add(apiCall.getStmt());
        }

        if (API.v().hasSinkInfoKind(apiCall.getTarget())
            && InfoKind.callsSensitiveSink(apiCall.getStmt())) {
          sinks.add(apiCall.getStmt());
        }
      }
    }

    buf.append("Total reachable sink call statments: " + sinks.size() + "\n");
    buf.append("Total reachable source call statements: " + sources.size() + "\n");

    return buf.toString();
  }
  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();
  }