private AlgorithmStatus run0(ReachedSet reached, Algorithm algorithm)
     throws InterruptedException, CPAException, CPAEnabledAnalysisPropertyViolationException {
   logger.log(Level.INFO, "Starting sub-analysis");
   shutdownNotifier.shutdownIfNecessary();
   AlgorithmStatus status = algorithm.run(reached);
   shutdownNotifier.shutdownIfNecessary();
   logger.log(Level.INFO, "Finished sub-analysis");
   return status;
 }
  @Override
  protected void refineUsingInterpolants(
      final ARGReachedSet pReached,
      final InterpolationTree<ValueAnalysisState, ValueAnalysisInterpolant> pInterpolationTree)
      throws InterruptedException {
    final boolean predicatePrecisionIsAvailable = isPredicatePrecisionAvailable(pReached);

    Map<ARGState, List<Precision>> refinementInformation = new HashMap<>();
    Collection<ARGState> refinementRoots =
        pInterpolationTree.obtainRefinementRoots(restartStrategy);

    for (ARGState root : refinementRoots) {
      shutdownNotifier.shutdownIfNecessary();
      root = relocateRefinementRoot(root, predicatePrecisionIsAvailable);

      if (refinementRoots.size() == 1
          && isSimilarRepeatedRefinement(
              pInterpolationTree.extractPrecisionIncrement(root).values())) {
        root = relocateRepeatedRefinementRoot(root);
      }

      List<Precision> precisions = new ArrayList<>(2);
      // merge the value precisions of the subtree, and refine it
      precisions.add(
          mergeValuePrecisionsForSubgraph(root, pReached)
              .withIncrement(pInterpolationTree.extractPrecisionIncrement(root)));

      // merge the predicate precisions of the subtree, if available
      if (predicatePrecisionIsAvailable) {
        precisions.add(mergePredicatePrecisionsForSubgraph(root, pReached));
      }

      refinementInformation.put(root, precisions);
    }

    for (Entry<ARGState, List<Precision>> info : refinementInformation.entrySet()) {
      shutdownNotifier.shutdownIfNecessary();
      List<Predicate<? super Precision>> precisionTypes = new ArrayList<>(2);

      precisionTypes.add(VariableTrackingPrecision.isMatchingCPAClass(ValueAnalysisCPA.class));
      if (predicatePrecisionIsAvailable) {
        precisionTypes.add(Predicates.instanceOf(PredicatePrecision.class));
      }

      pReached.removeSubtree(info.getKey(), info.getValue(), precisionTypes);
    }
  }
  private List<AbstractState> combineARGStates(
      final List<ARGState> combiningStates,
      final Map<String, Integer> pStateToPos,
      final List<AbstractState> pInitialStates)
      throws InterruptedException, CPAException {
    // set every state to the top state (except for automaton states) in case we have no concrete
    // information
    List<AbstractState> result = new ArrayList<>(pInitialStates);

    Iterable<AbstractState> wrapped;
    int index;

    // replace top by more concrete information found by exploration (saved in ARGStates)
    for (ARGState combiner : combiningStates) {
      shutdown.shutdownIfNecessary();

      wrapped = getWrappedStates(combiner);
      for (AbstractState innerWrapped : wrapped) {
        shutdown.shutdownIfNecessary();

        if (!pStateToPos.containsKey(getName(innerWrapped))) {
          Preconditions.checkState(
              innerWrapped instanceof AutomatonState
                  || innerWrapped instanceof AssumptionStorageState,
              "Found state which is not considered in combined composite state and which is not due to the use of an assumption automaton");
          continue;
        }
        index = pStateToPos.get(getName(innerWrapped));
        if (pInitialStates.get(index) == result.get(index)) {
          if (result.get(index) instanceof AutomatonState) {
            result.set(
                index,
                automatonARGBuilderSupport.replaceStateByStateInAutomatonOfSameInstance(
                    (AutomatonState) innerWrapped));
          } else {
            result.set(index, innerWrapped);
          }
        } else {
          logger.logOnce(
              Level.WARNING,
              "Cannot identify the inner state which is more precise, use the earliest found. Combination may be unsound.");
        }
      }
    }
    return result;
  }
 @Before
 public void loadZ3() throws Exception {
   NativeLibraries.loadLibrary("z3j");
   Configuration config = Configuration.defaultConfiguration();
   LogManager logger = TestLogManager.getInstance();
   mgr = Z3FormulaManager.create(logger, config, ShutdownNotifier.create(), null, 42);
   ifmgr = (Z3IntegerFormulaManager) mgr.getIntegerFormulaManager();
 }
  @Override
  public void run() {
    int nextPartitionId;
    while (numPartitionsAcquiredForChecking.incrementAndGet() <= ioHelper.getNumPartitions()) {
      if (shutdownNotifier.shouldShutdown()) {
        abortCheckingPreparation();
      }

      if (!checkResult.get()) {
        break;
      }

      try {
        readAndUnprocessedPartitions.acquire();
      } catch (InterruptedException e) {
        abortCheckingPreparation();
        return;
      }
      nextPartitionId = nextPartition.getAndIncrement();

      if (!checkResult.get()) {
        break;
      }

      if (shutdownNotifier.shouldShutdown()) {
        abortCheckingPreparation();
      }
      checker.checkPartition(nextPartitionId);

      mutex.lock();
      try {
        checker.addCertificatePartsToCertificate(certificate);
        checker.addPartitionElements(partitionElems);
        checker.addElementsCheckedInOtherPartitions(inOtherPartition);
      } finally {
        mutex.unlock();
      }

      checkedPartitions.release();

      checker.clearAllSavedPartitioningElements();
    }
  }
  protected boolean checkCertificate(
      ReachedSet pReachedSet, ARGState pRoot, @Nullable List<ARGState> incompleteStates)
      throws CPAException, InterruptedException {
    // TODO does not account for strengthen yet (proof check will fail if strengthen is needed to
    // explain successor states)
    initChecking(pRoot);

    logger.log(Level.INFO, "Proof check algorithm started");

    ARGState initialState = (ARGState) pReachedSet.popFromWaitlist();
    Precision initialPrecision = pReachedSet.getPrecision(initialState);

    logger.log(Level.FINE, "Checking root state");

    if (!checkCovering(initialState, pRoot, initialPrecision)) {
      return false;
    }

    pReachedSet.add(pRoot, initialPrecision);

    do {

      if (!prepareNextWaitlistIteration(pReachedSet)) {
        return false;
      }

      while (pReachedSet.hasWaitingState()) {
        shutdownNotifier.shutdownIfNecessary();

        stats.increaseIteration();
        ARGState state = (ARGState) pReachedSet.popFromWaitlist();

        logger.log(Level.FINE, "Looking at state", state);

        if (!checkForStatePropertyAndOtherStateActions(state)) {
          logger.log(Level.INFO, "Property violation at state", state);
          return false;
        }

        if (state.isCovered()) {
          if (!checkCoveredStates(state, pReachedSet, initialPrecision)) {
            return false;
          }
        } else {
          if (!checkAndAddSuccessors(state, pReachedSet, initialPrecision, incompleteStates)) {
            return false;
          }
        }
      }
    } while (!isCheckComplete());

    stats.increaseProofSize(pReachedSet.size() - 1);
    return isCheckSuccessful();
  }
    private void buildBalancedOr() throws InterruptedException {
      BDD result = factory.zero(); // false

      for (BDD cube : cubes) {
        if (cube != null) {
          shutdownNotifier.shutdownIfNecessary();
          result.orWith(cube);
        }
      }
      cubes.clear();
      cubes.add(result);
      assert (cubes.size() == 1);
    }
