Package wycs.transforms

Source Code of wycs.transforms.SmtVerificationCheck$SolverFailure

// Copyright (c) 2014, Henry J. Wylde (hjwylde@gmail.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * Neither the name of the <organization> nor the
//      names of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL DAVID J. PEARCE BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package wycs.transforms;

import static wycc.lang.SyntaxError.internalFailure;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

import wybs.lang.Builder;
import wycc.lang.Transform;
import wycc.util.Pair;
import wycs.builders.Wyal2WycsBuilder;
import wycs.core.Code;
import wycs.core.SemanticType;
import wycs.core.Value;
import wycs.core.WycsFile;
import wycs.solver.smt.Block;
import wycs.solver.smt.Logic;
import wycs.solver.smt.Option;
import wycs.solver.smt.Response;
import wycs.solver.smt.Smt2File;
import wycs.solver.smt.Sort;
import wycs.solver.smt.Stmt;

/**
* A SMT verification checker. This class will first re-write the {@link wycs.core.WycsFile} into a
* {@link wycs.solver.smt.Smt2File}. Then it will attempt to run an external SMT solver on it to
* prove its correctness. If it cannot be proved correct, then an error is thrown.
*
* @author Henry J. Wylde
*/
public final class SmtVerificationCheck implements Transform<WycsFile> {

    private static final long SMT2_TIMEOUT = 10;
    private static final TimeUnit TIMEOUT_UNIT = TimeUnit.SECONDS;

    private static final String VAR_PREFIX = "r";
    private static final String GEN_VAR_PREFIX = "g";

    private final Wyal2WycsBuilder builder;

    /**
     * Determines whether this transform is enabled or not.
     */
    private boolean enabled = getEnable();
    /**
     * Determines whether debugging is enabled or not.
     */
    private boolean debug = getDebug();
    /**
     * The external SMT solver to use for verification.
     */
    private String solver = getSolver();

    /**
     * The WycsFile we are currently applying this check to.
     */
    private WycsFile wycsFile;

    /**
     * The SMT2 file we are generating during the verification check process.
     */
    private Smt2File smt2File;
    /**
     * The current assertion block we are building.
     */
    private Block block;
    /**
     * The current extra conditions we are building. When we go into an assertion, this stack has a
     * new list pushed onto it. If any of the expressions used inside the quantifier require an
     * extra condition to be generated (e.g., for constant generation), then they may add a
     * condition into the current list and the surrounding assertion will add it in as a condition
     * before it generates itself.
     */
    private Stack<List<String>> conditions;
    /**
     * A list of assertions in the order they are written. Allows us to still use the error messages
     * by matching them to their corresponding "check-sat" statement when we verify the SMT file.
     * Each assertion is paired to an expected result, one of either {@value
     * wycs.solver.smt.Response#SAT} or {@value wycs.solver.smt.Response#UNSAT}. When verifying the
     * file, if the assertion does not match the expected result then an error is thrown.
     */
    private List<Pair<WycsFile.Assert, String>> assertions;
    /**
     * A unique generator for variable / constant names.
     */
    private int gen = 0;
    /**
     * A list of uninterpreted functions that have already had their declaration and assertion
     * statements added into the current block. This list should be cleared each time a new block is
     * created. This list is used to help prevent a {@link java.lang.StackOverflowError} in the
     * event a function is recursive.
     */
    private Set<Pair<String, Map<String, SemanticType>>> functions;

