org.sosy_lab.java_smt.solvers.z3.Z3FormulaCreator.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.java_smt.solvers.z3.Z3FormulaCreator.java

Source

/*
 *  JavaSMT is an API wrapper for a collection of SMT solvers.
 *  This file is part of JavaSMT.
 *
 *  Copyright (C) 2007-2016  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.
 */
package org.sosy_lab.java_smt.solvers.z3;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.common.primitives.Longs;
import com.microsoft.z3.Native;
import com.microsoft.z3.Z3Exception;
import com.microsoft.z3.enumerations.Z3_ast_kind;
import com.microsoft.z3.enumerations.Z3_decl_kind;
import com.microsoft.z3.enumerations.Z3_sort_kind;
import com.microsoft.z3.enumerations.Z3_symbol_kind;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.sosy_lab.common.ShutdownNotifier;
import org.sosy_lab.common.configuration.Configuration;
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.rationals.Rational;
import org.sosy_lab.common.time.Timer;
import org.sosy_lab.java_smt.api.ArrayFormula;
import org.sosy_lab.java_smt.api.BitvectorFormula;
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.FloatingPointFormula;
import org.sosy_lab.java_smt.api.FloatingPointRoundingMode;
import org.sosy_lab.java_smt.api.Formula;
import org.sosy_lab.java_smt.api.FormulaType;
import org.sosy_lab.java_smt.api.FormulaType.ArrayFormulaType;
import org.sosy_lab.java_smt.api.FunctionDeclarationKind;
import org.sosy_lab.java_smt.api.QuantifiedFormulaManager.Quantifier;
import org.sosy_lab.java_smt.api.SolverException;
import org.sosy_lab.java_smt.api.visitors.FormulaVisitor;
import org.sosy_lab.java_smt.basicimpl.FormulaCreator;
import org.sosy_lab.java_smt.basicimpl.FunctionDeclarationImpl;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3ArrayFormula;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3BitvectorFormula;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3BooleanFormula;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3FloatingPointFormula;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3FloatingPointRoundingModeFormula;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3IntegerFormula;
import org.sosy_lab.java_smt.solvers.z3.Z3Formula.Z3RationalFormula;

@Options(prefix = "solver.z3")
class Z3FormulaCreator extends FormulaCreator<Long, Long, Long, Long> {

    private static final Map<Integer, Object> Z3_CONSTANTS = ImmutableMap.<Integer, Object>builder()
            .put(Z3_decl_kind.Z3_OP_TRUE.toInt(), true).put(Z3_decl_kind.Z3_OP_FALSE.toInt(), false)
            .put(Z3_decl_kind.Z3_OP_FPA_PLUS_ZERO.toInt(), +0.0)
            .put(Z3_decl_kind.Z3_OP_FPA_MINUS_ZERO.toInt(), -0.0)
            .put(Z3_decl_kind.Z3_OP_FPA_PLUS_INF.toInt(), Double.POSITIVE_INFINITY)
            .put(Z3_decl_kind.Z3_OP_FPA_MINUS_INF.toInt(), Double.NEGATIVE_INFINITY)
            .put(Z3_decl_kind.Z3_OP_FPA_NAN.toInt(), Double.NaN)
            .put(Z3_decl_kind.Z3_OP_FPA_RM_NEAREST_TIES_TO_EVEN.toInt(),
                    FloatingPointRoundingMode.NEAREST_TIES_TO_EVEN)
            .put(Z3_decl_kind.Z3_OP_FPA_RM_NEAREST_TIES_TO_AWAY.toInt(),
                    FloatingPointRoundingMode.NEAREST_TIES_AWAY)
            .put(Z3_decl_kind.Z3_OP_FPA_RM_TOWARD_POSITIVE.toInt(), FloatingPointRoundingMode.TOWARD_POSITIVE)
            .put(Z3_decl_kind.Z3_OP_FPA_RM_TOWARD_NEGATIVE.toInt(), FloatingPointRoundingMode.TOWARD_NEGATIVE)
            .put(Z3_decl_kind.Z3_OP_FPA_RM_TOWARD_ZERO.toInt(), FloatingPointRoundingMode.TOWARD_ZERO).build();

    // Set of error messages that might occur if Z3 is interrupted.
    private static final ImmutableSet<String> Z3_INTERRUPT_ERRORS = ImmutableSet.of("canceled",
            // These occur on interrupts during interpolation
            "interpolation cannot proceed without a model", // cf. Z3 commit 654780b
            "Proof error!");

