//===-- 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" #include "klee/Internal/Support/ErrorHandling.h" #include "klee/Internal/Support/FileHandling.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/CommandLine.h" #include "llvm/Support/raw_ostream.h" namespace { // NOTE: Very useful for debugging Z3 behaviour. These files can be given to // the z3 binary to replay all Z3 API calls using its `-log` option. llvm::cl::opt Z3LogInteractionFile( "debug-z3-log-api-interaction", llvm::cl::init(""), llvm::cl::desc("Log API interaction with Z3 to the specified path")); llvm::cl::opt Z3QueryDumpFile( "debug-z3-dump-queries", llvm::cl::init(""), llvm::cl::desc("Dump Z3's representation of the query to the specified path")); llvm::cl::opt Z3ValidateModels( "debug-z3-validate-models", llvm::cl::init(false), llvm::cl::desc("When generating Z3 models validate these against the query")); } #include "llvm/Support/ErrorHandling.h" namespace klee { class Z3SolverImpl : public SolverImpl { private: Z3Builder *builder; double timeout; SolverRunStatus runStatusCode; llvm::raw_fd_ostream* dumpedQueriesFile; ::Z3_params solverParameters; // Parameter symbols ::Z3_symbol timeoutParamStrSymbol; bool internalRunSolver(const Query &, const std::vector *objects, std::vector > *values, bool &hasSolution); bool validateZ3Model(::Z3_solver &theSolver, ::Z3_model &theModel); 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, /*z3LogInteractionFileArg=*/Z3LogInteractionFile.size() > 0 ? Z3LogInteractionFile.c_str() : NULL)), timeout(0.0), runStatusCode(SOLVER_RUN_STATUS_FAILURE), dumpedQueriesFile(0) { 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); if (!Z3QueryDumpFile.empty()) { std::string error; dumpedQueriesFile = klee_open_output_file(Z3QueryDumpFile, error); if (!error.empty()) { klee_error("Error creating file for dumping Z3 queries: %s", error.c_str()); } klee_message("Dumping Z3 queries to \"%s\"", Z3QueryDumpFile.c_str()); } } Z3SolverImpl::~Z3SolverImpl() { Z3_params_dec_ref(builder->ctx, solverParameters); delete builder; if (dumpedQueriesFile) { dumpedQueriesFile->close(); delete dumpedQueriesFile; } } 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; // We use a different builder here because we don't want to interfere // with the solver's builder because it may change the solver builder's // cache. // NOTE: The builder does not set `z3LogInteractionFile` to avoid conflicting // with whatever the solver's builder is set to do. Z3Builder temp_builder(/*autoClearConstructCache=*/false, /*z3LogInteractionFile=*/NULL); for (std::vector >::const_iterator it = query.constraints.begin(), ie = query.constraints.end(); it != ie; ++it) { assumptions.push_back(temp_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(temp_builder.ctx, temp_builder.construct(query.expr)), temp_builder.ctx); ::Z3_string result = Z3_benchmark_to_smtlib_string( temp_builder.ctx, /*name=*/"Emited by klee::Z3SolverImpl::getConstraintLog()", /*logic=*/"", /*status=*/"unknown", /*attributes=*/"", /*num_assumptions=*/numAssumptions, /*assumptions=*/assumptionsArray, /*formula=*/formula); if (numAssumptions) free(assumptionsArray); // We need to trigger a dereference before the `temp_builder` gets destroyed. // We do this indirectly by emptying `assumptions` and assigning to // `formula`. assumptions.clear(); formula = Z3ASTHandle(NULL, temp_builder.ctx); // 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); // NOTE: Z3 will switch to using a slower solver internally if push/pop are // used so for now it is likely that creating a new solver each time is the // right way to go until Z3 changes its behaviour. // // TODO: Investigate using a custom tactic as described in // https://github.com/klee/klee/issues/653 Z3_solver theSolver = Z3_mk_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)); if (dumpedQueriesFile) { *dumpedQueriesFile << "; start Z3 query\n"; *dumpedQueriesFile << Z3_solver_to_string(builder->ctx, theSolver); *dumpedQueriesFile << "(check-sat)\n"; *dumpedQueriesFile << "(reset)\n"; *dumpedQueriesFile << "; end Z3 query\n\n"; dumpedQueriesFile->flush(); } ::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); } // Validate the model if requested if (Z3ValidateModels) { bool success = validateZ3Model(theSolver, theModel); if (!success) abort(); } 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 || strcmp(reason, "(resource limits reached)") == 0) { return SolverImpl::SOLVER_RUN_STATUS_TIMEOUT; } if (strcmp(reason, "unknown") == 0) { return SolverImpl::SOLVER_RUN_STATUS_FAILURE; } klee_warning("Unexpected solver failure. Reason is \"%s,\"\n", reason); abort(); } default: llvm_unreachable("unhandled Z3 result"); } } bool Z3SolverImpl::validateZ3Model(::Z3_solver &theSolver, ::Z3_model &theModel) { bool success = true; ::Z3_ast_vector constraints = Z3_solver_get_assertions(builder->ctx, theSolver); Z3_ast_vector_inc_ref(builder->ctx, constraints); unsigned size = Z3_ast_vector_size(builder->ctx, constraints); for (unsigned index = 0; index < size; ++index) { Z3ASTHandle constraint = Z3ASTHandle( Z3_ast_vector_get(builder->ctx, constraints, index), builder->ctx); ::Z3_ast rawEvaluatedExpr; bool successfulEval = Z3_model_eval(builder->ctx, theModel, constraint, /*model_completion=*/Z3_TRUE, &rawEvaluatedExpr); assert(successfulEval && "Failed to evaluate model"); // Use handle to do ref-counting. Z3ASTHandle evaluatedExpr(rawEvaluatedExpr, builder->ctx); Z3SortHandle sort = Z3SortHandle(Z3_get_sort(builder->ctx, evaluatedExpr), builder->ctx); assert(Z3_get_sort_kind(builder->ctx, sort) == Z3_BOOL_SORT && "Evaluated expression has wrong sort"); Z3_lbool evaluatedValue = Z3_get_bool_value(builder->ctx, evaluatedExpr); if (evaluatedValue != Z3_L_TRUE) { llvm::errs() << "Validating model failed:\n" << "The expression:\n"; constraint.dump(); llvm::errs() << "evaluated to \n"; evaluatedExpr.dump(); llvm::errs() << "But should be true\n"; success = false; } } if (!success) { llvm::errs() << "Solver state:\n" << Z3_solver_to_string(builder->ctx, theSolver) << "\n"; llvm::errs() << "Model:\n" << Z3_model_to_string(builder->ctx, theModel) << "\n"; } Z3_ast_vector_dec_ref(builder->ctx, constraints); return success; } SolverImpl::SolverRunStatus Z3SolverImpl::getOperationStatusCode() { return runStatusCode; } } #endif // ENABLE_Z3