org.sosy_lab.cpachecker.cpa.arg.counterexamples.CEXExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.cpa.arg.counterexamples.CEXExporter.java

Source

/*
 *  CPAchecker is a tool for configurable software verification.
 *  This file is part of CPAchecker.
 *
 *  Copyright (C) 2007-2014  Dirk Beyer
 *  All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 *  CPAchecker web page:
 *    http://cpachecker.sosy-lab.org
 */
package org.sosy_lab.cpachecker.cpa.arg.counterexamples;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.FluentIterable.from;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.sosy_lab.common.Appender;
import org.sosy_lab.common.Pair;
import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.FileOption;
import org.sosy_lab.common.configuration.InvalidConfigurationException;
import org.sosy_lab.common.configuration.Option;
import org.sosy_lab.common.configuration.Options;
import org.sosy_lab.common.io.Files;
import org.sosy_lab.common.io.Path;
import org.sosy_lab.common.io.PathTemplate;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.cpachecker.cfa.model.CFAEdge;
import org.sosy_lab.cpachecker.core.CounterexampleInfo;
import org.sosy_lab.cpachecker.core.counterexample.CFAEdgeWithAssignments;
import org.sosy_lab.cpachecker.core.counterexample.CFAMultiEdgeWithAssignments;
import org.sosy_lab.cpachecker.core.counterexample.CFAPathWithAssignments;
import org.sosy_lab.cpachecker.core.counterexample.Model;
import org.sosy_lab.cpachecker.cpa.arg.ARGPath;
import org.sosy_lab.cpachecker.cpa.arg.ARGPathExport;
import org.sosy_lab.cpachecker.cpa.arg.ARGState;
import org.sosy_lab.cpachecker.cpa.arg.ARGToDotWriter;
import org.sosy_lab.cpachecker.cpa.arg.ARGUtils;
import org.sosy_lab.cpachecker.cpa.arg.ErrorPathShrinker;
import org.sosy_lab.cpachecker.util.cwriter.PathToCTranslator;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;

@Options(prefix = "cpa.arg.errorPath")
public class CEXExporter {

    @Option(secure = true, name = "enabled", description = "export error path to file, if one is found")
    private boolean exportErrorPath = true;

    @Option(secure = true, name = "file", description = "export error path to file, if one is found")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathFile = PathTemplate.ofFormatString("ErrorPath.%d.txt");

    @Option(secure = true, name = "core", description = "export error path to file, if one is found")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathCoreFile = PathTemplate.ofFormatString("ErrorPath.%d.core.txt");

    @Option(secure = true, name = "source", description = "export error path to file, if one is found")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathSourceFile = PathTemplate.ofFormatString("ErrorPath.%d.c");

    @Option(secure = true, name = "exportAsSource", description = "translate error path to C program")
    private boolean exportSource = true;

    @Option(secure = true, name = "json", description = "export error path to file, if one is found")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathJson = PathTemplate.ofFormatString("ErrorPath.%d.json");

    @Option(secure = true, name = "assignment", description = "export one variable assignment for error path to file, if one is found")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathAssignment = PathTemplate.ofFormatString("ErrorPath.%d.assignment.txt");

    @Option(secure = true, name = "graph", description = "export error path to file, if one is found")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathGraphFile = PathTemplate.ofFormatString("ErrorPath.%d.dot");

    @Option(secure = true, name = "automaton", description = "export error path to file as an automaton")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathAutomatonFile = PathTemplate.ofFormatString("ErrorPath.%d.spc");

    @Option(secure = true, name = "graphml", description = "export error path to file as an automaton to a graphml file")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private PathTemplate errorPathAutomatonGraphmlFile = null;

    @Option(secure = true, name = "exportImmediately", description = "export error paths to files immediately after they were found")
    private boolean dumpErrorPathImmediately = false;

    private final LogManager logger;
    private final ARGPathExport witnessExporter;

    public CEXExporter(Configuration config, LogManager logger) throws InvalidConfigurationException {
        config.inject(this);
        this.logger = logger;
        this.witnessExporter = new ARGPathExport(config);

        if (!exportSource) {
            errorPathSourceFile = null;
        }
        if (errorPathAssignment == null && errorPathCoreFile == null && errorPathFile == null
                && errorPathGraphFile == null && errorPathJson == null && errorPathSourceFile == null
                && errorPathAutomatonFile == null && errorPathAutomatonGraphmlFile == null) {
            exportErrorPath = false;
        }
    }

