//===-- Z3Solver.cpp -------------------------------------------*- C++ -*-====// // // The KLEE Symbolic Virtual Machine // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "klee/Config/config.h" #ifdef ENABLE_Z3 #include "Z3Builder.h" #include "klee/Constraints.h" #include "klee/Solver.h" #include "klee/SolverImpl.h" #include "klee/util/Assignment.h" #include "klee/util/ExprUtil.h" #include "llvm/Support/ErrorHandling.h" namespace klee { class Z3SolverImpl : public SolverImpl { private: Z3Builder *builder; double timeout; SolverRunStatus runStatusCode; ::Z3_params solverParameters; // Parameter symbols ::Z3_symbol timeoutParamStrSymbol; bool internalRunSolver(const Query &, const std::vector *objects, std::vector > *values, bool &hasSolution); public: Z3SolverImpl(); ~Z3SolverImpl(); char *getConstraintLog(const Query &); void setCoreSolverTimeout(double _timeout) { assert(_timeout >= 0.0 && "timeout must be >= 0"); timeout = _timeout; unsigned int timeoutInMilliSeconds = (unsigned int)((timeout * 1000) + 0.5); if (timeoutInMilliSeconds == 0) timeoutInMilliSeconds = UINT_MAX; Z3_params_set_uint(builder->ctx, solverParameters, timeoutParamStrSymbol, timeoutInMilliSeconds); } bool computeTruth(const Query &, bool &isValid); bool computeValue(const Query &, ref &result); bool computeInitialValues(const Query &, const std::vector &objects, std::vector > &values, bool &hasSolution); SolverRunStatus handleSolverResponse(::Z3_solver theSolver, ::Z3_lbool satisfiable, const std::vector *objects, std::vector > *values, bool &hasSolution); SolverRunStatus getOperationStatusCode(); }; Z3SolverImpl::Z3SolverImpl() : builder(new Z3Builder(/*autoClearConstructCache=*/false)), timeout(0.0), runStatusCode(SOLVER_RUN_STATUS_FAILURE) { assert(builder && "unable to create Z3Builder"); solverParameters = Z3_mk_params(builder->ctx); Z3_params_inc_ref(builder->ctx, solverParameters); timeoutParamStrSymbol = Z3_mk_string_symbol(builder->ctx, "timeout"); setCoreSolverTimeout(timeout); } Z3SolverImpl::~Z3SolverImpl() { Z3_params_dec_ref(builder->ctx, solverParameters); delete builder; } Z3Solver::Z3Solver() : Solver(new Z3SolverImpl()) {} char *Z3Solver::getConstraintLog(const Query &query) { return impl->getConstraintLog(query); } void Z3Solver::setCoreSolverTimeout(double timeout) { impl->setCoreSolverTimeout(timeout); } char *Z3SolverImpl::getConstraintLog(const Query &query) { std::vector assumptions; for (std::vector >::const_iterator it = query.constraints.begin(), ie = query.constraints.end(); it != ie; ++it) { assumptions.push_back(builder->construct(*it)); } ::Z3_ast *assumptionsArray = NULL; int numAssumptions = query.constraints.size(); if (numAssumptions) { assumptionsArray = (::Z3_ast *)malloc(sizeof(::Z3_ast) * numAssumptions); for (int index = 0; index < numAssumptions; ++index) { assumptionsArray[index] = (::Z3_ast)assumptions[index]; } } // KLEE Queries are validity queries i.e. // ∀ X Constraints(X) → query(X) // but Z3 works in terms of satisfiability so instead we ask the // the negation of the equivalent i.e. // ∃ X Constraints(X) ∧ ¬ query(X) Z3ASTHandle formula = Z3ASTHandle( Z3_mk_not(builder->ctx, builder->construct(query.expr)), builder->ctx); ::Z3_string result = Z3_benchmark_to_smtlib_string( builder->ctx, /*name=*/"Emited by klee::Z3SolverImpl::getConstraintLog()", /*logic=*/"", /*status=*/"unknown", /*attributes=*/"", /*num_assumptions=*/numAssumptions, /*assumptions=*/assumptionsArray, /*formula=*/formula); if (numAssumptions) free(assumptionsArray); // Client is responsible for freeing the returned C-string return strdup(result); } bool Z3SolverImpl::computeTruth(const Query &query, bool &isValid) { bool hasSolution; bool status = internalRunSolver(query, /*objects=*/NULL, /*values=*/NULL, hasSolution); isValid = !hasSolution; return status; } bool Z3SolverImpl::computeValue(const Query &query, ref &result) { std::vector objects; std::vector > values; bool hasSolution; // Find the object used in the expression, and compute an assignment // for them. findSymbolicObjects(query.expr, objects); if (!computeInitialValues(query.withFalse(), objects, values, hasSolution)) return false; assert(hasSolution && "state has invalid constraint set"); // Evaluate the expression with the computed assignment. Assignment a(objects, values); result = a.evaluate(query.expr); return true; } bool Z3SolverImpl::computeInitialValues( const Query &query, const std::vector &objects, std::vector > &values, bool &hasSolution) { return internalRunSolver(query, &objects, &values, hasSolution); } bool Z3SolverImpl::internalRunSolver( const Query &query, const std::vector *objects, std::vector > *values, bool &hasSolution) { TimerStatIncrementer t(stats::queryTime); // TODO: Does making a new solver for each query have a performance // impact vs making one global solver and using push and pop? // TODO: is the "simple_solver" the right solver to use for // best performance? Z3_solver theSolver = Z3_mk_simple_solver(builder->ctx); Z3_solver_inc_ref(builder->ctx, theSolver); Z3_solver_set_params(builder->ctx, theSolver, solverParameters); runStatusCode = SOLVER_RUN_STATUS_FAILURE; for (ConstraintManager::const_iterator it = query.constraints.begin(), ie = query.constraints.end(); it != ie; ++it) { Z3_solver_assert(builder->ctx, theSolver, builder->construct(*it)); } ++stats::queries; if (objects) ++stats::queryCounterexamples; Z3ASTHandle z3QueryExpr = Z3ASTHandle(builder->construct(query.expr), builder->ctx); // KLEE Queries are validity queries i.e. // ∀ X Constraints(X) → query(X) // but Z3 works in terms of satisfiability so instead we ask the // negation of the equivalent i.e. // ∃ X Constraints(X) ∧ ¬ query(X) Z3_solver_assert( builder->ctx, theSolver, Z3ASTHandle(Z3_mk_not(builder->ctx, z3QueryExpr), builder->ctx)); ::Z3_lbool satisfiable = Z3_solver_check(builder->ctx, theSolver); runStatusCode = handleSolverResponse(theSolver, satisfiable, objects, values, hasSolution); Z3_solver_dec_ref(builder->ctx, theSolver); // Clear the builder's cache to prevent memory usage exploding. // By using ``autoClearConstructCache=false`` and clearning now // we allow Z3_ast expressions to be shared from an entire // ``Query`` rather than only sharing within a single call to // ``builder->construct()``. builder->clearConstructCache(); if (runStatusCode == SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE || runStatusCode == SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE) { if (hasSolution) { ++stats::queriesInvalid; } else { ++stats::queriesValid; } return true; // success } return false; // failed } SolverImpl::SolverRunStatus Z3SolverImpl::handleSolverResponse( ::Z3_solver theSolver, ::Z3_lbool satisfiable, const std::vector *objects, std::vector > *values, bool &hasSolution) { switch (satisfiable) { case Z3_L_TRUE: { hasSolution = true; if (!objects) { // No assignment is needed assert(values == NULL); return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE; } assert(values && "values cannot be nullptr"); ::Z3_model theModel = Z3_solver_get_model(builder->ctx, theSolver); assert(theModel && "Failed to retrieve model"); Z3_model_inc_ref(builder->ctx, theModel); values->reserve(objects->size()); for (std::vector::const_iterator it = objects->begin(), ie = objects->end(); it != ie; ++it) { const Array *array = *it; std::vector data; data.reserve(array->size); for (unsigned offset = 0; offset < array->size; offset++) { // We can't use Z3ASTHandle here so have to do ref counting manually ::Z3_ast arrayElementExpr; Z3ASTHandle initial_read = builder->getInitialRead(array, offset); bool successfulEval = Z3_model_eval(builder->ctx, theModel, initial_read, /*model_completion=*/Z3_TRUE, &arrayElementExpr); assert(successfulEval && "Failed to evaluate model"); Z3_inc_ref(builder->ctx, arrayElementExpr); assert(Z3_get_ast_kind(builder->ctx, arrayElementExpr) == Z3_NUMERAL_AST && "Evaluated expression has wrong sort"); int arrayElementValue = 0; bool successGet = Z3_get_numeral_int(builder->ctx, arrayElementExpr, &arrayElementValue); assert(successGet && "failed to get value back"); assert(arrayElementValue >= 0 && arrayElementValue <= 255 && "Integer from model is out of range"); data.push_back(arrayElementValue); Z3_dec_ref(builder->ctx, arrayElementExpr); } values->push_back(data); } Z3_model_dec_ref(builder->ctx, theModel); return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE; } case Z3_L_FALSE: hasSolution = false; return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE; case Z3_L_UNDEF: { ::Z3_string reason = ::Z3_solver_get_reason_unknown(builder->ctx, theSolver); if (strcmp(reason, "timeout") == 0 || strcmp(reason, "canceled") == 0) { return SolverImpl::SOLVER_RUN_STATUS_TIMEOUT; } if (strcmp(reason, "unknown") == 0) { return SolverImpl::SOLVER_RUN_STATUS_FAILURE; } llvm::errs() << "Unexpected solver failure. Reason is \"" << reason << "\"\n"; abort(); } default: llvm_unreachable("unhandled Z3 result"); } } SolverImpl::SolverRunStatus Z3SolverImpl::getOperationStatusCode() { return runStatusCode; } } #endif // ENABLE_Z3