    @Option(secure = true, description = "Whether to use PhantomReferences for discarding Z3 AST")
    private boolean usePhantomReferences = false;

    private final Table<Long, Long, Long> allocatedArraySorts = HashBasedTable.create();

    /** Automatic clean-up of Z3 ASTs. */
    private final ReferenceQueue<Z3Formula> referenceQueue = new ReferenceQueue<>();

    private final Map<PhantomReference<? extends Z3Formula>, Long> referenceMap = Maps.newIdentityHashMap();

    // todo: getters for statistic.
    private final Timer cleanupTimer = new Timer();
    protected final ShutdownNotifier shutdownNotifier;

    Z3FormulaCreator(long pEnv, long pBoolType, long pIntegerType, long pRealType, Configuration config,
            ShutdownNotifier pShutdownNotifier) throws InvalidConfigurationException {
        super(pEnv, pBoolType, pIntegerType, pRealType);
        shutdownNotifier = pShutdownNotifier;
        config.inject(this);
    }

    final Z3Exception handleZ3Exception(Z3Exception e) throws Z3Exception, InterruptedException {
        if (Z3_INTERRUPT_ERRORS.contains(e.getMessage())) {
            shutdownNotifier.shutdownIfNecessary();
        }
        throw e;
    }

    @Override
    public Long makeVariable(Long type, String varName) {
        long z3context = getEnv();
        long symbol = Native.mkStringSymbol(z3context, varName);
        return Native.mkConst(z3context, symbol, type);
    }

