bool Z3SolverImpl::internalRunSolver( const Query &query, const std::vector<const Array *> *objects, std::vector<std::vector<unsigned char> > *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 }
char *Z3SolverImpl::getConstraintLog(const Query &query) { std::vector<Z3ASTHandle> 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<ref<Expr> >::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::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; }
char *Z3SolverImpl::getConstraintLog(const Query &query) { std::vector<Z3ASTHandle> assumptions; for (std::vector<ref<Expr> >::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::internalRunSolver( const Query &query, const std::vector<const Array *> *objects, std::vector<std::vector<unsigned char> > *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 }