Пример #8
0
  /**
   * builds a formula that represents the necessary variable assignments to "merge" the two ssa
   * maps. That is, for every variable X that has two different ssa indices i and j in the maps,
   * creates a new formula (X_k = X_i) | (X_k = X_j), where k is a fresh ssa index. Returns the
   * formula described above, plus a new SSAMap that is the merge of the two.
   *
   * @param ssa1 an SSAMap
   * @param pts1 the PointerTargetSet for ssa1
   * @param ssa2 an SSAMap
   * @param pts2 the PointerTargetSet for ssa1
   * @return The new SSAMap and the formulas that need to be added to the path formulas before
   *     disjuncting them.
   */
  MergeResult<SSAMap> mergeSSAMaps(
      final SSAMap ssa1,
      final PointerTargetSet pts1,
      final SSAMap ssa2,
      final PointerTargetSet pts2)
      throws InterruptedException {
    final List<MapsDifference.Entry<String, Integer>> symbolDifferences = new ArrayList<>();
    final SSAMap resultSSA = SSAMap.merge(ssa1, ssa2, collectMapsDifferenceTo(symbolDifferences));

    BooleanFormula mergeFormula1 = bfmgr.makeTrue();
    BooleanFormula mergeFormula2 = bfmgr.makeTrue();

    for (final MapsDifference.Entry<String, Integer> symbolDifference : symbolDifferences) {
      shutdownNotifier.shutdownIfNecessary();
      final String symbolName = symbolDifference.getKey();
      final CType symbolType = resultSSA.getType(symbolName);
      final int index1 = symbolDifference.getLeftValue().orElse(1);
      final int index2 = symbolDifference.getRightValue().orElse(1);

      assert symbolName != null;
      if (index1 > index2 && index1 > 1) {
        // i2:smaller, i1:bigger
        // => need correction term for i2
        BooleanFormula mergeFormula = makeSsaMerger(symbolName, symbolType, index2, index1, pts2);

        mergeFormula2 = bfmgr.and(mergeFormula2, mergeFormula);

      } else if (index2 > 1) {
        assert index1 < index2;
        // i1:smaller, i2:bigger
        // => need correction term for i1
        BooleanFormula mergeFormula = makeSsaMerger(symbolName, symbolType, index1, index2, pts1);

        mergeFormula1 = bfmgr.and(mergeFormula1, mergeFormula);
      }
    }

    return new MergeResult<>(resultSSA, mergeFormula1, mergeFormula2, bfmgr.makeTrue());
  }