    /**
     * Export an Error Trace in different formats, for example as C-file, dot-file or automaton.
     *
     * @param pTargetState state of an ARG, used as fallback, if pCounterexampleInfo contains no targetPath.
     * @param pCounterexampleInfo contains further information and the (optional) targetPath.
     *                            If the targetPath is available, it will be used for the output.
     *                            Otherwise we use backwards reachable states from pTargetState.
     * @param cexIndex should be a unique index for the CEX and will be used to enumerate files.
     * @param allTargetPathEdges can be used to collect edges. All targetPath-edges are added to it.
     * @param reallyWriteToDisk enable/disable output to files.
     */
    public void exportCounterexample(final ARGState pTargetState,
            @Nullable final CounterexampleInfo pCounterexampleInfo, int cexIndex,
            @Nullable final Set<Pair<ARGState, ARGState>> allTargetPathEdges, boolean reallyWriteToDisk) {
        checkNotNull(pTargetState);

        final ARGPath targetPath = checkNotNull(getTargetPath(pTargetState, pCounterexampleInfo));

        final Set<Pair<ARGState, ARGState>> targetPathEdges = getEdgesOfPath(targetPath);
        if (allTargetPathEdges != null) {
            allTargetPathEdges.addAll(targetPathEdges);
        }

        if (reallyWriteToDisk && exportErrorPath && pCounterexampleInfo != null) {
            exportCounterexample(pTargetState, cexIndex, pCounterexampleInfo, targetPath,
                    Predicates.in(targetPathEdges));
        }
    }

    private void exportCounterexample(final ARGState lastState, final int cexIndex,
            @Nullable final CounterexampleInfo counterexample, @Nonnull final ARGPath targetPath,
            final Predicate<Pair<ARGState, ARGState>> isTargetPathEdge) {

        final ARGState rootState = targetPath.getFirstState();

        writeErrorPathFile(errorPathFile, cexIndex,
                createErrorPathWithVariableAssignmentInformation(targetPath.getInnerEdges(), counterexample));

        if (errorPathCoreFile != null) {
            // the shrinked errorPath only includes the nodes,
            // that are important for the error, it is not a complete path,
            // only some nodes of the targetPath are part of it
            ErrorPathShrinker pathShrinker = new ErrorPathShrinker();
            List<CFAEdge> shrinkedErrorPath = pathShrinker.shrinkErrorPath(targetPath);
            writeErrorPathFile(errorPathCoreFile, cexIndex,
                    createErrorPathWithVariableAssignmentInformation(shrinkedErrorPath, counterexample));
        }

        writeErrorPathFile(errorPathJson, cexIndex, new Appender() {
            @Override
            public void appendTo(Appendable pAppendable) throws IOException {

                if (counterexample != null && counterexample.getTargetPathModel() != null
                        && counterexample.getTargetPathModel().getCFAPathWithAssignments() != null) {
                    counterexample.getTargetPathModel().getCFAPathWithAssignments().toJSON(pAppendable, targetPath);
                } else {
                    targetPath.toJSON(pAppendable);
                }
            }
        });

        final Set<ARGState> pathElements;
        Appender pathProgram = null;
        if (counterexample != null && counterexample.getTargetPath() != null) {
            // precise error path
            pathElements = targetPath.getStateSet();

            if (errorPathSourceFile != null) {
                pathProgram = PathToCTranslator.translateSinglePath(targetPath);
            }

        } else {
            // Imprecise error path.
            // For the text export, we have no other chance,
            // but for the C code and graph export we use all existing paths
            // to avoid this problem.
            pathElements = ARGUtils.getAllStatesOnPathsTo(lastState);

            if (errorPathSourceFile != null) {
                pathProgram = PathToCTranslator.translatePaths(rootState, pathElements);
            }
        }

        if (pathProgram != null) {
            writeErrorPathFile(errorPathSourceFile, cexIndex, pathProgram);
        }

        writeErrorPathFile(errorPathGraphFile, cexIndex, new Appender() {
            @Override
            public void appendTo(Appendable pAppendable) throws IOException {
                ARGToDotWriter.write(pAppendable, rootState, ARGUtils.CHILDREN_OF_STATE,
                        Predicates.in(pathElements), isTargetPathEdge);
            }
        });

        writeErrorPathFile(errorPathAutomatonFile, cexIndex, new Appender() {
            @Override
            public void appendTo(Appendable pAppendable) throws IOException {
                ARGUtils.producePathAutomaton(pAppendable, rootState, pathElements, "ErrorPath" + cexIndex,
                        counterexample);
            }
        });

        if (counterexample != null) {
            if (counterexample.getTargetPathModel() != null) {
                writeErrorPathFile(errorPathAssignment, cexIndex, counterexample.getTargetPathModel());
            }

            for (Pair<Object, PathTemplate> info : counterexample.getAllFurtherInformation()) {
                if (info.getSecond() != null) {
                    writeErrorPathFile(info.getSecond(), cexIndex, info.getFirst());
                }
            }
        }

        writeErrorPathFile(errorPathAutomatonGraphmlFile, cexIndex, new Appender() {
            @Override
            public void appendTo(Appendable pAppendable) throws IOException {
                witnessExporter.writePath(pAppendable, rootState, ARGUtils.CHILDREN_OF_STATE,
                        Predicates.in(pathElements), isTargetPathEdge, counterexample);
            }
        });
    }

