@Override public void computeAliasTaints(Abstraction d1, Stmt src, Value targetValue, Set<Abstraction> taintSet, SootMethod method, Abstraction newAbs) { // Use global aliasing Value baseValue = ((InstanceFieldRef) targetValue).getBase(); Set<AccessPath> aliases = methodToAliases.getUnchecked(method).get (new AccessPath(baseValue, true)); if (aliases != null) for (AccessPath ap : aliases) { Abstraction aliasAbs = newAbs.deriveNewAbstraction( ap.merge(newAbs.getAccessPath()), null); if (taintSet.add(aliasAbs)) // We have found a new alias. This new base object may however yet // again alias with something, so we need to check again if (ap.isInstanceFieldRef()) { InstanceFieldRef aliasBaseVal = Jimple.v().newInstanceFieldRef (ap.getPlainValue(), ap.getFirstField().makeRef()); computeAliasTaints(d1, src, aliasBaseVal, taintSet, method, aliasAbs); } } }
@Override public Set<Abstraction> getTaintsForMethod(Stmt stmt, Abstraction d1, Abstraction taintedPath) { Set<Abstraction> resList = new HashSet<Abstraction>(); for (ITaintPropagationWrapper w : this.wrappers) { Set<Abstraction> curAbsSet = w.getTaintsForMethod(stmt, d1, taintedPath); if (curAbsSet != null) resList.addAll(curAbsSet); } // Bookkeeping for statistics if (resList.isEmpty()) misses.incrementAndGet(); else hits.incrementAndGet(); return resList; }
@Override public Set<Abstraction> getTaintsForMethod(Stmt stmt, Abstraction d1, Abstraction taintedPath) { // Compute the tainted access paths Set<AccessPath> aps = getTaintsForMethodInternal(stmt, taintedPath.getAccessPath()); if (aps == null || aps.isEmpty()) return null; // Convert the access paths into full abstractions Set<Abstraction> res = new HashSet<Abstraction>(aps.size()); for (AccessPath ap : aps) if (ap == taintedPath.getAccessPath()) res.add(taintedPath); else res.add(taintedPath.deriveNewAbstraction(ap, stmt)); return res; }
/** * Computes the taints for the aliases of a given tainted variable * @param d1 The context in which the variable has been tainted * @param src The statement that tainted the variable * @param targetValue The target value which has been tainted * @param taintSet The set to which all generated alias taints shall be * added * @param method The method containing src * @param newAbs The newly generated abstraction for the variable taint * @return The set of immediately available alias abstractions. If no such * abstractions exist, null is returned */ private void computeAliasTaints (final Abstraction d1, final Stmt src, final Value targetValue, Set<Abstraction> taintSet, SootMethod method, Abstraction newAbs) { // If we are not in a conditionally-called method, we run the // full alias analysis algorithm. Otherwise, we use a global // non-flow-sensitive approximation. if (!d1.getAccessPath().isEmpty()) { aliasingStrategy.computeAliasTaints(d1, src, targetValue, taintSet, method, newAbs); } else if (targetValue instanceof InstanceFieldRef) { assert enableImplicitFlows; implicitFlowAliasingStrategy.computeAliasTaints(d1, src, targetValue, taintSet, method, newAbs); } }
/** * Adds a new result of the data flow analysis to the collection * @param resultAbs The abstraction at the sink instruction */ private void addResult(AbstractionAtSink resultAbs) { // Check whether we need to filter a result in a system package if (ignoreFlowsInSystemPackages && SystemClassHandler.isClassInSystemPackage (interproceduralCFG().getMethodOf(resultAbs.getSinkStmt()).getDeclaringClass().getName())) return; // Make sure that the sink statement also appears inside the // abstraction resultAbs = new AbstractionAtSink (resultAbs.getAbstraction().deriveNewAbstraction (resultAbs.getAbstraction().getAccessPath(), resultAbs.getSinkStmt()), resultAbs.getSinkStmt()); resultAbs.getAbstraction().setCorrespondingCallSite(resultAbs.getSinkStmt()); Abstraction newAbs = this.results.putIfAbsentElseGet (resultAbs, resultAbs.getAbstraction()); if (newAbs != resultAbs.getAbstraction()) newAbs.addNeighbor(resultAbs.getAbstraction()); }
/** * Checks whether the given base value matches the base of the given * taint abstraction * @param baseValue The value to check * @param source The taint abstraction to check * @return True if the given value has the same base value as the given * taint abstraction, otherwise false */ protected boolean baseMatches(final Value baseValue, Abstraction source) { if (baseValue instanceof Local) { if (baseValue.equals(source.getAccessPath().getPlainValue())) return true; } else if (baseValue instanceof InstanceFieldRef) { InstanceFieldRef ifr = (InstanceFieldRef) baseValue; if (ifr.getBase().equals(source.getAccessPath().getPlainValue()) && source.getAccessPath().firstFieldMatches(ifr.getField())) return true; } else if (baseValue instanceof StaticFieldRef) { StaticFieldRef sfr = (StaticFieldRef) baseValue; if (source.getAccessPath().firstFieldMatches(sfr.getField())) return true; } return false; }
protected boolean registerActivationCallSite(Unit callSite, SootMethod callee, Abstraction activationAbs) { if (!flowSensitiveAliasing) return false; Unit activationUnit = activationAbs.getActivationUnit(); if (activationUnit == null) return false; Set<Unit> callSites = activationUnitsToCallSites.putIfAbsentElseGet (activationUnit, new ConcurrentHashSet<Unit>()); if (callSites.contains(callSite)) return false; if (!activationAbs.isAbstractionActive()) if (!callee.getActiveBody().getUnits().contains(activationUnit)) { boolean found = false; for (Unit au : callSites) if (callee.getActiveBody().getUnits().contains(au)) { found = true; break; } if (!found) return false; } return callSites.add(callSite); }
@Override public void run() { final Set<SourceContextAndPath> paths = abstraction.getPaths(); final Abstraction pred = abstraction.getPredecessor(); if (pred != null) { for (SourceContextAndPath scap : paths) { // Process the predecessor if (processPredecessor(scap, pred)) // Schedule the predecessor executor.execute(new SourceFindingTask(pred)); // Process the predecessor's neighbors if (pred.getNeighbors() != null) for (Abstraction neighbor : pred.getNeighbors()) if (processPredecessor(scap, neighbor)) // Schedule the predecessor executor.execute(new SourceFindingTask(neighbor)); } } }
/** * Checks whether the given abstraction is a source. If so, a result entry * is created. * @param abs The abstraction to check * @param scap The path leading up to the current abstraction * @return True if the current abstraction is a source, otherwise false */ protected boolean checkForSource(Abstraction abs, SourceContextAndPath scap) { if (abs.getPredecessor() != null) return false; // If we have no predecessors, this must be a source assert abs.getSourceContext() != null; assert abs.getNeighbors() == null; // Register the source that we have found SourceContext sourceContext = abs.getSourceContext(); results.addResult(scap.getAccessPath(), scap.getStmt(), sourceContext.getAccessPath(), sourceContext.getStmt(), sourceContext.getUserData(), scap.getPath()); return true; }
/** * Checks whether the given abstraction is a source. If so, a result entry * is created. * @param abs The abstraction to check * @param scap The path leading up to the current abstraction * @return True if the current abstraction is a source, otherwise false */ private boolean checkForSource(Abstraction abs, SourceContextAndPath scap) { if (abs.getPredecessor() != null) return false; // If we have no predecessors, this must be a source assert abs.getSourceContext() != null; assert abs.getNeighbors() == null; // Register the source that we have found SourceContext sourceContext = abs.getSourceContext(); results.addResult(scap.getAccessPath(), scap.getStmt(), sourceContext.getAccessPath(), sourceContext.getStmt(), sourceContext.getUserData(), scap.getPath()); return true; }
@Override protected void processExit(PathEdge<Unit, Abstraction> edge) { super.processExit(edge); if (followReturnsPastSeeds && followReturnsPastSeedsHandler != null) { final Abstraction d1 = edge.factAtSource(); final Unit u = edge.getTarget(); final Abstraction d2 = edge.factAtTarget(); final SootMethod methodThatNeedsSummary = icfg.getMethodOf(u); final Map<Unit, Map<Abstraction, Abstraction>> inc = incoming(d1, methodThatNeedsSummary); if (inc == null || inc.isEmpty()) followReturnsPastSeedsHandler.handleFollowReturnsPastSeeds(d1, u, d2); } }
@Override public void injectContext(IInfoflowSolver otherSolver, SootMethod callee, Abstraction d3, Unit callSite, Abstraction d2, Abstraction d1) { if (!(otherSolver instanceof InfoflowSolver)) throw new RuntimeException("Other solver must be of same type"); synchronized (incoming) { for (Unit sP : icfg.getStartPointsOf(callee)) addIncoming(sP, d3, callSite, d2); } // First, get a list of the other solver's jump functions. // Then release the lock on otherSolver.jumpFn before doing // anything that locks our own jumpFn. final Set<Abstraction> otherAbstractions; final InfoflowSolver solver = (InfoflowSolver) otherSolver; synchronized (solver.jumpFn) { otherAbstractions = new HashSet<Abstraction> (solver.jumpFn.reverseLookup(callSite, d2).keySet()); } for (Abstraction dx1: otherAbstractions) if (!dx1.getAccessPath().isEmpty() && !dx1.getAccessPath().isStaticFieldRef()) processEdge(new PathEdge<Unit, Abstraction>(d1, callSite, d2)); }
@Override protected Set<Abstraction> computeReturnFlowFunction( FlowFunction<Abstraction> retFunction, Abstraction d1, Abstraction d2, Unit callSite, Set<Abstraction> callerSideDs) { if (retFunction instanceof SolverReturnFlowFunction) { // Get the d1s at the start points of the caller Set<Abstraction> d1s = new HashSet<Abstraction>(callerSideDs.size() * 5); for (Abstraction d4 : callerSideDs) if (d4 == zeroValue) d1s.add(d4); else synchronized (jumpFn) { d1s.addAll(jumpFn.reverseLookup(callSite, d4).keySet()); } return ((SolverReturnFlowFunction) retFunction).computeTargets(d2, d1, d1s); } else return retFunction.computeTargets(d2); }
@Override protected void propagate(Abstraction sourceVal, Unit target, Abstraction targetVal, EdgeFunction<BinaryDomain> f, /* deliberately exposed to clients */ Unit relatedCallSite, /* deliberately exposed to clients */ boolean isUnbalancedReturn) { // Check whether we already have an abstraction that entails the new one. // In such a case, we can simply ignore the new abstraction. boolean noProp = false; /* for (Abstraction abs : new HashSet<Abstraction>(jumpFn.forwardLookup(sourceVal, target).keySet())) if (abs != targetVal) { if (abs.entails(targetVal)) { noProp = true; break; } if (targetVal.entails(abs)) { jumpFn.removeFunction(sourceVal, target, abs); } } */ if (!noProp) super.propagate(sourceVal, target, targetVal, f, relatedCallSite, isUnbalancedReturn); }
@Override public Set<Pair<Unit, Abstraction>> endSummary(SootMethod m, Abstraction d3) { Set<Pair<Unit, Abstraction>> res = null; for (Unit sP : icfg.getStartPointsOf(m)) { Set<Table.Cell<Unit,Abstraction,EdgeFunction<IFDSSolver.BinaryDomain>>> endSum = super.endSummary(sP, d3); if (endSum == null || endSum.isEmpty()) continue; if (res == null) res = new HashSet<>(); for (Table.Cell<Unit,Abstraction,EdgeFunction<IFDSSolver.BinaryDomain>> cell : endSum) res.add(new Pair<>(cell.getRowKey(), cell.getColumnKey())); } return res; }
@Override protected void processExit(PathEdge<Unit, Abstraction> edge) { super.processExit(edge); if (followReturnsPastSeeds && followReturnsPastSeedsHandler != null) { final Abstraction d1 = edge.factAtSource(); final Unit u = edge.getTarget(); final Abstraction d2 = edge.factAtTarget(); final SootMethod methodThatNeedsSummary = icfg.getMethodOf(u); for (Unit sP : icfg.getStartPointsOf(methodThatNeedsSummary)) { final Map<Unit, Set<Abstraction>> inc = incoming(d1, sP); if (inc == null || inc.isEmpty()) followReturnsPastSeedsHandler.handleFollowReturnsPastSeeds(d1, u, d2); } } }
@Override public Set<Abstraction> getTaintedValues(Stmt call, Abstraction source, Value[] params){ //check some evaluated methods: //arraycopy: //arraycopy(Object src, int srcPos, Object dest, int destPos, int length) //Copies an array from the specified source array, beginning at the specified position, //to the specified position of the destination array. if(call.getInvokeExpr().getMethod().toString().contains("arraycopy")) if(params[0].equals(source.getAccessPath().getPlainValue())) { Abstraction abs = source.deriveNewAbstraction(params[2], false, call, source.getAccessPath().getBaseType()); abs.setCorrespondingCallSite(call); return Collections.singleton(abs); } return Collections.emptySet(); }
@Override public void computeAliasTaints(Abstraction d1, Stmt src, Value targetValue, Set<Abstraction> taintSet, SootMethod method, Abstraction newAbs) { // If we don't have an alias set for this method yet, we compute it if (!globalAliases.containsRow(method)) computeGlobalAliases(method); // Use global aliasing Value baseValue = ((InstanceFieldRef) targetValue).getBase(); Set<AccessPath> aliases = globalAliases.get(method, new AccessPath( baseValue)); if (aliases != null) for (AccessPath ap : aliases) { Abstraction aliasAbs = newAbs.deriveNewAbstraction( ap.merge(newAbs.getAccessPath()), src); taintSet.add(aliasAbs); } }
public void injectContext(IInfoflowSolver otherSolver, SootMethod callee, Abstraction d3, Unit callSite, Abstraction d2) { if (!(otherSolver instanceof InfoflowSolver)) throw new RuntimeException("Other solver must be of same type"); synchronized (incoming) { for (Unit sP : icfg.getStartPointsOf(callee)) addIncoming(sP, d3, callSite, d2); } // First, get a list of the other solver's jump functions. // Then release the lock on otherSolver.jumpFn before doing // anything that locks our own jumpFn. final Set<Abstraction> otherAbstractions; final InfoflowSolver solver = (InfoflowSolver) otherSolver; synchronized (solver.jumpFn) { otherAbstractions = new HashSet<Abstraction> (solver.jumpFn.reverseLookup(callSite, d2)); } for (Abstraction d1: otherAbstractions) if (!d1.getAccessPath().isEmpty() && !d1.getAccessPath().isStaticFieldRef()) processEdge(new PathEdge<Unit, Abstraction>(d1, callSite, d2)); }
@Override protected Set<Abstraction> computeReturnFlowFunction (FlowFunction<Abstraction> retFunction, Abstraction d2, Unit callSite, Set<Abstraction> callerSideDs) { if (retFunction instanceof SolverReturnFlowFunction) { // Get the d1s at the start points of the caller Set<Abstraction> d1s = new HashSet<Abstraction>(callerSideDs.size() * 5); for (Abstraction d4 : callerSideDs) if (d4 == zeroValue) d1s.add(d4); else synchronized (jumpFn) { d1s.addAll(jumpFn.reverseLookup(callSite, d4)); } return ((SolverReturnFlowFunction) retFunction).computeTargets(d2, d1s); } else return retFunction.computeTargets(d2); }
@Override protected void propagate(Abstraction sourceVal, Unit target, Abstraction targetVal, /* deliberately exposed to clients */ Unit relatedCallSite, /* deliberately exposed to clients */ boolean isUnbalancedReturn) { // Check whether we already have an abstraction that entails the new one. // In such a case, we can simply ignore the new abstraction. boolean noProp = false; /* for (Abstraction abs : new HashSet<Abstraction>(jumpFn.forwardLookup(sourceVal, target).keySet())) if (abs != targetVal) { if (abs.entails(targetVal)) { noProp = true; break; } if (targetVal.entails(abs)) { jumpFn.removeFunction(sourceVal, target, abs); } } */ if (!noProp) super.propagate(sourceVal, target, targetVal, relatedCallSite, isUnbalancedReturn); }
public void injectContext(IInfoflowSolver otherSolver, SootMethod callee, Abstraction d3, Unit callSite, Abstraction d2) { if (!(otherSolver instanceof InfoflowSolver)) throw new RuntimeException("Other solver must be of same type"); synchronized (incoming) { for (Unit sP : icfg.getStartPointsOf(callee)) addIncoming(sP, d3, callSite, d2); } // First, get a list of the other solver's jump functions. // Then release the lock on otherSolver.jumpFn before doing // anything that locks our own jumpFn. final Set<Abstraction> otherAbstractions; final InfoflowSolver solver = (InfoflowSolver) otherSolver; synchronized (solver.jumpFn) { otherAbstractions = new HashSet<Abstraction> (solver.jumpFn.reverseLookup(callSite, d2).keySet()); } for (Abstraction d1: otherAbstractions) if (!d1.getAccessPath().isEmpty() && !d1.getAccessPath().isStaticFieldRef()) processEdge(new PathEdge<Unit, Abstraction>(d1, callSite, d2)); }
@Override protected Set<Abstraction> computeReturnFlowFunction (FlowFunction<Abstraction> retFunction, Abstraction d2, Unit callSite, Set<Abstraction> callerSideDs) { if (retFunction instanceof SolverReturnFlowFunction) { // Get the d1s at the start points of the caller Set<Abstraction> d1s = new HashSet<Abstraction>(callerSideDs.size() * 5); for (Abstraction d4 : callerSideDs) if (d4 == zeroValue) d1s.add(d4); else synchronized (jumpFn) { d1s.addAll(jumpFn.reverseLookup(callSite, d4).keySet()); } return ((SolverReturnFlowFunction) retFunction).computeTargets(d2, d1s); } else return retFunction.computeTargets(d2); }
@Override public void computeAliasTaints (final Abstraction d1, final Stmt src, final Value targetValue, Set<Abstraction> taintSet, SootMethod method, Abstraction newAbs) { // Start the backwards solver Abstraction bwAbs = newAbs.deriveInactiveAbstraction(src); for (Unit predUnit : interproceduralCFG().getPredsOf(src)) bSolver.processEdge(new PathEdge<Unit, Abstraction>(d1, predUnit, bwAbs)); }
@Override public void computeAliasTaints (final Abstraction d1, final Stmt src, final Value targetValue, Set<Abstraction> taintSet, SootMethod method, Abstraction newAbs) { // nothing to do here }
@Override public boolean isExclusive(Stmt stmt, Abstraction taintedPath) { for (ITaintPropagationWrapper w : this.wrappers) if (w.isExclusive(stmt, taintedPath)) return true; return false; }
@Override public Set<Abstraction> getAliasesForMethod(Stmt stmt, Abstraction d1, Abstraction taintedPath) { Set<Abstraction> resList = new HashSet<Abstraction>(); for (ITaintPropagationWrapper w : this.wrappers) { Set<Abstraction> curAbsSet = w.getAliasesForMethod(stmt, d1, taintedPath); if (curAbsSet != null) resList.addAll(curAbsSet); } return resList; }
@Override public boolean isExclusive(Stmt stmt, Abstraction taintedPath) { if (isExclusiveInternal(stmt, taintedPath.getAccessPath())) { wrapperHits.incrementAndGet(); return true; } else { wrapperMisses.incrementAndGet(); return false; } }
/** * we cannot rely just on "real" heap objects, but must also inspect locals because of Jimple's representation ($r0 =... ) * @param val the value which gets tainted * @param source the source from which the taints comes from. Important if not the value, but a field is tainted * @return true if a reverseFlow should be triggered or an inactive taint should be propagated (= resulting object is stored in heap = alias) */ private boolean triggerInaktiveTaintOrReverseFlow(Stmt stmt, Value val, Abstraction source){ if (stmt instanceof DefinitionStmt) { DefinitionStmt defStmt = (DefinitionStmt) stmt; // If the left side is overwritten completely, we do not need to // look for aliases. This also covers strings. if (defStmt.getLeftOp() instanceof Local && defStmt.getLeftOp() == source.getAccessPath().getPlainValue()) return false; // Arrays are heap objects if (val instanceof ArrayRef) return true; if (val instanceof FieldRef) return true; } // Primitive types or constants do not have aliases if (val.getType() instanceof PrimType) return false; if (val instanceof Constant) return false; // String cannot have aliases if (isStringType(val.getType())) return false; return val instanceof FieldRef || (val instanceof Local && ((Local)val).getType() instanceof ArrayType); }
/** * Checks whether the given base value matches the base of the given * taint abstraction and ends there. So a will match a, but not a.x. * Not that this function will still match a to a.*. * @param baseValue The value to check * @param source The taint abstraction to check * @return True if the given value has the same base value as the given * taint abstraction and no further elements, otherwise false */ protected boolean baseMatchesStrict(final Value baseValue, Abstraction source) { if (!baseMatches(baseValue, source)) return false; if (baseValue instanceof Local) return source.getAccessPath().isLocal(); else if (baseValue instanceof InstanceFieldRef || baseValue instanceof StaticFieldRef) return source.getAccessPath().getFieldCount() == 1; throw new RuntimeException("Unexpected left side"); }
/** * Adds the given initial seeds to the information flow problem * @param unit The unit to be considered as a seed * @param seeds The abstractions with which to start at the given seed */ public void addInitialSeeds(Unit unit, Set<Abstraction> seeds) { if (this.initialSeeds.containsKey(unit)) this.initialSeeds.get(unit).addAll(seeds); else this.initialSeeds.put(unit, new HashSet<Abstraction>(seeds)); }
private void runSourceFindingTasks(final Set<AbstractionAtSink> res) { if (res.isEmpty()) return; long beforePathTracking = System.nanoTime(); logger.info("Obtainted {} connections between sources and sinks", res.size()); // Start the propagation tasks int curResIdx = 0; for (final AbstractionAtSink abs : res) { logger.info("Building path " + ++curResIdx); buildPathForAbstraction(abs); // Also build paths for the neighbors of our result abstraction if (abs.getAbstraction().getNeighbors() != null) for (Abstraction neighbor : abs.getAbstraction().getNeighbors()) { AbstractionAtSink neighborAtSink = new AbstractionAtSink(neighbor, abs.getSinkStmt()); buildPathForAbstraction(neighborAtSink); } } try { executor.awaitCompletion(); } catch (InterruptedException ex) { logger.error("Could not wait for path executor completion: {0}", ex.getMessage()); ex.printStackTrace(); } logger.info("Path processing took {} seconds in total", (System.nanoTime() - beforePathTracking) / 1E9); }
/** * Computes the path of tainted data between the source and the sink * @param res The data flow tracker results */ private void computeTaintPathsInternal(final Set<AbstractionAtSink> res) { logger.debug("Running path reconstruction"); logger.info("Obtainted {} connections between sources and sinks", res.size()); int curResIdx = 0; for (final AbstractionAtSink abs : res) { logger.info("Building path " + ++curResIdx); executor.execute(new Runnable() { @Override public void run() { Stack<Pair<Stmt, Set<Abstraction>>> initialStack = new Stack<Pair<Stmt, Set<Abstraction>>>(); initialStack.push(new Pair<Stmt, Set<Abstraction>>(null, Collections.newSetFromMap(new IdentityHashMap<Abstraction,Boolean>()))); for (SourceContextAndPath context : getPaths(lastTaskId++, abs.getAbstraction(), initialStack)) { List<Stmt> newPath = new ArrayList<>(context.getPath()); newPath.add(abs.getSinkStmt()); results.addResult(abs.getAbstraction().getAccessPath(), abs.getSinkStmt(), context.getAccessPath(), context.getStmt(), context.getUserData(), newPath); } } }); } try { executor.awaitCompletion(); } catch (InterruptedException ex) { logger.error("Could not wait for path executor completion: {0}", ex.getMessage()); ex.printStackTrace(); } executor.shutdown(); logger.debug("Path reconstruction done."); }
private boolean processPredecessor(SourceContextAndPath scap, Abstraction pred) { // Put the current statement on the list SourceContextAndPath extendedScap = scap.extendPath(pred, reconstructPaths); if (extendedScap == null) return false; // Add the new path checkForSource(pred, extendedScap); return pred.addPathElement(extendedScap); }
@Override protected Set<Abstraction> computeReturnFlowFunction( FlowFunction<Abstraction> retFunction, Abstraction d1, Abstraction d2, Unit callSite, Collection<Abstraction> callerSideDs) { if (retFunction instanceof SolverReturnFlowFunction) { // Get the d1s at the start points of the caller return ((SolverReturnFlowFunction) retFunction).computeTargets(d2, d1, callerSideDs); } else return retFunction.computeTargets(d2); }
@Override protected Set<Abstraction> computeNormalFlowFunction (FlowFunction<Abstraction> flowFunction, Abstraction d1, Abstraction d2) { if (flowFunction instanceof SolverNormalFlowFunction) return ((SolverNormalFlowFunction) flowFunction).computeTargets(d1, d2); else return flowFunction.computeTargets(d2); }
@Override protected Set<Abstraction> computeCallToReturnFlowFunction (FlowFunction<Abstraction> flowFunction, Abstraction d1, Abstraction d2) { if (flowFunction instanceof SolverCallToReturnFlowFunction) return ((SolverCallToReturnFlowFunction) flowFunction).computeTargets(d1, d2); else return flowFunction.computeTargets(d2); }