Пример #9
0
  BooleanFormula addMergeAssumptions(
      final BooleanFormula pFormula,
      final SSAMap ssa1,
      final PointerTargetSet pts1,
      final SSAMap ssa2)
      throws InterruptedException {
    final List<MapsDifference.Entry<String, Integer>> symbolDifferences = new ArrayList<>();
    final SSAMap resultSSA = SSAMap.merge(ssa1, ssa2, collectMapsDifferenceTo(symbolDifferences));

    List<BooleanFormula> mergeFormula = new ArrayList<>();
    mergeFormula.add(pFormula);

    for (final MapsDifference.Entry<String, Integer> symbolDifference : symbolDifferences) {
      shutdownNotifier.shutdownIfNecessary();
      final String symbolName = symbolDifference.getKey();
      final CType symbolType = resultSSA.getType(symbolName);
      final int index1 = symbolDifference.getLeftValue().orElse(1);
      final int index2 = symbolDifference.getRightValue().orElse(1);

      assert symbolName != null;
      if (index1 > index2 && index1 > 1) {
        // assumption violated
        // ssa2 is not the merge result of ssa1 and further ssa maps
        // simplify following PCC coverage check which will likely fail anyway
        // and return coarsest overapproximation
        return bfmgr.makeTrue();

      } else if (index2 > 1) {
        assert index1 < index2;
        // i1:smaller, i2:bigger
        // => need correction term for i1
        for (int i = index1; i < index2; i++) {
          mergeFormula.add(makeSsaMerger(symbolName, symbolType, i, i + 1, pts1));
        }
      }
    }

    return bfmgr.and(mergeFormula);
  }
  private Collection<Pair<List<AbstractState>, List<ARGState>>> computeCartesianProduct(
      final List<List<ARGState>> pSuccessorsForEdge,
      final Map<String, Integer> pStateToPos,
      final List<AbstractState> pInitialStates)
      throws InterruptedException, CPAException {
    // compute number of successors
    int count = 0;
    for (List<ARGState> successor : pSuccessorsForEdge) {
      if (successor.size() > 0) {
        count = count == 0 ? successor.size() : count * successor.size();
      }
    }

    // no successor in every of the ARGs
    if (count == 0) {
      return Collections.emptySet();
    }

    Collection<Pair<List<AbstractState>, List<ARGState>>> result = new ArrayList<>(count);

    // compute cartesian product
    int[] indices = new int[pSuccessorsForEdge.size()];
    int nextIndex = 0;
    boolean restart;
    int lastSize = pSuccessorsForEdge.get(pSuccessorsForEdge.size() - 1).size();

    if (lastSize == 0) {
      lastSize = 1;
    }

    while (indices[indices.length - 1] < lastSize) {
      shutdown.shutdownIfNecessary();

      final List<ARGState> argSuccessors = new ArrayList<>(pSuccessorsForEdge.size());

      // collect ARG successors
      for (int index = 0; index < indices.length; index++) {
        if (pSuccessorsForEdge.get(index).size() > 0) {
          argSuccessors.add(
              getUncoveredSuccessor(pSuccessorsForEdge.get(index).get(indices[index])));
        }
      }

      // combine ARG states to get one cartesian product element, assume top state if no explicit
      // state information available
      result.add(
          Pair.of(combineARGStates(argSuccessors, pStateToPos, pInitialStates), argSuccessors));

      // compute indices for elements of next cartesian element
      indices[nextIndex]++;
      restart = false;
      while (indices[nextIndex] >= pSuccessorsForEdge.get(nextIndex).size()
          && nextIndex < indices.length - 1) {
        nextIndex++;
        indices[nextIndex]++;
        restart = true;
      }

      while (restart && nextIndex > 0) {
        indices[--nextIndex] = 0;
      }
    }

    return result;
  }
  private Pair<Map<String, Integer>, List<AbstractState>>
      identifyCompositeStateTypesAndTheirInitialInstances(Collection<ARGState> rootNodes)
          throws InterruptedException, CPAException {
    logger.log(Level.FINE, "Derive composite state structure of combined ARG");

    List<AbstractState> initialState = new ArrayList<>();
    Map<String, Integer> stateToPos = new HashMap<>();
    List<String> automataStateNames = new ArrayList<>();

    String name;
    int nextId = 0;
    Iterable<AbstractState> wrapped;

    logger.log(Level.FINE, "Add non-automaton states");
    for (ARGState root : rootNodes) {
      shutdown.shutdownIfNecessary();

      wrapped = getWrappedStates(root);

      for (AbstractState innerWrapped : wrapped) {
        shutdown.shutdownIfNecessary();

        if (innerWrapped instanceof AssumptionStorageState) {
          continue;
        }

        name = getName(innerWrapped);

        if (stateToPos.containsKey(name)) {
          if (!initialState.get(stateToPos.get(name)).equals(innerWrapped)) {
            logger.log(
                Level.WARNING,
                "Abstract state ",
                innerWrapped.getClass(),
                " is used by multiple configurations, but cannot check that always start in the same initial state as it is assumed");
          }
        } else {
          assert (initialState.size() == nextId);

          if (innerWrapped instanceof AutomatonState) {
            automataStateNames.add(name);
          } else {
            stateToPos.put(name, nextId);
            initialState.add(innerWrapped);
            nextId++;
          }
        }
      }
    }

    logger.log(Level.FINE, "Add automaton states related to specification");
    Collections.sort(automataStateNames);

    int numRootStates = rootNodes.size();
    Set<String> commonAutomataStates = new TreeSet<>();
    for (int i = 1, j = 0; i < automataStateNames.size(); i++) {
      assert (j < i && j >= 0);
      if (automataStateNames.get(j).equals(automataStateNames.get(i))) {
        if (j + numRootStates - 1 == i) {
          // automaton states commonly used
          commonAutomataStates.add(automataStateNames.get(j));
        }
      } else {
        j = i;
      }
    }

    // assume root is the root node of the first ARG constructed
    ARGState root = rootNodes.iterator().next();

    if (root.getWrappedState() instanceof AbstractWrapperState) {
      wrapped = ((AbstractWrapperState) root.getWrappedState()).getWrappedStates();
    } else {
      wrapped = Collections.singleton(root.getWrappedState());
    }

    for (AbstractState innerWrapped : wrapped) {
      shutdown.shutdownIfNecessary();

      name = getName(innerWrapped);
      if (commonAutomataStates.contains(name)) {
        assert (initialState.size() == nextId);

        stateToPos.put(name, nextId);
        if (!automatonARGBuilderSupport.registerAutomaton((AutomatonState) innerWrapped)) {
          logger.log(
              Level.SEVERE,
              "Property specification, given by automata specification, is ambigous.");
          throw new CPAException(
              "Ambigious property specification,  automata specification contains automata with same name or same state names");
        }
        initialState.add(
            automatonARGBuilderSupport.replaceStateByStateInAutomatonOfSameInstance(
                (AutomatonState) innerWrapped));
        nextId++;
      }
    }

    return Pair.of(stateToPos, initialState);
  }
  private boolean combineARGs(
      List<ARGState> roots,
      ForwardingReachedSet pReceivedReachedSet,
      HistoryForwardingReachedSet pForwaredReachedSet)
      throws InterruptedException, CPAException {
    Pair<Map<String, Integer>, List<AbstractState>> initStates =
        identifyCompositeStateTypesAndTheirInitialInstances(roots);

    Map<String, Integer> stateToPos = initStates.getFirst();
    List<AbstractState> initialStates = initStates.getSecond();

    try {
      pReceivedReachedSet.setDelegate(new ReachedSetFactory(config, logger).create());
    } catch (InvalidConfigurationException e) {
      logger.log(Level.SEVERE, "Creating reached set which should contain combined ARG fails.");
      return false;
    }

    shutdown.shutdownIfNecessary();

    // combined root
    ARGState combinedRoot = new ARGState(new CompositeState(initialStates), null);

    CFANode locPred;
    ARGState composedState, composedSuccessor;
    Collection<ARGState> components;

    List<List<ARGState>> successorsForEdge = new ArrayList<>(initialStates.size());
    EdgeSuccessor edgeSuccessorIdentifier = new EdgeSuccessor();

    Map<Pair<List<AbstractState>, List<ARGState>>, ARGState> constructedCombinedStates =
        Maps.newHashMap();
    Deque<Pair<List<ARGState>, ARGState>> toVisit = new ArrayDeque<>();
    toVisit.add(Pair.of(roots, combinedRoot));

    // traverse through ARGs and construct combined ARG
    // assume that states in initial states are most general, represent top state (except for
    // automaton CPAs)
    while (!toVisit.isEmpty()) {
      shutdown.shutdownIfNecessary();

      components = toVisit.peek().getFirst();
      composedState = toVisit.poll().getSecond();

      // add composed state to reached set
      pReceivedReachedSet.add(composedState, SingletonPrecision.getInstance());
      pReceivedReachedSet.removeOnlyFromWaitlist(composedState);

      // identify possible successor edges
      locPred = AbstractStates.extractLocation(composedState);
      nextEdge:
      for (CFAEdge succEdge : CFAUtils.allLeavingEdges(locPred)) {
        shutdown.shutdownIfNecessary();

        successorsForEdge.clear();
        edgeSuccessorIdentifier.setCFAEdge(succEdge);

        for (ARGState component : components) {
          // get the successors of ARG state for this edge succEdge
          edgeSuccessorIdentifier.setPredecessor(component);
          successorsForEdge.add(
              Lists.newArrayList(
                  Iterables.filter(component.getChildren(), edgeSuccessorIdentifier)));
          // check if stopped because no concrete successors exists, then do not
          if (successorsForEdge.get(successorsForEdge.size() - 1).isEmpty()
              && noConcreteSuccessorExist(component, succEdge, pForwaredReachedSet)) {
            continue nextEdge;
          }
        }

        // construct successors for each identified combination
        for (Pair<List<AbstractState>, List<ARGState>> combinedSuccessor :
            computeCartesianProduct(successorsForEdge, stateToPos, initialStates)) {
          if (constructedCombinedStates.containsKey(combinedSuccessor)) {
            // handle coverage
            constructedCombinedStates.get(combinedSuccessor).addParent(composedState);
          } else {
            // construct and register composed successor
            composedSuccessor =
                new ARGState(new CompositeState(combinedSuccessor.getFirst()), composedState);
            constructedCombinedStates.put(combinedSuccessor, composedSuccessor);

            // add successor for further exploration
            toVisit.add(Pair.of(combinedSuccessor.getSecond(), composedSuccessor));
          }
        }
      }
    }
    return true;
  }
  @Override
  public AlgorithmStatus run(ReachedSet pReachedSet)
      throws CPAException, InterruptedException, CPAEnabledAnalysisPropertyViolationException {
    checkArgument(
        pReachedSet instanceof ForwardingReachedSet,
        "PartialARGsCombiner needs ForwardingReachedSet");

    HistoryForwardingReachedSet reached = new HistoryForwardingReachedSet(pReachedSet);

    logger.log(Level.INFO, "Start inner algorithm to analyze program(s)");
    AlgorithmStatus status = AlgorithmStatus.UNSOUND_AND_PRECISE;

    stats.analysisTime.start();
    try {
      status = restartAlgorithm.run(reached);
    } finally {
      stats.analysisTime.stop();
    }

    if (status.isSound()) {
      shutdown.shutdownIfNecessary();

      logger.log(Level.INFO, "Program(s) soundly analyzed, start combining ARGs.");

      stats.argCombineTime.start();
      try {
        Collection<ReachedSet> usedReachedSets = reached.getAllReachedSetsUsedAsDelegates();

        if (usedReachedSets.size() <= 1) {
          logger.log(Level.INFO, "Only a single ARG is considered. Do not need to combine ARGs");
          if (usedReachedSets.size() == 1) {
            ((ForwardingReachedSet) pReachedSet).setDelegate(reached.getDelegate());
          }
          return status;
        }

        if (from(reached.getDelegate()).anyMatch((IS_TARGET_STATE))) {
          logger.log(Level.INFO, "Error found, do not combine ARGs.");
          ((ForwardingReachedSet) pReachedSet).setDelegate(reached.getDelegate());
          return status;
        }

        logger.log(Level.FINE, "Extract root nodes of ARGs");
        List<ARGState> rootNodes = new ArrayList<>(usedReachedSets.size());
        for (ReachedSet usedReached : usedReachedSets) {
          checkArgument(
              usedReached.getFirstState() instanceof ARGState,
              "Require that all restart configurations use ARGCPA as top level CPA.");
          checkArgument(
              AbstractStates.extractLocation(usedReached.getFirstState()) != null,
              "Require that all restart configurations consider a location aware state");

          for (AbstractState errorState : from(usedReached).filter((IS_TARGET_STATE))) {
            logger.log(
                Level.INFO,
                "Error state found in reached set ",
                usedReached,
                "but not by last configuration. Error state must be infeasible.");
            logger.log(Level.FINE, "Remove infeasible error state", errorState);
            ((ARGState) errorState).removeFromARG();
          }

          rootNodes.add((ARGState) usedReached.getFirstState());
        }

        shutdown.shutdownIfNecessary();

        if (!combineARGs(rootNodes, (ForwardingReachedSet) pReachedSet, reached)) {
          logger.log(Level.SEVERE, "Combination of ARGs failed.");
          return status.withSound(false);
        }
      } finally {
        stats.argCombineTime.stop();
      }

      logger.log(Level.INFO, "Finished combination of ARGS");

    } else {
      logger.log(
          Level.INFO,
          "Program analysis is already unsound.",
          "Do not continue with combination of unsound results");
      // set reached set to last used by restart algorithm
      if (reached.getDelegate() != pReachedSet) {
        ((ForwardingReachedSet) pReachedSet).setDelegate(reached.getDelegate());
      }
      return status.withSound(false);
    }

    return status.withSound(true);
  }
  private ARGState relocateRefinementRoot(
      final ARGState pRefinementRoot, final boolean predicatePrecisionIsAvailable)
      throws InterruptedException {

    // no relocation needed if only running value analysis,
    // because there, this does slightly degrade performance
    // when running VA+PA, merging/covering and refinements
    // of both CPAs could lead to the state, where in two
    // subsequent refinements, two identical error paths
    // were found, through different parts of the ARG
    // So now, when running VA+PA, the refinement root
    // is set to the lowest common ancestor of those states
    // that are covered by the states in the subtree of the
    // original refinement root
    if (!predicatePrecisionIsAvailable) {
      return pRefinementRoot;
    }

    // no relocation needed if restart at top
    if (restartStrategy == RestartStrategy.ROOT) {
      return pRefinementRoot;
    }

    Set<ARGState> descendants = pRefinementRoot.getSubgraph();
    Set<ARGState> coveredStates = new HashSet<>();
    shutdownNotifier.shutdownIfNecessary();
    for (ARGState descendant : descendants) {
      coveredStates.addAll(descendant.getCoveredByThis());
    }
    coveredStates.add(pRefinementRoot);

    // no relocation needed if set of descendants is closed under coverage
    if (descendants.containsAll(coveredStates)) {
      return pRefinementRoot;
    }

    Map<ARGState, ARGState> predecessorRelation = Maps.newHashMap();
    SetMultimap<ARGState, ARGState> successorRelation = LinkedHashMultimap.create();

    Deque<ARGState> todo = new ArrayDeque<>(coveredStates);
    ARGState coverageTreeRoot = null;

    // build the coverage tree, bottom-up, starting from the covered states
    while (!todo.isEmpty()) {
      shutdownNotifier.shutdownIfNecessary();
      final ARGState currentState = todo.removeFirst();

      if (currentState.getParents().iterator().hasNext()) {
        ARGState parentState = currentState.getParents().iterator().next();
        todo.add(parentState);
        predecessorRelation.put(currentState, parentState);
        successorRelation.put(parentState, currentState);

      } else if (coverageTreeRoot == null) {
        coverageTreeRoot = currentState;
      }
    }

    // starting from the root of the coverage tree,
    // the new refinement root is either the first node
    // having two or more children, or the original
    // refinement root, what ever comes first
    shutdownNotifier.shutdownIfNecessary();
    ARGState newRefinementRoot = coverageTreeRoot;
    while (successorRelation.get(newRefinementRoot).size() == 1
        && newRefinementRoot != pRefinementRoot) {
      newRefinementRoot = Iterables.getOnlyElement(successorRelation.get(newRefinementRoot));
    }

    rootRelocations.inc();
    return newRefinementRoot;
  }