/* * Copyright (C) 2021 Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * Copyright (C) 2021 University of Freiburg * * This file is part of the ULTIMATE Util Library. * * The ULTIMATE Util 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 Util 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 Util Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE Util 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 Util Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; /** * Manages book-keeping data for depth-first traversal of a graph with sophisticated loop detection. This loop detection * allows to distinguish backtracking because all reachable nodes have been explored from backtracking inside a loop, * where perhaps some reachable nodes have not yet been explored. * * @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * * @param * The type of nodes in the traversed graph */ public class DfsBookkeeping { private final List mStack = new ArrayList<>(); // Maps each visited node v to the outermost loop head reached from v, and its stack index. // If v has no entry, it has not been visited. If v is mapped to null, no loop head has been reached from v. // When backtracking v, this is used to determine if all nodes reachable from v have been explored, or if this may // not be the case because v is in a loop. private final Map> mVisited2LoopHeadIndex = new HashMap<>(); /** * Determines if the given node has already been visited. */ public boolean isVisited(final V node) { return mVisited2LoopHeadIndex.containsKey(node); } /** * Determines if the DFS has started, i.e., if any node has been visited so far. */ public boolean hasStarted() { return !mVisited2LoopHeadIndex.isEmpty(); } /** * Determines if the stack is empty. */ public boolean isStackEmpty() { return mStack.isEmpty(); } /** * Returns the index of the given node in the stack (the bottom element has index 0, the top element's index is the * biggest). Returns -1 if the node is not on the stack. */ public int stackIndexOf(final V node) { return mStack.indexOf(node); } /** * Pushes the given node on the DFS stack. * * @return true if this is the first visit to the node, false otherwise. */ public boolean push(final V node) { final boolean visitedBefore = isVisited(node); if (!visitedBefore) { mVisited2LoopHeadIndex.put(node, null); } assert !mStack.contains(node) : "must not infinitely unroll loop"; mStack.add(node); return !visitedBefore; } /** * Retrieves the top element of the DFS stack. */ public V peek() { return mStack.get(mStack.size() - 1); } private V pop() { return mStack.remove(mStack.size() - 1); } /** * Backtracks the top node on the stack. * * @return true if the node is backtracked completely, i.e., all nodes reachable from it have been explored, and * were not ignored because the node is part of a loop. */ public boolean backtrack() { final V oldNode = pop(); assert isVisited(oldNode) : "stack node must have been visited"; Pair loopHead = mVisited2LoopHeadIndex.get(oldNode); if (loopHead != null && loopHead.getSecond() == oldNode) { // The outermost loop head can be backtracked completely. mVisited2LoopHeadIndex.put(oldNode, null); loopHead = null; } // If there is a loop head, propagate it to the predecessor. if (loopHead != null) { assert !mStack.isEmpty() : "Initial node must not be in a loop (except as loop head)"; assert validLoopHead(loopHead) : "Backtracked node's loop head must be higher on the stack"; updateLoopHead(peek(), loopHead); } // Determine if we are backtracking inside a loop, or if the backtrack is "complete". return loopHead == null; } /** * Updates the stored loop head for a given node. I.e., if there is an existing loop head, the loop head deeper on * the stack is kept. If there is no existing loop head, sets the node's loop head to the given one. */ public void updateLoopHead(final V node, final Pair newLoopHead) { assert mStack.contains(node) : "loop head can only be updated for stack nodes"; assert isVisited(node) : "loop head can only be updated for visited nodes"; assert validLoopHead(newLoopHead) : "new loop head is invalid"; final Pair oldLoopHead = mVisited2LoopHeadIndex.get(node); assert oldLoopHead == null || validLoopHead(oldLoopHead) : "old loop head has become invalid"; if (oldLoopHead == null || newLoopHead.getFirst() < oldLoopHead.getFirst()) { mVisited2LoopHeadIndex.put(node, newLoopHead); } } /** * When encountering an edge to a previously visited node, propagates loop head information from the successor node * of the edge back to the current node. */ public void backPropagateLoopHead(final V current, final V successor) { final Pair loopHead = getStackedLoopHead(successor); if (loopHead != null) { updateLoopHead(current, loopHead); } } private Pair getStackedLoopHead(final V node) { int currentIndex = Integer.MAX_VALUE; V currentNode = node; while (true) { assert currentIndex >= 0 : "negative loop head index"; assert isVisited(currentNode) : "encountered unvisited node in loop head chain"; final Pair loopHead = mVisited2LoopHeadIndex.get(currentNode); if (loopHead == null || validLoopHead(loopHead)) { return loopHead; } final V loopHeadNode = loopHead.getSecond(); if (loopHeadNode == currentNode) { return null; } assert loopHead.getFirst() < currentIndex : "loop head index must decrease"; currentIndex = loopHead.getFirst(); currentNode = loopHeadNode; } } private boolean validLoopHead(final Pair loopHead) { return loopHead.getFirst() < mStack.size() && mStack.get(loopHead.getFirst()) == loopHead.getSecond(); } }