/* * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) * Copyright (C) 2015 University of Freiburg * * This file is part of the ULTIMATE ModelCheckerUtils Library. * * The ULTIMATE ModelCheckerUtils Library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE ModelCheckerUtils Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE ModelCheckerUtils Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE ModelCheckerUtils Library, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE ModelCheckerUtils Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import de.uni_freiburg.informatik.ultimate.core.lib.models.annotation.LoopEntryAnnotation; import de.uni_freiburg.informatik.ultimate.core.lib.models.annotation.LoopEntryAnnotation.LoopEntryType; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.ICallAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfg; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgCallTransition; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgForkTransitionThreadCurrent; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgForkTransitionThreadOther; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgJoinTransitionThreadCurrent; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgJoinTransitionThreadOther; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgReturnTransition; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgTransition; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IInternalAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IReturnAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgEdge; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgEdgeIterator; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgLocation; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgLocationIterator; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transitions.UnmodifiableTransFormula; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramNonOldVar; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramOldVar; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramVar; import de.uni_freiburg.informatik.ultimate.logic.Script.LBool; import de.uni_freiburg.informatik.ultimate.util.DfsBookkeeping; import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; /** * * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) * */ public class IcfgUtils { private IcfgUtils() { // do not instantiate utility class } public static Set getPotentialCycleProgramPoints(final IIcfg icfg) { return new IcfgLocationIterator<>(icfg).asStream().filter(a -> a.getOutgoingEdges().stream().anyMatch(b -> { final LoopEntryAnnotation loa = LoopEntryAnnotation.getAnnotation(b); return loa != null && loa.getLoopEntryType() == LoopEntryType.GOTO; })).collect(Collectors.toSet()); } /** * Collect all program points that are predecessors or successors of an {@link IIcfgCallTransition}. */ public static Set getCallerAndCalleePoints(final IIcfg icfg) { return new IcfgEdgeIterator(icfg).asStream().filter(e -> e instanceof IIcfgCallTransition) .flatMap(e -> Stream.of((LOC) e.getSource(), (LOC) e.getTarget())).collect(Collectors.toSet()); } /** * Collect all program points that are predecessors of an {@link IIcfgReturnTransition}. */ public static Set getReturnPredecessorPoints(final IIcfg icfg) { return new IcfgEdgeIterator(icfg).asStream().filter(e -> e instanceof IIcfgReturnTransition) .map(x -> (LOC) x.getSource()).collect(Collectors.toSet()); } public static boolean isConcurrent(final IIcfg icfg) { return !icfg.getCfgSmtToolkit().getConcurrencyInformation().getThreadInstanceMap().isEmpty(); } /** * @return {@link List} that contains all {@link IcfgEdge}s that originate from an initial location. */ public static List extractStartEdges(final IIcfg icfg) { return icfg.getInitialNodes().stream().flatMap(a -> a.getOutgoingEdges().stream()).collect(Collectors.toList()); } public static > UnmodifiableTransFormula getTransformula(final T transition) { if (transition instanceof IInternalAction) { return ((IInternalAction) transition).getTransformula(); } if (transition instanceof ICallAction) { return ((ICallAction) transition).getLocalVarsAssignment(); } if (transition instanceof IReturnAction) { return ((IReturnAction) transition).getAssignmentOfReturn(); } else { throw new UnsupportedOperationException( "Dont know how to extract transformula from transition " + transition); } } public static Set getErrorLocations(final IIcfg icfg) { final Set errorLocs = new LinkedHashSet<>(); for (final Entry> entry : icfg.getProcedureErrorNodes().entrySet()) { errorLocs.addAll(entry.getValue()); } return errorLocs; } public static boolean isErrorLocation(final IIcfg icfg, final IcfgLocation loc) { if (icfg == null) { throw new IllegalArgumentException(); } if (loc == null) { return false; } final String proc = loc.getProcedure(); final Map> errorNodes = icfg.getProcedureErrorNodes(); if (errorNodes == null || errorNodes.isEmpty()) { return false; } final Set procErrorNodes = errorNodes.get(proc); if (procErrorNodes == null || procErrorNodes.isEmpty()) { return false; } return procErrorNodes.contains(loc); } public static Stream getAllLocations(final IIcfg icfg) { return icfg.getProgramPoints().values().stream().flatMap(x -> x.values().stream()); } public static int getNumberOfLocations(final IIcfg icfg) { return icfg.getProgramPoints().entrySet().stream().map(x -> x.getValue().size()) .collect(Collectors.summingInt(Integer::intValue)); } /** * Collects all program variables, both globals and local variables of all procedures. For global variables, both * oldvar and non-oldvar are included. */ public static Set collectAllProgramVars(final CfgSmtToolkit csToolkit) { final Set result = new HashSet<>(); for (final IProgramNonOldVar nonold : csToolkit.getSymbolTable().getGlobals()) { result.add(nonold); final IProgramOldVar oldVar = nonold.getOldVar(); result.add(oldVar); } for (final String proc : csToolkit.getProcedures()) { result.addAll(csToolkit.getSymbolTable().getLocals(proc)); } return result; } /** * Compute a hashcode for the graph structure of an ICFG. The hashcode is is only based on the hashcode of locations * and edges and ignores {@link IProgramVar}s and other objects that come along with an ICFG. The method can help * while debugging in order to find nondeterminism in our implementation. */ public static int computeIcfgHashCode(final IIcfg icfg) { final IcfgLocationIterator locIt = new IcfgLocationIterator<>(icfg); int result = 0; while (locIt.hasNext()) { final LOC loc = locIt.next(); result += loc.hashCode(); for (final IcfgEdge edge : loc.getOutgoingEdges()) { result += edge.hashCode(); } } return result; } public static boolean areReachableProgramPointsRegistered(final IIcfg icfg) { final Set reachableProgramPoints = new IcfgEdgeIterator(icfg).asStream().map(x -> x.getTarget()) .collect(Collectors.toSet()); reachableProgramPoints.addAll(icfg.getInitialNodes()); final Set registeredProgramPoints = icfg.getProgramPoints().entrySet().stream() .flatMap(x -> x.getValue().entrySet().stream()).map(Entry::getValue).collect(Collectors.toSet()); final Set diff = new HashSet<>(reachableProgramPoints); diff.removeAll(registeredProgramPoints); if (!diff.isEmpty()) { throw new AssertionError("Program points reachable but not registered: " + diff); } return true; } public static boolean areRegisteredProgramPointsReachable(final IIcfg icfg) { final Set reachableProgramPoints = new IcfgEdgeIterator(icfg).asStream().map(x -> x.getTarget()) .collect(Collectors.toSet()); reachableProgramPoints.addAll(icfg.getInitialNodes()); final Set registeredProgramPoints = icfg.getProgramPoints().entrySet().stream() .flatMap(x -> x.getValue().entrySet().stream()).map(Entry::getValue).collect(Collectors.toSet()); final Set diff = new HashSet<>(registeredProgramPoints); diff.removeAll(reachableProgramPoints); // ExitNodes are registered even if they are not reachable (the optimization // where we omit ExitNodes would require many case distinctions and would only // save a few nodes). final Set exitProgramPoints = icfg.getProcedureExitNodes().entrySet().stream().map(Entry::getValue) .collect(Collectors.toSet()); diff.removeAll(exitProgramPoints); if (!diff.isEmpty()) { throw new AssertionError("Program points registered but not reachable: " + diff); } return true; } /** * Checks an invariant that must hold for {@link IIcfg}s: For every procedure entry node, there must be a * corresponding procedure exit node, and vice versa. This should hold, even if e.g. the exit node is unreachable. */ public static boolean checkMatchingEntryExitNodes(final IIcfg icfg) { final var entryNodes = icfg.getProcedureEntryNodes(); final var exitNodes = icfg.getProcedureExitNodes(); for (final var e : entryNodes.entrySet()) { final var proc = e.getKey(); assert e.getValue() != null : "Entry node for procedure " + proc + " is null"; final var exit = exitNodes.get(proc); if (exit == null) { assert false : "No corresponding exit node for entry node with procedure " + proc; return false; } } for (final var e : exitNodes.entrySet()) { final var proc = e.getKey(); assert e.getValue() != null : "Exit node for procedure " + proc + " is null"; final var entry = entryNodes.get(proc); if (entry == null) { assert false : "No corresponding entry node for exit node with procedure " + proc; return false; } } return true; } /** * @return true iff loc is entry node of some procedure */ public static boolean isEntry(final LOC loc, final IIcfg icfg) { final var entry = icfg.getProcedureEntryNodes().get(loc.getProcedure()); return entry.equals(loc); } /** * @return true iff loc is exit node of some procedure */ public static boolean isExit(final LOC loc, final IIcfg icfg) { final var exit = icfg.getProcedureExitNodes().get(loc.getProcedure()); return loc.equals(exit); } /** * Determines if a given edge is currently enabled, given the set of current locations (across all thread instances) * * Note that {@link IIcfgForkTransitionThreadCurrent} and {@link IIcfgJoinTransitionThreadCurrent} are never * considered enabled, but their counterparts ({@link IIcfgForkTransitionThreadOther} and * {@link IIcfgJoinTransitionThreadOther}) are. * * @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * * @param * The type of locations * @param locs * The set of all current locations * @param edge * An edge of the CFG * @return true iff the given edge is enabled */ public static boolean isEnabled(final Set locs, final IcfgEdge edge) { if (edge instanceof IIcfgForkTransitionThreadCurrent || edge instanceof IIcfgJoinTransitionThreadCurrent) { // These edges exist in the Icfg, but in traces they are represented by the respective // IIcfg*TransitionThreadOther transitions. Hence they are never enabled. return false; } if (edge instanceof IIcfgForkTransitionThreadOther) { // Enabled if predecessor location is in state, and forked thread instance is not yet running. final Set threads = locs.stream().map(IcfgLocation::getProcedure).collect(Collectors.toSet()); final String forkedThread = edge.getSucceedingProcedure(); return locs.contains(edge.getSource()) && !threads.contains(forkedThread); } if (edge instanceof IIcfgJoinTransitionThreadOther) { // Enabled if predecessor location and predecessor location of the corresponding // IIcfg*TransitionThreadCurrent instance are both in the state. final var joinOther = (IIcfgJoinTransitionThreadOther) edge; final var joinCurrent = joinOther.getCorrespondingIIcfgJoinTransitionCurrentThread(); return locs.contains(joinOther.getSource()) && locs.contains(joinCurrent.getSource()); } return locs.contains(edge.getSource()); } /** * Performs a DFS to determine if from a given source location, a target edge can be reached. A given cache allows * re-using results across queries. * * @param sourceLoc * The source location from which reachability is checked. * @param isTarget * Identifies target edges. * @param prune * Used to prune edges if paths through such edges should not be considered. However, such edges are * still considered as target edges. * @param getCachedResult * A function that retrieves a cached reachability result. SAT represents reachability, UNSAT represents * non-reachability. * @param setCachedResult * A function that updates the cache. * @return */ public static boolean canReachCached(final IcfgLocation sourceLoc, final Predicate isTarget, final Predicate prune, final Function getCachedResult, final BiConsumer setCachedResult) { // First check if result is cached. final LBool cachedCanReach = getCachedResult.apply(sourceLoc); if (cachedCanReach != LBool.UNKNOWN) { return cachedCanReach == LBool.SAT; } // Do a DFS search of the CFG. final DfsBookkeeping dfs = new DfsBookkeeping<>(); final LinkedList worklist = new LinkedList<>(); worklist.add(sourceLoc); LBool canReach = LBool.UNSAT; while (!worklist.isEmpty() && canReach != LBool.SAT) { final IcfgLocation currentLoc = worklist.getLast(); // If the result is cached, retrieve it, mark the location as visited, and backtrack. final LBool knownCanReach = getCachedResult.apply(currentLoc); if (knownCanReach != LBool.UNKNOWN) { // Do not replace UNKNOWN by UNSAT, as we must not propagate this unreachability to predecessors. canReach = knownCanReach == LBool.SAT || canReach != LBool.UNKNOWN ? knownCanReach : canReach; worklist.removeLast(); dfs.push(currentLoc); dfs.backtrack(); continue; } // When backtracking, remember the computed result for future queries. if (dfs.isVisited(currentLoc)) { assert canReach != LBool.SAT : "After reachability confirmed, should be fast-backtracking"; worklist.removeLast(); if (dfs.peek() != currentLoc) { // Node might have been added to worklist multiple times and since been visited. Hence it might not // be on the stack. In that case, no backtracking is needed, nor do we visit the node again. continue; } final boolean completeBacktrack = dfs.backtrack(); // Inside a loop, reachability cannot be UNSAT. Yet, a successor outside the loop might have // UNSAT status. Once back inside the loop, we here set canReach to UNKNOWN. // Conversely, if we just backtracked the outermost loop head, reset canReach to UNSAT. canReach = completeBacktrack ? LBool.UNSAT : LBool.UNKNOWN; if (canReach != LBool.UNKNOWN) { assert canReach == LBool.UNSAT; setCachedResult.accept(currentLoc, false); } continue; } // Visit location. dfs.push(currentLoc); final List outgoing = currentLoc.getOutgoingEdges(); final List successors = new ArrayList<>(outgoing.size()); for (final IcfgEdge edge : outgoing) { final IcfgLocation succ = edge.getTarget(); // Abort when reachability is confirmed. if (isTarget.test(edge)) { canReach = LBool.SAT; break; } // Ignore successors of explicitly pruned edges. if (prune.test(edge)) { continue; } final int stackIndex; if (!dfs.isVisited(succ)) { // If the successor has not been visited before, explore it now. successors.add(succ); } else if ((stackIndex = dfs.stackIndexOf(succ)) != -1) { // If the edge leads back to the stack, reachability is unknown until succ (or an even earlier loop // head) is backtracked. To avoid infinite looping, we do not explore succ. assert getCachedResult .apply(succ) == LBool.UNKNOWN : "Loop heads on stack must have UNKNOWN status"; canReach = LBool.UNKNOWN; dfs.updateLoopHead(currentLoc, new Pair<>(stackIndex, succ)); } else { // If the successor has been visited before, but is not on the stack, then we know its reachability // is either UNSAT or UNKNOWN. final LBool succCanReach = getCachedResult.apply(succ); assert succCanReach != LBool.SAT : "Backtracked node must not have SAT status"; // In either case, we do not need to explore it again. Instead, we simply propagate reachability and // loop head information back to currentLoc. canReach = succCanReach == LBool.UNKNOWN ? LBool.UNKNOWN : canReach; dfs.backPropagateLoopHead(currentLoc, succ); } } // When reachability was confirmed, do not search any further. if (canReach != LBool.SAT) { successors.stream().forEach(worklist::add); } } // Fast-backtrack: If we exited the previous loop because reachability was confirmed, // we only backtrack, and no longer explore states on the work list. while (!dfs.isStackEmpty()) { assert canReach == LBool.SAT : "Fast-backtracking must only happen in case of reachability"; final IcfgLocation currentLoc = dfs.peek(); dfs.backtrack(); setCachedResult.accept(currentLoc, true); } final LBool cachedReachability = getCachedResult.apply(sourceLoc); assert cachedReachability != LBool.UNKNOWN : "reachability should be clearly determined"; assert cachedReachability == canReach : "contradictory reachability: " + cachedReachability + " != " + canReach; return canReach == LBool.SAT; } public static Set getAllThreadInstances(final IIcfg icfg) { final Stream threadInstances = icfg.getCfgSmtToolkit().getConcurrencyInformation().getThreadInstanceMap().values().stream() .flatMap(Collection::stream).map(ThreadInstance::getThreadInstanceName); final String mainThread = DataStructureUtils.getOneAndOnly(icfg.getInitialNodes(), "initial node").getProcedure(); return Stream.concat(Stream.of(mainThread), threadInstances).collect(Collectors.toSet()); } public static Set> getForksInLoop(final IIcfg icfg) { final Set> result = new HashSet<>(); final Map entryLocs = icfg.getProcedureEntryNodes(); for (final var fork : icfg.getCfgSmtToolkit().getConcurrencyInformation().getThreadInstanceMap().keySet()) { final ArrayDeque queue = new ArrayDeque<>(); final Set visited = new HashSet<>(); queue.add(fork.getTarget()); queue.add(entryLocs.get(fork.getNameOfForkedProcedure())); while (!queue.isEmpty()) { final IcfgLocation loc = queue.pop(); if (!visited.add(loc)) { continue; } if (loc.equals(fork.getSource())) { result.add(fork); break; } for (final IcfgEdge edge : loc.getOutgoingEdges()) { queue.add(edge.getTarget()); if (edge instanceof IIcfgForkTransitionThreadCurrent) { final var fork2 = (IIcfgForkTransitionThreadCurrent) edge; queue.add(entryLocs.get(fork2.getNameOfForkedProcedure())); } } } } return result; } }