    /**
     * Creates a new {@code SmtVerificationCheck} with the given project builder.
     *
     * @param builder the builder.
     */
    public SmtVerificationCheck(Builder builder) {
        this.builder = (Wyal2WycsBuilder) builder;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void apply(WycsFile file) throws IOException {
        if (!enabled) {
            return;
        }

        // Reset the helper variables
        wycsFile = file;
        smt2File = new Smt2File();
        assertions = new ArrayList<Pair<WycsFile.Assert, String>>();
        conditions = new Stack();
        gen = 0;
        functions = new HashSet<Pair<String, Map<String, SemanticType>>>();

        // Write out the header
        writeHeader();

        // Translate the WycsFile
        for (WycsFile.Declaration declaration : file.declarations()) {
            translate(declaration);
        }

        // Write out the footer
        writeFooter();

        // Attempt to verify the generated SMT2 file
        verify(write());
    }

    /**
     * Gets the description of the debug option.
     *
     * @return the debug description.
     */
    public static String describeDebug() {
        return "Enable/disable debugging information";
    }

    /**
     * Gets the description of the enable option.
     *
     * @return the enable description.
     */
    public static String describeEnable() {
        return "Enable/disable verification";
    }

    /**
     * Gets the description of the solver option.
     *
     * @return the solver description.
     */
    public static String describeSolver() {
        return "Set the external SMT solver to use";
    }

    /**
     * Gets the default value of the debug option. The default is {@value false}.
     *
     * @return the debug default value.
     */
    public static boolean getDebug() {
        return false;
    }

    /**
     * Gets the default value of the enable option. The default is {@value true}.
     *
     * @return the enable default value.
     */
    public static boolean getEnable() {
        return false;
    }

    /**
     * Gets the default value of the solver option. The default is {@link
     * wycs.solver.smt.Solver#Z3}.
     *
     * @return the solver default value.
     */
    public static String getSolver() {
      return System.getenv("SMT_SOLVER");
    }

    /**
     * Sets the value of the debug option.
     *
     * @param flag the new debug value.
     */
    public void setDebug(boolean flag) {
        this.debug = flag;
    }

    /**
     * Sets the value of the enable option.
     *
     * @param flag the new enable value.
     */
    public void setEnable(boolean flag) {
        this.enabled = flag;
    }

    /**
     * Sets the value of the solver option. The value should be one of the available enumeration
     * values from {@link wycs.solver.smt.Solver}.
     *
     * @param solver the new solver value.
     */
    public void setSolver(String solver) {
        this.solver = solver;
    }

    /**
     * Builds up a generic binding map that can be used to instantiate generics in function or macro
     * definitions.
     *
     * @param from the from array of variable types to get the names from.
     * @param to the to array of types.
     * @return the generic binding map.
     */
    private Map<String, SemanticType> buildGenericBinding(SemanticType[] from, SemanticType[] to) {
        Map<String, SemanticType> binding = new HashMap();
        for (int i = 0; i < to.length; i++) {
            SemanticType.Var var = (SemanticType.Var) from[i];

            binding.put(var.name(), to[i]);
        }

        return binding;
    }

    /**
     * Generates a unique name for a new variable.
     *
     * @return a new unique variable name.
     */
    private String generateVariable() {
        return GEN_VAR_PREFIX + gen++;
    }

    private Sort getSort(SemanticType type) {
        if (type instanceof SemanticType.And) {
            // TODO: Implement type instanceof SemanticType.And
        } else if (type instanceof SemanticType.Any) {
            return Sort.ANY;
        } else if (type instanceof SemanticType.Bool) {
            return Sort.BOOL;
        } else if (type instanceof SemanticType.Function) {
            // TODO: Implement type instanceof SemanticType.Function
        } else if (type instanceof SemanticType.Int) {
            return Sort.INT;
        } else if (type instanceof SemanticType.Not) {
            // TODO: Implement type instanceof SemanticType.Not
        } else if (type instanceof SemanticType.Or) {
            // TODO: Implement type instanceof SemanticType.Or
        } else if (type instanceof SemanticType.Real) {
            return Sort.REAL;
        } else if (type instanceof SemanticType.Set) {
            SemanticType.Set set = (SemanticType.Set) type;

            String inner;
            // An empty set has an inner type of Void, which we can't actually translate properly
            if (set.element() instanceof SemanticType.Void) {
                // If it's an empty set, treat it as a set of Ints, that way we can still generate
                // the set functions (i.e., empty(), length(set), etc.)
                inner = Sort.INT.toString();
            } else {
                inner = translate(set.element());
            }

            return new Sort.Set(inner);
        } else if (type instanceof SemanticType.Tuple) {
            SemanticType.Tuple tuple = (SemanticType.Tuple) type;

            List<String> inners = new ArrayList();
            for (int i = 0; i < tuple.size(); i++) {
                inners.add(translate(tuple.tupleElement(i)));
            }

            return new Sort.Tuple(inners);
        } else if (type instanceof SemanticType.Var) {
            // TODO: Implement type instanceof SemanticType.Var
        } else if (type instanceof SemanticType.Void) {
            // TODO: Implement type instanceof SemanticType.Void
        }

        // Can't use internalFailure as Value isn't a syntactic element
        throw new InternalError("getSort(SemanticType) not fully implemented: " + type.getClass());
    }

    /**
     * Reads in an input stream and converts it to a {@link java.lang.String}. Uses the {@link
     * java.nio.charset.StandardCharsets#UTF_8} character set by default.
     *
     * @param in the input stream to read fully.
     * @return the string.
     * @throws IOException if the stream could not be read.
     */
    private static String readInputStream(InputStream in) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
        try {
            StringBuilder sb = new StringBuilder();

            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }

            return sb.toString();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                // Ignore
            }
        }
    }

    /**
     * Simplifies the given load code. This method first recursively simplifies the operand of the
     * load before attempting to simplify the load itself. The load may only be simplified if it has
     * an opcode of {@link Code.Op#TUPLE}, wherein the simplification is performed by looking up the
     * correct element based on the load's index field. The result of this is a complete
     * simplification of loads over known tuples.
     *
     * @param code the tuple load code to simplify.
     * @return the (if possible) simplified code.
     */
    private Code simplify(Code.Load code) {
        // Recurse into the operand first
        Code operand = simplify(code.operands[0]);

        // Simplify known tuples
        if (operand.opcode == Code.Op.TUPLE) {
            operand = operand.operands[code.index];
        }
        // We can't simplify Code.Op.CONST because that returns a Value, which doesn't subtype Code

        return simplify(operand);
    }

    /**
     * Attempts to simplify the given code. Currently the simplification will only work if the code
     * is a {@link wycs.core.Code.Load}.
     *
     * @param code the code to simplify.
     * @return the (if possible) simplified code.
     */
    private Code simplify(Code code) {
        if (code instanceof Code.Load) {
            return simplify((Code.Load) code);
        }

        return code;
    }

    private void translate(WycsFile.Declaration declaration) {
        if (declaration instanceof WycsFile.Assert) {
            translate((WycsFile.Assert) declaration);
        } else if (declaration instanceof WycsFile.Function) {
            translate((WycsFile.Function) declaration);
        } else if (declaration instanceof WycsFile.Macro) {
            translate((WycsFile.Macro) declaration);
        } else {
            internalFailure("translate(WycsFile.Declaration) not fully implemented: " + declaration
                    .getClass(), wycsFile.filename(), declaration);
        }
    }

    private void translate(WycsFile.Assert declaration) {
        // Use a new block for each assertion to remove redundant declarations after each
        // "(check-sat)"
        // Should help to prevent exponential bloating
        // Each block automatically outputs a "(push 1)" and "(pop 1)" statement when being written
        // out
        block = new Block();

        // Clear the list of defined functions
        functions.clear();

        // Push an extra conditions list onto the stack
        conditions.add(new ArrayList<String>());

        // Reset the variable name counter
        gen = 0;

        // Returns a pair, (expr, result)
        Pair<String, String> pair = translateAssertCode(declaration.condition);

        // Add the declaration and expected result to assertions, this will be used when we verify
        // the translated file
        assertions.add(new Pair(declaration, pair.second()));

        // Add the extra conditions and then the code
        // The extra conditions here aren't surrounded by a quantifier, so we must add them each as
        // assertions
        List<String> extras = conditions.pop();
        for (String extra : extras) {
            block.append(new Stmt.Assert(extra));
        }

        block.append(new Stmt.Assert(pair.first()));
        block.append(new Stmt.CheckSat());

        smt2File.append(block);

        // Sanity assignment to ensure code isn't called without a block!
        block = null;
    }

    private String translate(Code<?> code) {
        switch (code.opcode) {
            case VAR:
                return translate((Code.Variable) code);
            case CONST:
                return translate((Code.Constant) code);
            case EXISTS:
            case FORALL:
                return translate((Code.Quantifier) code);
            case FUNCALL:
                return translate((Code.FunCall) code);
            case LOAD:
                return translate((Code.Load) code);
            case LENGTH:
            case NEG:
            case NOT:
            case NULL:
                return translate((Code.Unary) code);
            case ADD:
            case SUB:
            case MUL:
            case DIV:
            case REM:
            case EQ:
            case NEQ:
            case LT:
            case LTEQ:
            case IN:
            case SUBSET:
            case SUBSETEQ:
                return translate((Code.Binary) code);
            case AND:
            case OR:
            case TUPLE:
            case SET:
                return translate((Code.Nary) code);
            default:
                internalFailure("translate(Code<?>) not fully implemented: " + code.opcode,
                        wycsFile.filename(), code);
        }

        throw new InternalError("DEADCODE");
    }

    private String translate(Code.Unary code) {
        String target = translate(code.operands[0]);
        String op = null;

        switch (code.opcode) {
            case LENGTH:
                op = "length";
                break;
            case NEG:
                op = "-";
                break;
            case NOT:
                op = "not";
                break;
            case NULL:
                // TODO: Implement code.opcode == NULL
            default:
                internalFailure("translate(Code.Unary) not fully implemented: " + code.opcode,
                        wycsFile.filename(), code);
        }

        return "(" + op + " " + target + ")";
    }

    private String translate(Code.Nary code) {
        String op = null;

        switch (code.opcode) {
            case AND:
                op = "and";
                break;
            case OR:
                op = "or";
                break;
            case TUPLE:
                return translateTuple(code);
            case SET:
                return translateSet(code);
            default:
                internalFailure("translate(Code.Nary) not fully implemented: " + code.opcode,
                        wycsFile.filename(), code);
        }

        StringBuilder sb = new StringBuilder();
        sb.append("(");
        sb.append(op);
        sb.append(" ");
        for (int i = 0; i < code.operands.length; i++) {
            sb.append(translate(code.operands[i]));

            if (i < code.operands.length - 1) {
                sb.append(" ");
            }
        }
        sb.append(")");

        return sb.toString();
    }

    private String translate(Code.Binary code) {
        String lhs = translate(code.operands[0]);
        String rhs = translate(code.operands[1]);
        String op = null;

        switch (code.opcode) {
            case ADD:
                op = "+";
                break;
            case SUB:
                op = "-";
                break;
            case MUL:
                op = "*";
                break;
            case DIV:
                op = "div";
                break;
            case REM:
                op = "rem";
                break;
            case EQ:
                op = "=";
                break;
            case NEQ:
                op = "distinct";
                break;
            case LT:
                op = "<";
                break;
            case LTEQ:
                op = "<=";
                break;
            case IN:
                op = Sort.Set.FUN_CONTAINS_NAME;

                // Swap round the lhs and rhs
                String tmp = rhs;
                rhs = lhs;
                lhs = tmp;
                break;
            case SUBSET:
                op = Sort.Set.FUN_SUBSET_NAME;
                break;
            case SUBSETEQ:
                op = Sort.Set.FUN_SUBSETEQ_NAME;
                break;
            default:
                internalFailure("translate(Code.Binary) not fully implemented: " + code.opcode,
                        wycsFile.filename(), code);
        }

        return "(" + op + " " + lhs + " " + rhs + ")";
    }

    private String translate(Code.Quantifier code) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");

        switch (code.opcode) {
            case EXISTS:
                sb.append("exists");
                break;
            case FORALL:
                sb.append("forall");
                break;
            default:
                internalFailure("translate(Code.Quantifier) not fully implemented: " + code.opcode,
                        wycsFile.filename(), code);
        }

        // Add the parameter declarations
        sb.append(" (");
        for (int i = 0; i < code.types.length; i++) {
            Pair<SemanticType, Integer> pair = code.types[i];

            sb.append("(");
            sb.append(VAR_PREFIX);
            sb.append(pair.second());
            sb.append(" ");
            sb.append(translate(pair.first()));
            sb.append(")");

            if (i < code.types.length - 1) {
                sb.append(" ");
            }
        }
        sb.append(") ");

        // An EXISTS can't use the conditions push feature, only a FORALL can, so let's bypass it
        // If we let an EXISTS add in the extra conditions that are added as an implication, it
        // would always return true as it could just turn the conditions false and the whole
        // quantifier expression would then be true
        if (code.opcode == Code.Op.EXISTS) {
            return sb.append(translate(code.operands[0])).append(")").toString();
        }

        // Push an extra conditions list onto the stack
        conditions.push(new ArrayList<String>());

        // Translate the code
        String expr = translate(code.operands[0]);

        // Add the extra conditions and then the code
        // The extra conditions are added in an implication way, i.e., if a and b are extra
        // conditions and c is the original expression, we add (a and b implies c), or
        // (or (not a) (not b) c)

        List<String> extras = conditions.pop();
        if (extras.isEmpty()) {
            return sb.append(expr).append(")").toString();
        }

        sb.append("(or ");
        for (String extra : extras) {
            sb.append("(not ");
            sb.append(extra);
            sb.append(") ");
        }

        sb.append(expr);

        sb.append("))");

        return sb.toString();
    }

    private String translate(Code.FunCall code) {
        // Working with uninterpreted functions now
        // We want to make an uninterpreted function declaration and an assertion over the function
        // i.e. abs(int x) => int y: x >= 0 ==> y > 0
        // Has the declaration: abs(int) => int
        // and the assertion: forall (r1 int) . r1 >= 0 ==> abs(r1) > 0
        // (note: functions have the return value numbered as 0, so the parameters start from 1)

        WycsFile module = null;
        try {
            module = builder.getModule(code.nid.module());
        } catch (Exception e) {
            internalFailure(e.getMessage(), wycsFile.filename(), code, e);
        }

        if (module == null) {
            throw new InternalError("module '" + code.nid.module() + "' not found");
        }

        WycsFile.Declaration decl = module.declaration(code.nid.name());

        if (decl == null) {
            throw new InternalError(
                    "function call to a non-declared function (name=" + code.nid.name() + ")");
        }
        if (!(decl instanceof WycsFile.Function)) {
            internalFailure("function call to a non-function declaration", wycsFile.filename(),
                    decl);
        }

        String id = code.nid.module() + "_" + code.nid.name();

        // Get the function declaration and instantiate its generic types
        WycsFile.Function function = (WycsFile.Function) decl;
        Map<String, SemanticType> generics = buildGenericBinding(function.type.generics(),
                code.type.generics());
        SemanticType.Function type = (SemanticType.Function) function.type.substitute(generics);

        // Recursion potential here! Skip if we've already generated the declaration and assertion

        // A function can be uniquely identified by it's identifier and generic binding
        if (!functions.contains(new Pair<String, Map<String, SemanticType>>(id, generics))) {
            functions.add(new Pair<String, Map<String, SemanticType>>(id, generics));

            // Generate the uninterpreted function declaration

            List<String> parameters = new ArrayList<String>();
            parameters.add(translate(type.from()));
            String returnSort = translate(type.to());

            block.append(new Stmt.DeclareFun(id, parameters, returnSort));

            // Generate the uninterpreted function assertion
            if (function.constraint != null) {
                // Create a binding that replaces the named return variable with the function call
                Map<Integer, Code> binding = new HashMap();
                binding.put(0, Code.FunCall(type, Code.Variable(type.from(), 1), code.nid));

                // Substitute and instantiate to get the new operand for the assertion
                Code operand = function.constraint.substitute(binding).instantiate(generics);
                Pair<SemanticType, Integer>[] types = new Pair[] {new Pair(type.from(), 1)};
                // TODO: What type should a quantifier have?
                Code assertion = Code.Quantifier(SemanticType.Bool, Code.Op.FORALL, operand, types);

                block.append(new Stmt.Assert(translate(assertion)));
            }
        }

        return "(" + id + " " + translate(code.operands[0]) + ")";
    }

    private String translate(Code.Load code) {
        Code operand = simplify(code.operands[0]);

        // Check to see if we can simplify this expression
        if (operand.opcode == Code.Op.CONST) {
            Value.Tuple tuple = (Value.Tuple) ((Code.Constant) operand).value;

            return translate(tuple.values.get(code.index));
        } else if (operand.opcode == Code.Op.TUPLE) {
            return translate(operand.operands[code.index]);
        }

        // Guess not, let's just return the translated load

        return "(" + Sort.Tuple.generateGetFunctionName(code.index) + " " + translate(
                code.operands[0]) + ")";
    }

    private String translate(Code.Constant code) {
        return translate(code.value);
    }

    private String translate(Code.Variable code) {
        return VAR_PREFIX + code.index;
    }

    private void translate(WycsFile.Function declaration) {
        // Ignore, functions can be generic, so we must generate the code for them at the time of
        // the function call
    }

    private void translate(WycsFile.Macro declaration) {
        // Ignore, macro expansion should have handled this
    }

    private String translate(Value value) {
        if (value instanceof Value.Bool) {
            return ((Value.Bool) value).value ? "true" : "false";
        } else if (value instanceof Value.Decimal) {
            return ((Value.Decimal) value).value.toString();
        } else if (value instanceof Value.Integer) {
            return ((Value.Integer) value).value.toString();
        } else if (value instanceof Value.Set) {
            return translate((Value.Set) value);
        } else if (value instanceof Value.Tuple) {
            return translate((Value.Tuple) value);
        }

        // Can't use internalFailure as Value isn't a syntactic element
        throw new InternalError("translate(Value) not fully implemented: " + value.getClass());
    }

    private String translate(Value.Set value) {
        // Trigger the addition of the set functions
        translate(value.type());

        Sort.Set sort = (Sort.Set) getSort(value.type());

        // Create the in-lined constant expression
        String expr = sort.getEmptyNameAsQualified();
        for (Value inner : value.values) {
            expr = "(" + Sort.Set.FUN_ADD_NAME + " " + expr + " " + translate(inner) + ")";
        }

        return expr;
    }

    private String translate(Value.Tuple value) {
        // Trigger the addition of the tuple functions
        translate(value.type());

        String var = generateVariable();

        // Add the constant declaration
        block.append(new Stmt.DeclareFun(var, Collections.EMPTY_LIST, translate(value.type())));

        // Create the extra conditions to assert the value of the tuple
        for (int i = 0; i < value.values.size(); i++) {
            String extra = "(= (" + Sort.Tuple.generateGetFunctionName(i) + " " + var + ") "
                    + translate(value.values.get(i)) + ")";

            conditions.peek().add(extra);
        }

        return var;
    }

    private String translate(SemanticType type) {
        Sort sort = getSort(type);

        // Generate some initialisation statements for the sort and relevant functions
        block.append(sort.generateInitialisers());

        return sort.toString();
    }

    /**
     * Translates the given code, knowing that it will be used in a {@link
     * wycs.solver.smt.Stmt.Assert}. This method will attempt to optimise the translation if the
     * code is a quantifier.
     * <p/>
     * An EXISTS optimisation is performed by skolemization and simply asserting the condition with
     * this new generated variable.
     * <p/>
     * A FORALL optimisation is performed by splitting up the operand and adding the premises and
     * negated goal as assertions. If this optimisation is performed correctly, the verifier should
     * check for an {@value wycs.solver.smt.Response#UNSAT} instead of a {@value
     * wycs.solver.smt.Response#SAT}. This expectation is sent back to the caller in the return
     * value.
     * <p/>
     * Returns a pair of strings. The first element is the final expression to add as an assertion,
     * the second element is the expected result, one of {@value wycs.solver.smt.Response#SAT} or
     * {@value wycs.solver.smt.Response#UNSAT}.
     *
     * @param code the assertion code to translate.
     * @return the expression and expected result as a pair.
     */
    private Pair<String, String> translateAssertCode(Code<?> code) {
        // Can we do the optimisation?
        if (!(code instanceof Code.Quantifier)) {
            // Guess not, let's negate it and check for UNSAT anyway as UNSAT is easier for a solver
            // to determine (and lets our length function work properly)
            return new Pair<String, String>("(not " + translate(code) + ")", Response.UNSAT);
        }

        Code.Quantifier quantifier = (Code.Quantifier) code;
        Code<?> operand = code.operands[0];

        // Let's introduce some constants for the quantifier variables

        for (int i = 0; i < quantifier.types.length; i++) {
            Pair<SemanticType, Integer> pair = quantifier.types[i];

            block.append(new Stmt.DeclareFun(VAR_PREFIX + pair.second(), Collections.EMPTY_LIST,
                    translate(pair.first())));
        }

        // Check if the opcode is an EXISTS, if it is, then we translate it as follows:
        // exists x : a and b and c
        // Translates to:
        // declare-sort x
        // assert a and b and c
        if (code.opcode == Code.Op.EXISTS) {
            // Potential limitation here, if we're checking for SAT, then our length function may
            // not work properly. Ideally, we'd check for UNSAT but I don't know how to do that in
            // an EXISTS. Overall, for a Whiley file it shouldn't matter as they're all translated
            // to a FORALL, but the limitation still exists for other potential uses of WyAL and
            // WyCS
            return new Pair<String, String>(translate(operand), Response.SAT);
        }

        // Opcode must be a FORALL

        // We treat the operand as if it is an OR, as that is how an implication is translated
        // (i.e., a and b => c === !a or !b or c)

        // We translate it as follows:
        // forall x : !a or !b or c
        // Translates to:
        // declare-sort x
        // assert a
        // assert b
        // assert !c

        // If it's unsatisfiable then we know it's true

        // If it doesn't happen to be an OR, then we can still translate it treating it as if it
        // were an or of a single term
        if (operand.opcode != Code.Op.OR) {
            return new Pair<String, String>("(not " + translate(operand) + ")", Response.UNSAT);
        }

        // Looks like the FORALL is over an OR (or implication), got to add the negated premises

        for (int i = 0; i < operand.operands.length - 1; i++) {
            // The implication will have already negated the arguments, we need to change them back
            block.append(new Stmt.Assert("(not " + translate(operand.operands[i]) + ")"));
        }

        // Translate the final expression and negate it
        String expr = translate(operand.operands[operand.operands.length - 1]);
        expr = "(not " + expr + ")";

        return new Pair<String, String>(expr, Response.UNSAT);
    }

    private String translateSet(Code.Nary code) {
        // Trigger the addition of the set functions
        translate(code.returnType());

        Sort.Set sort = (Sort.Set) getSort(code.returnType());

        // Create the in-lined constant expression
        String expr = sort.getEmptyNameAsQualified();
        for (Code inner : code.operands) {
            expr = "(" + Sort.Set.FUN_ADD_NAME + " " + expr + " " + translate(inner) + ")";
        }

        return expr;
    }

    private String translateTuple(Code.Nary code) {
        // Trigger the addition of the tuple functions
        translate(code.returnType());

        String var = generateVariable();

        // Add the constant declaration
        block.append(new Stmt.DeclareFun(var, Collections.EMPTY_LIST, translate(
                code.returnType())));

        // Create the extra conditions to assert the value of the tuple
        for (int i = 0; i < code.operands.length; i++) {
            String extra = "(= (" + Sort.Tuple.generateGetFunctionName(i) + " " + var + ") "
                    + translate(code.operands[i]) + ")";

            conditions.peek().add(extra);
        }

        return var;
    }

    /**
     * Runs a solver on the given file and checks that all of the assertions passed. If any
     * assertion failed, then an appropriate error is thrown.
     *
     * @param file the file to verify.
     * @throws IOException if the file could not be read.
     */
    private void verify(File file) throws IOException {
        // Create the process to call the solver
        List<String> args = new ArrayList();
        String solver = getSolver();
        if(solver == null) {
          throw new InternalError("Environment variable $SMT_SOLVER not set");
        }
        args.add(getSolver());
        // Add the solvers custom arguments
        args.add(file.getAbsolutePath());
        ProcessBuilder pb = new ProcessBuilder(args);
        final Process process = pb.start();

        List<String> output = null;
        Timer timer = new Timer();
        try {
            // TODO: Fix this up and don't use a timer, two exceptions are being thrown with this implementation
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    process.destroy();
                    throw new SolverFailure(
                            "solver timed out after " + SMT2_TIMEOUT + " " + TIMEOUT_UNIT
                                    .toString());
                }
            }, TimeUnit.MILLISECONDS.convert(SMT2_TIMEOUT, TIMEOUT_UNIT));

            process.waitFor();
            timer.cancel();

            if (process.exitValue() != 0) {
                throw new SolverFailure(
                        "verification exited with non-zero value: " + readInputStream(
                                process.getInputStream()));
            }

            // Read all of the standard output from the process
            output = Arrays.asList(readInputStream(process.getInputStream()).split("\n"));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // Make sure the process is finished
            process.destroy();
        }

        if (output == null) {
            throw new RuntimeException("verification did not complete");
        }

        // A counter for what assertion we're up to
        // Each "(check-sat)" command in the file corresponds to an assertion in the #assertions
        // field
        int index = 0;
        for (String line : output) {
            if (line.isEmpty()) {
                continue;
            }

            // Get the current assertion and expected result
            Pair<WycsFile.Assert, String> pair = assertions.get(index);
            WycsFile.Assert assertion = pair.first();
            String expectedResult = pair.second();

            if (line.equals(expectedResult)) {
                // Assertion was valid, move to the next assertion
                index++;
            } else if (line.equals(Response.UNKNOWN)) {
                throw new SolverFailure("solver returned unknown");
            } else if (line.equals(Response.SAT) || line.equals(Response.UNSAT)) {
                // Assertion was invalid, create an appropriate error
                if (assertion.message == null) {
                    throw new AssertionFailure(assertion);
                } else {
                    throw new AssertionFailure(assertion.message, assertion);
                }
            } else if (line.equals(Response.UNSUPPORTED)) {
                // A set-option was unsupported, skip this line
            } else if (line.startsWith("(error")) {
                // Internal error occurred that shouldn't have, unwrap the error message
                String error = line.substring(7, line.length() - 1);
                throw new SolverFailure(error);
            } else {
                throw new RuntimeException(line);
            }
        }
    }

    /**
     * Writes out the {@link #smt2File} to a temporary file and returns it. This method also adds a
     * trap onto the file to ensure it is deleted upon application exit.
     *
     * @return the written temporary file.
     * @throws IOException if the file could not be written.
     */
    private File write() throws IOException {
        // Prepare the output destination
      // FIXME: the following is a bit of a hack and needs to be fixed!
        File out = new File(wycsFile.filename().replace(".whiley",".smt2").replace(".wyal",".smt2"));
        out.deleteOnExit();

        FileOutputStream fos = null;
        try {
            // Create the stream and write out the contents to it
            fos = new FileOutputStream(out);
            fos.write(smt2File.toString().getBytes("UTF-8"));
            fos.flush();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        return out;
    }

    /**
     * Writes out the footer to the {@link #smt2File}.
     */
    private void writeFooter() {
        smt2File.append(new Stmt.Exit());
    }

    /**
     * Writes out the header to the {@link #smt2File}. The header includes options to set and the
     * logic to use.
     */
    private void writeHeader() {
        // Don't print "success" for each command
        smt2File.append(new Stmt.SetOption(Option.PRINT_SUCCESS, "false"));

        // Append the logic
        smt2File.append(new Stmt.SetLogic(Logic.AUFNIRA));
    }

    /**
     * Represents a failure that occurred in an assertion, i.e., an assertion was found to be either
     * {@value wycs.solver.smt.Response#UNSAT} or {@value wycs.solver.smt.Response#UNKNOWN}.
     * Appropriate information is provided to give a detailed error message to the user about the
     * particular assertion that failed.
     *
     * @author Henry J. Wylde
     */
    public static class AssertionFailure extends RuntimeException {

        private WycsFile.Assert assertion;

        /**
         * Creates a new {@code AssertionFailure} with the given error message and assertion.
         *
         * @param message the error message.
         * @param assertion the failed assertion.
         */
        public AssertionFailure(String message, WycsFile.Assert assertion) {
            super(message);

            if (assertion == null) {
                throw new NullPointerException("assertion cannot be null");
            }

            this.assertion = assertion;
        }

        /**
         * Creates a new {@code AssertionFailure} with the given assertion.
         *
         * @param assertion the failed assertion.
         */
        public AssertionFailure(WycsFile.Assert assertion) {
            this("assertion failure", assertion);
        }

        /**
         * Gets the assertion that caused this failure.
         *
         * @return the assertion.
         */
        public WycsFile.Assert getAssertion() {
            return assertion;
        }
    }

    /**
     * Represents a failure that occurred in the SMT solver. Failures in the SMT solver are {@link
     * java.lang.InternalError}s as they should never occur.
     *
     * @author Henry J. Wylde
     */
    public static class SolverFailure extends InternalError {

        /**
         * Creates a new {@code SolverFailure} with the given error message.
         *
         * @param message the error message.
         */
        public SolverFailure(String message) {
            super(message);
        }
    }
}
TOP

Related Classes of wycs.transforms.SmtVerificationCheck$SolverFailure

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
script>