    @Override
    public Long extractInfo(Formula pT) {
        return Z3FormulaManager.getZ3Expr(pT);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Formula> FormulaType<T> getFormulaType(T pFormula) {
        Long term = extractInfo(pFormula);
        return (FormulaType<T>) getFormulaType(term);
    }

    public FormulaType<?> getFormulaTypeFromSort(Long pSort) {
        long z3context = getEnv();
        Z3_sort_kind sortKind = Z3_sort_kind.fromInt(Native.getSortKind(z3context, pSort));
        switch (sortKind) {
        case Z3_BOOL_SORT:
            return FormulaType.BooleanType;
        case Z3_INT_SORT:
            return FormulaType.IntegerType;
        case Z3_REAL_SORT:
            return FormulaType.RationalType;
        case Z3_BV_SORT:
            return FormulaType.getBitvectorTypeWithSize(Native.getBvSortSize(z3context, pSort));
        case Z3_ARRAY_SORT:
            long domainSort = Native.getArraySortDomain(z3context, pSort);
            long rangeSort = Native.getArraySortRange(z3context, pSort);
            return FormulaType.getArrayType(getFormulaTypeFromSort(domainSort), getFormulaTypeFromSort(rangeSort));
        case Z3_FLOATING_POINT_SORT:
            return FormulaType.getFloatingPointType(Native.fpaGetEbits(z3context, pSort),
                    Native.fpaGetSbits(z3context, pSort));
        case Z3_ROUNDING_MODE_SORT:
            return FormulaType.FloatingPointRoundingModeType;
        case Z3_DATATYPE_SORT:
        case Z3_RELATION_SORT:
        case Z3_FINITE_DOMAIN_SORT:
        case Z3_SEQ_SORT:
        case Z3_RE_SORT:
        case Z3_UNKNOWN_SORT:
        case Z3_UNINTERPRETED_SORT:
            // TODO: support for remaining sorts.
            throw new IllegalArgumentException(
                    "Unknown formula type " + Native.sortToString(z3context, pSort) + " with sort " + sortKind);
        default:
            throw new UnsupportedOperationException("Unexpected state.");
        }
    }

    @Override
    public FormulaType<?> getFormulaType(Long pFormula) {
        long sort = Native.getSort(getEnv(), pFormula);
        return getFormulaTypeFromSort(sort);
    }

    @Override
    protected <TD extends Formula, TR extends Formula> FormulaType<TR> getArrayFormulaElementType(
            ArrayFormula<TD, TR> pArray) {
        return ((Z3ArrayFormula<TD, TR>) pArray).getElementType();
    }

    @Override
    protected <TD extends Formula, TR extends Formula> FormulaType<TD> getArrayFormulaIndexType(
            ArrayFormula<TD, TR> pArray) {
        return ((Z3ArrayFormula<TD, TR>) pArray).getIndexType();
    }

    @Override
    protected <TD extends Formula, TR extends Formula> ArrayFormula<TD, TR> encapsulateArray(Long pTerm,
            FormulaType<TD> pIndexType, FormulaType<TR> pElementType) {
        assert getFormulaType(pTerm).equals(FormulaType.getArrayType(pIndexType, pElementType));
        cleanupReferences();
        return storePhantomReference(new Z3ArrayFormula<>(getEnv(), pTerm, pIndexType, pElementType), pTerm);
    }

    private <T extends Z3Formula> T storePhantomReference(T out, Long pTerm) {
        if (usePhantomReferences) {
            PhantomReference<T> ref = new PhantomReference<>(out, referenceQueue);
            referenceMap.put(ref, pTerm);
        }
        return out;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Formula> T encapsulate(FormulaType<T> pType, Long pTerm) {
        assert pType.equals(getFormulaType(pTerm)) || (pType.equals(FormulaType.RationalType)
                && getFormulaType(pTerm).equals(FormulaType.IntegerType)) : String
                        .format("Trying to encapsulate formula of type %s as %s", getFormulaType(pTerm), pType);
        cleanupReferences();
        if (pType.isBooleanType()) {
            return (T) storePhantomReference(new Z3BooleanFormula(getEnv(), pTerm), pTerm);
        } else if (pType.isIntegerType()) {
            return (T) storePhantomReference(new Z3IntegerFormula(getEnv(), pTerm), pTerm);
        } else if (pType.isRationalType()) {
            return (T) storePhantomReference(new Z3RationalFormula(getEnv(), pTerm), pTerm);
        } else if (pType.isBitvectorType()) {
            return (T) storePhantomReference(new Z3BitvectorFormula(getEnv(), pTerm), pTerm);
        } else if (pType.isFloatingPointType()) {
            return (T) storePhantomReference(new Z3FloatingPointFormula(getEnv(), pTerm), pTerm);
        } else if (pType.isFloatingPointRoundingModeType()) {
            return (T) storePhantomReference(new Z3FloatingPointRoundingModeFormula(getEnv(), pTerm), pTerm);
        } else if (pType.isArrayType()) {
            ArrayFormulaType<?, ?> arrFt = (ArrayFormulaType<?, ?>) pType;
            return (T) storePhantomReference(
                    new Z3ArrayFormula<>(getEnv(), pTerm, arrFt.getIndexType(), arrFt.getElementType()), pTerm);
        }

        throw new IllegalArgumentException("Cannot create formulas of type " + pType + " in Z3");
    }

    @Override
    public BooleanFormula encapsulateBoolean(Long pTerm) {
        assert getFormulaType(pTerm).isBooleanType();
        cleanupReferences();
        return storePhantomReference(new Z3BooleanFormula(getEnv(), pTerm), pTerm);
    }

    @Override
    public BitvectorFormula encapsulateBitvector(Long pTerm) {
        assert getFormulaType(pTerm).isBitvectorType();
        cleanupReferences();
        return storePhantomReference(new Z3BitvectorFormula(getEnv(), pTerm), pTerm);
    }

    @Override
    protected FloatingPointFormula encapsulateFloatingPoint(Long pTerm) {
        assert getFormulaType(pTerm).isFloatingPointType();
        cleanupReferences();
        return storePhantomReference(new Z3FloatingPointFormula(getEnv(), pTerm), pTerm);
    }

    @Override
    public Long getArrayType(Long pIndexType, Long pElementType) {
        Long allocatedArraySort = allocatedArraySorts.get(pIndexType, pElementType);
        if (allocatedArraySort == null) {
            allocatedArraySort = Native.mkArraySort(getEnv(), pIndexType, pElementType);
            Native.incRef(getEnv(), allocatedArraySort);
            allocatedArraySorts.put(pIndexType, pElementType, allocatedArraySort);
        }
        return allocatedArraySort;
    }

    @Override
    public Long getBitvectorType(int pBitwidth) {
        checkArgument(pBitwidth > 0, "Cannot use bitvector type with size %s", pBitwidth);
        long bvSort = Native.mkBvSort(getEnv(), pBitwidth);
        Native.incRef(getEnv(), Native.sortToAst(getEnv(), bvSort));
        return bvSort;
    }

    @Override
    public Long getFloatingPointType(FormulaType.FloatingPointType type) {
        long fpSort = Native.mkFpaSort(getEnv(), type.getExponentSize(), type.getMantissaSize());
        Native.incRef(getEnv(), Native.sortToAst(getEnv(), fpSort));
        return fpSort;
    }

    private void cleanupReferences() {
        if (!usePhantomReferences) {
            return;
        }
        cleanupTimer.start();
        try {
            Reference<? extends Z3Formula> ref;
            while ((ref = referenceQueue.poll()) != null) {
                long z3ast = referenceMap.remove(ref);
                Native.decRef(environment, z3ast);
            }
        } finally {
            cleanupTimer.stop();
        }
    }

    private String getAppName(long f) {
        long funcDecl = Native.getAppDecl(environment, f);
        long symbol = Native.getDeclName(environment, funcDecl);
        return symbolToString(symbol);
    }

    @Override
    public <R> R visit(FormulaVisitor<R> visitor, final Formula formula, final Long f) {
        switch (Z3_ast_kind.fromInt(Native.getAstKind(environment, f))) {
        case Z3_NUMERAL_AST:
            return visitor.visitConstant(formula, convertValue(f));
        case Z3_APP_AST:
            int arity = Native.getAppNumArgs(environment, f);

            if (arity == 0) {

                // constants
                int declKind = Native.getDeclKind(environment, Native.getAppDecl(environment, f));
                Object value = Z3_CONSTANTS.get(declKind);
                if (value != null) {
                    return visitor.visitConstant(formula, value);

                } else if (declKind == Z3_decl_kind.Z3_OP_FPA_NUM.toInt() || Native.getSortKind(environment,
                        Native.getSort(environment, f)) == Z3_sort_kind.Z3_ROUNDING_MODE_SORT.toInt()) {
                    return visitor.visitConstant(formula, convertValue(f));

                } else {

                    // Has to be a variable otherwise.
                    // TODO: assert that.
                    return visitor.visitFreeVariable(formula, getAppName(f));
                }
            }

            ImmutableList.Builder<Formula> args = ImmutableList.builder();
            ImmutableList.Builder<FormulaType<?>> argTypes = ImmutableList.builder();
            for (int i = 0; i < arity; i++) {
                long arg = Native.getAppArg(environment, f, i);
                FormulaType<?> argumentType = getFormulaType(arg);
                args.add(encapsulate(argumentType, arg));
                argTypes.add(argumentType);
            }
            return visitor.visitFunction(formula, args.build(), FunctionDeclarationImpl.of(getAppName(f),
                    getDeclarationKind(f), argTypes.build(), getFormulaType(f), Native.getAppDecl(environment, f)));
        case Z3_VAR_AST:
            int deBruijnIdx = Native.getIndexValue(environment, f);
            return visitor.visitBoundVariable(formula, deBruijnIdx);
        case Z3_QUANTIFIER_AST:
            BooleanFormula body = encapsulateBoolean(Native.getQuantifierBody(environment, f));
            Quantifier q = Native.isQuantifierForall(environment, f) ? Quantifier.FORALL : Quantifier.EXISTS;
            return visitor.visitQuantifier((BooleanFormula) formula, q, getBoundVars(f), body);

        case Z3_SORT_AST:
        case Z3_FUNC_DECL_AST:
        case Z3_UNKNOWN_AST:
        default:
            throw new UnsupportedOperationException(
                    "Input should be a formula AST, " + "got unexpected type instead");
        }
    }

    protected String symbolToString(long symbol) {
        switch (Z3_symbol_kind.fromInt(Native.getSymbolKind(environment, symbol))) {
        case Z3_STRING_SYMBOL:
            return Native.getSymbolString(environment, symbol);
        case Z3_INT_SYMBOL:

            // Bound variable.
            return "#" + Native.getSymbolInt(environment, symbol);
        default:
            throw new UnsupportedOperationException("Unexpected state");
        }
    }

    private List<Formula> getBoundVars(long f) {
        int numBound = Native.getQuantifierNumBound(environment, f);
        List<Formula> boundVars = new ArrayList<>(numBound);
        for (int i = 0; i < numBound; i++) {
            long varName = Native.getQuantifierBoundName(environment, f, i);
            long varSort = Native.getQuantifierBoundSort(environment, f, i);
            boundVars.add(
                    encapsulate(getFormulaTypeFromSort(varSort), Native.mkConst(environment, varName, varSort)));
        }
        return boundVars;
    }

    private FunctionDeclarationKind getDeclarationKind(long f) {
        assert Native.getArity(environment,
                Native.getAppDecl(environment, f)) > 0 : "Variables should be handled in other branch.";
        if (getAppName(f).equals("div0")) {
            // Z3 segfaults in getDeclKind for this term (cf. https://github.com/Z3Prover/z3/issues/669)
            return FunctionDeclarationKind.OTHER;
        }
        Z3_decl_kind decl = Z3_decl_kind
                .fromInt(Native.getDeclKind(environment, Native.getAppDecl(environment, f)));
        switch (decl) {
        case Z3_OP_AND:
            return FunctionDeclarationKind.AND;
        case Z3_OP_NOT:
            return FunctionDeclarationKind.NOT;
        case Z3_OP_OR:
            return FunctionDeclarationKind.OR;
        case Z3_OP_IFF:
            return FunctionDeclarationKind.IFF;
        case Z3_OP_ITE:
            return FunctionDeclarationKind.ITE;
        case Z3_OP_XOR:
            return FunctionDeclarationKind.XOR;
        case Z3_OP_DISTINCT:
            return FunctionDeclarationKind.DISTINCT;
        case Z3_OP_IMPLIES:
            return FunctionDeclarationKind.IMPLIES;

        case Z3_OP_SUB:
            return FunctionDeclarationKind.SUB;
        case Z3_OP_ADD:
            return FunctionDeclarationKind.ADD;
        case Z3_OP_DIV:
            return FunctionDeclarationKind.DIV;
        case Z3_OP_MUL:
            return FunctionDeclarationKind.MUL;
        case Z3_OP_MOD:
            return FunctionDeclarationKind.MODULO;

        case Z3_OP_UNINTERPRETED:
            return FunctionDeclarationKind.UF;

        case Z3_OP_LT:
            return FunctionDeclarationKind.LT;
        case Z3_OP_LE:
            return FunctionDeclarationKind.LTE;
        case Z3_OP_GT:
            return FunctionDeclarationKind.GT;
        case Z3_OP_GE:
            return FunctionDeclarationKind.GTE;
        case Z3_OP_EQ:
            return FunctionDeclarationKind.EQ;

        case Z3_OP_STORE:
            return FunctionDeclarationKind.STORE;
        case Z3_OP_SELECT:
            return FunctionDeclarationKind.SELECT;

        case Z3_OP_TRUE:
        case Z3_OP_FALSE:
        case Z3_OP_ANUM:
        case Z3_OP_AGNUM:
            throw new UnsupportedOperationException("Unexpected state: constants not expected");
        case Z3_OP_OEQ:
            throw new UnsupportedOperationException("Unexpected state: not a proof");
        case Z3_OP_INTERP:
            // TODO: should we treat those separately?
            throw new UnsupportedOperationException("Unexpected state: interpolant marks not expected");
        case Z3_OP_UMINUS:
            return FunctionDeclarationKind.UMINUS;
        case Z3_OP_IDIV:

            // TODO: different handling for integer division?
            return FunctionDeclarationKind.DIV;
        default:
            return FunctionDeclarationKind.OTHER;
        }
    }

    /**
     * @param value Z3_ast
     * @return Whether the value is a constant and can be passed to {@link #convertValue(long)}.
     */
    public boolean isConstant(long value) {
        return Native.isNumeralAst(environment, value) || Native.isAlgebraicNumber(environment, value)
                || isOP(environment, value, Z3_decl_kind.Z3_OP_TRUE.toInt())
                || isOP(environment, value, Z3_decl_kind.Z3_OP_FALSE.toInt());
    }

    /**
     * @param value Z3_ast representing a constant value.
     * @return {@link BigInteger} or {@link Double} or {@link Rational} or {@link Boolean} or {@link
     *     FloatingPointRoundingMode}.
     */
    public Object convertValue(long value) {
        assert isConstant(value) : "value is not constant";
        Native.incRef(environment, value);

        Object constantValue = Z3_CONSTANTS
                .get(Native.getDeclKind(environment, Native.getAppDecl(environment, value)));
        if (constantValue != null) {
            return constantValue;
        }

        try {
            FormulaType<?> type = getFormulaType(value);
            if (type.isBooleanType()) {
                return isOP(environment, value, Z3_decl_kind.Z3_OP_TRUE.toInt());
            } else if (type.isIntegerType()) {
                return new BigInteger(Native.getNumeralString(environment, value));
            } else if (type.isRationalType()) {
                return Rational.ofString(Native.getNumeralString(environment, value));
            } else if (type.isBitvectorType()) {
                return new BigInteger(Native.getNumeralString(environment, value));
            } else if (type.isFloatingPointType()) {

                // Converting to Rational first.
                return convertValue(Native.simplify(environment, Native.mkFpaToReal(environment, value)));
            } else {

                // Explicitly crash on unknown type.
                throw new IllegalArgumentException("Unexpected type encountered: " + type);
            }

        } finally {
            Native.decRef(environment, value);
        }
    }

    @Override
    public Long callFunctionImpl(FunctionDeclarationImpl<?, Long> declaration, List<Long> args) {
        return Native.mkApp(environment, declaration.getSolverDeclaration(), args.size(), Longs.toArray(args));
    }

    @Override
    protected Long getBooleanVarDeclarationImpl(Long pLong) {
        return Native.getAppDecl(getEnv(), pLong);
    }

    /** returns, if the function of the expression is the given operation. */
    static boolean isOP(long z3context, long expr, int op) {
        if (!Native.isApp(z3context, expr)) {
            return false;
        }

        long decl = Native.getAppDecl(z3context, expr);
        return Native.getDeclKind(z3context, decl) == op;
    }

    /**
     * Apply multiple tactics in sequence.
     *
     * @throws InterruptedException thrown by JNI code in case of termination request
     * @throws SolverException thrown by JNI code in case of error
     */
    public long applyTactics(long z3context, final Long pF, String... pTactics)
            throws InterruptedException, SolverException {
        long overallResult = pF;
        for (String tactic : pTactics) {
            long tacticResult = applyTactic(z3context, overallResult, tactic);
            if (overallResult != pF) {
                Native.decRef(z3context, overallResult);
            }
            overallResult = tacticResult;
        }
        return overallResult;
    }

    /**
     * Apply tactic on a Z3_ast object, convert the result back to Z3_ast.
     *
     * @param z3context Z3_context
     * @param tactic Z3 Tactic Name
     * @param pF Z3_ast
     * @return Z3_ast
     * @throws InterruptedException If execution gets interrupted.
     */
    public long applyTactic(long z3context, long pF, String tactic) throws InterruptedException {
        long tacticObject = Native.mkTactic(z3context, tactic);
        Native.tacticIncRef(z3context, tacticObject);

        long goal = Native.mkGoal(z3context, true, false, false);
        Native.goalIncRef(z3context, goal);
        Native.goalAssert(z3context, goal, pF);

        long result;
        try {
            result = Native.tacticApply(z3context, tacticObject, goal);
        } catch (Z3Exception exp) {
            throw handleZ3Exception(exp);
        }
        Native.applyResultIncRef(z3context, result);

        try {
            return applyResultToAST(z3context, result);
        } finally {
            Native.applyResultDecRef(z3context, result);
            Native.goalDecRef(z3context, goal);
            Native.tacticDecRef(z3context, tacticObject);
        }
    }

    private long applyResultToAST(long z3context, long applyResult) {
        int subgoalsCount = Native.applyResultGetNumSubgoals(z3context, applyResult);
        long[] goalFormulas = new long[subgoalsCount];

        for (int i = 0; i < subgoalsCount; i++) {
            long subgoal = Native.applyResultGetSubgoal(z3context, applyResult, i);
            Native.goalIncRef(z3context, subgoal);
            long subgoalAst = goalToAST(z3context, subgoal);
            Native.incRef(z3context, subgoalAst);
            goalFormulas[i] = subgoalAst;
            Native.goalDecRef(z3context, subgoal);
        }
        try {
            return goalFormulas.length == 1 ? goalFormulas[0]
                    : Native.mkOr(z3context, goalFormulas.length, goalFormulas);
        } finally {
            for (int i = 0; i < subgoalsCount; i++) {
                Native.decRef(z3context, goalFormulas[i]);
            }
        }
    }

    private long goalToAST(long z3context, long goal) {
        int subgoalFormulasCount = Native.goalSize(z3context, goal);
        long[] subgoalFormulas = new long[subgoalFormulasCount];
        for (int k = 0; k < subgoalFormulasCount; k++) {
            long f = Native.goalFormula(z3context, goal, k);
            Native.incRef(z3context, f);
            subgoalFormulas[k] = f;
        }
        try {
            return subgoalFormulas.length == 1 ? subgoalFormulas[0]
                    : Native.mkAnd(z3context, subgoalFormulas.length, subgoalFormulas);
        } finally {
            for (int k = 0; k < subgoalFormulasCount; k++) {
                Native.decRef(z3context, subgoalFormulas[k]);
            }
        }
    }

    /** Closing the context. */
    public void forceClose() {
        cleanupReferences();

        // Force clean all ASTs, even those which were not GC'd yet.
        // Is a no-op if phantom reference handling is not enabled.
        for (long ast : referenceMap.values()) {
            Native.decRef(getEnv(), ast);
        }
    }
}