    private Appender createErrorPathWithVariableAssignmentInformation(final List<CFAEdge> edgePath,
            final CounterexampleInfo counterexample) {
        final Model model = counterexample == null ? null : counterexample.getTargetPathModel();
        return new Appender() {
            @Override
            public void appendTo(Appendable out) throws IOException {
                // Write edges mixed with assigned values.
                CFAPathWithAssignments exactValuePath = model.getExactVariableValuePath(edgePath);

                if (exactValuePath != null) {
                    printPreciseValues(out, exactValuePath);
                } else {
                    printAllValues(out, edgePath);
                }
            }

            private void printAllValues(Appendable out, List<CFAEdge> pEdgePath) throws IOException {
                for (CFAEdge edge : from(pEdgePath).filter(notNull())) {
                    out.append(edge.toString());
                    out.append(System.lineSeparator());
                    //TODO Erase, counterexample is supposed to be independent of Assignable terms
                    for (Model.AssignableTerm term : model.getAllAssignedTerms(edge)) {
                        out.append('\t');
                        out.append(term.toString());
                        out.append(": ");
                        out.append(model.get(term).toString());
                        out.append(System.lineSeparator());
                    }
                }
            }

            private void printPreciseValues(Appendable out, CFAPathWithAssignments pExactValuePath)
                    throws IOException {

                for (CFAEdgeWithAssignments edgeWithAssignments : from(pExactValuePath).filter(notNull())) {

                    if (edgeWithAssignments instanceof CFAMultiEdgeWithAssignments) {
                        for (CFAEdgeWithAssignments singleEdge : (CFAMultiEdgeWithAssignments) edgeWithAssignments) {
                            printPreciseValues(out, singleEdge);
                        }
                    } else {
                        printPreciseValues(out, edgeWithAssignments);
                    }
                }
            }

            private void printPreciseValues(Appendable out, CFAEdgeWithAssignments edgeWithAssignments)
                    throws IOException {
                out.append(edgeWithAssignments.getCFAEdge().toString());
                out.append(System.lineSeparator());

                String cCode = edgeWithAssignments.prettyPrintCode(1);
                if (cCode != null) {
                    out.append(cCode);
                }

                String comment = edgeWithAssignments.getComment();

                if (comment != null) {
                    out.append('\t');
                    out.append(comment);
                    out.append(System.lineSeparator());
                }
            }
        };
    }

    private ARGPath getTargetPath(final ARGState targetState, @Nullable final CounterexampleInfo counterexample) {
        ARGPath targetPath = null;

        if (counterexample != null) {
            targetPath = counterexample.getTargetPath();
        }

        if (targetPath == null) {
            // try to find one
            // This is imprecise if there are several paths in the ARG,
            // because we randomly select one existing path,
            // but this path may actually be infeasible.
            targetPath = ARGUtils.getOnePathTo(targetState);
        }
        return targetPath;
    }

    private void writeErrorPathFile(PathTemplate template, int cexIndex, Object content) {
        if (template != null) {
            // fill in index in file name
            Path file = template.getPath(cexIndex);

            try {
                Files.writeFile(file, content);
            } catch (IOException e) {
                logger.logUserException(Level.WARNING, e,
                        "Could not write information about the error path to file");
            }
        }
    }

    private static Set<Pair<ARGState, ARGState>> getEdgesOfPath(ARGPath pPath) {
        Set<Pair<ARGState, ARGState>> result = Sets.newHashSetWithExpectedSize(pPath.size());
        Iterator<ARGState> it = pPath.asStatesList().iterator();
        assert it.hasNext();
        ARGState lastElement = it.next();
        while (it.hasNext()) {
            ARGState currentElement = it.next();
            result.add(Pair.of(lastElement, currentElement));
            lastElement = currentElement;
        }
        return result;
    }

    public boolean shouldDumpErrorPathImmediately() {
        return dumpErrorPathImmediately;
    }
}