klee
Z3Solver.cpp
Go to the documentation of this file.
1//===-- Z3Solver.cpp -------------------------------------------*- C++ -*-====//
2//
3// The KLEE Symbolic Virtual Machine
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "klee/Config/config.h"
14
15#include <csignal>
16
17#ifdef ENABLE_Z3
18
19#include "Z3Solver.h"
20#include "Z3Builder.h"
21
24#include "klee/Expr/ExprUtil.h"
25#include "klee/Solver/Solver.h"
27#include "llvm/Support/CommandLine.h"
28#include "llvm/Support/raw_ostream.h"
29
30namespace {
31// NOTE: Very useful for debugging Z3 behaviour. These files can be given to
32// the z3 binary to replay all Z3 API calls using its `-log` option.
33llvm::cl::opt<std::string> Z3LogInteractionFile(
34 "debug-z3-log-api-interaction", llvm::cl::init(""),
35 llvm::cl::desc("Log API interaction with Z3 to the specified path"),
36 llvm::cl::cat(klee::SolvingCat));
37
38llvm::cl::opt<std::string> Z3QueryDumpFile(
39 "debug-z3-dump-queries", llvm::cl::init(""),
40 llvm::cl::desc("Dump Z3's representation of the query to the specified path"),
41 llvm::cl::cat(klee::SolvingCat));
42
43llvm::cl::opt<bool> Z3ValidateModels(
44 "debug-z3-validate-models", llvm::cl::init(false),
45 llvm::cl::desc("When generating Z3 models validate these against the query"),
46 llvm::cl::cat(klee::SolvingCat));
47
48llvm::cl::opt<unsigned>
49 Z3VerbosityLevel("debug-z3-verbosity", llvm::cl::init(0),
50 llvm::cl::desc("Z3 verbosity level (default=0)"),
51 llvm::cl::cat(klee::SolvingCat));
52}
53
54#include "llvm/Support/ErrorHandling.h"
55
56namespace klee {
57
58class Z3SolverImpl : public SolverImpl {
59private:
60 Z3Builder *builder;
61 time::Span timeout;
62 SolverRunStatus runStatusCode;
63 std::unique_ptr<llvm::raw_fd_ostream> dumpedQueriesFile;
64 ::Z3_params solverParameters;
65 // Parameter symbols
66 ::Z3_symbol timeoutParamStrSymbol;
67
68 bool internalRunSolver(const Query &,
69 const std::vector<const Array *> *objects,
70 std::vector<std::vector<unsigned char> > *values,
71 bool &hasSolution);
72 bool validateZ3Model(::Z3_solver &theSolver, ::Z3_model &theModel);
73
74public:
75 Z3SolverImpl();
76 ~Z3SolverImpl();
77
78 char *getConstraintLog(const Query &);
79 void setCoreSolverTimeout(time::Span _timeout) {
80 timeout = _timeout;
81
82 auto timeoutInMilliSeconds = static_cast<unsigned>((timeout.toMicroseconds() / 1000));
83 if (!timeoutInMilliSeconds)
84 timeoutInMilliSeconds = UINT_MAX;
85 Z3_params_set_uint(builder->ctx, solverParameters, timeoutParamStrSymbol,
86 timeoutInMilliSeconds);
87 }
88
89 bool computeTruth(const Query &, bool &isValid);
90 bool computeValue(const Query &, ref<Expr> &result);
91 bool computeInitialValues(const Query &,
92 const std::vector<const Array *> &objects,
93 std::vector<std::vector<unsigned char> > &values,
94 bool &hasSolution);
95 SolverRunStatus
96 handleSolverResponse(::Z3_solver theSolver, ::Z3_lbool satisfiable,
97 const std::vector<const Array *> *objects,
98 std::vector<std::vector<unsigned char> > *values,
99 bool &hasSolution);
100 SolverRunStatus getOperationStatusCode();
101};
102
103Z3SolverImpl::Z3SolverImpl()
104 : builder(new Z3Builder(
105 /*autoClearConstructCache=*/false,
106 /*z3LogInteractionFileArg=*/Z3LogInteractionFile.size() > 0
107 ? Z3LogInteractionFile.c_str()
108 : NULL)),
109 runStatusCode(SOLVER_RUN_STATUS_FAILURE) {
110 assert(builder && "unable to create Z3Builder");
111 solverParameters = Z3_mk_params(builder->ctx);
112 Z3_params_inc_ref(builder->ctx, solverParameters);
113 timeoutParamStrSymbol = Z3_mk_string_symbol(builder->ctx, "timeout");
114 setCoreSolverTimeout(timeout);
115
116 if (!Z3QueryDumpFile.empty()) {
117 std::string error;
118 dumpedQueriesFile = klee_open_output_file(Z3QueryDumpFile, error);
119 if (!dumpedQueriesFile) {
120 klee_error("Error creating file for dumping Z3 queries: %s",
121 error.c_str());
122 }
123 klee_message("Dumping Z3 queries to \"%s\"", Z3QueryDumpFile.c_str());
124 }
125
126 // Set verbosity
127 if (Z3VerbosityLevel > 0) {
128 std::string underlyingString;
129 llvm::raw_string_ostream ss(underlyingString);
130 ss << Z3VerbosityLevel;
131 ss.flush();
132 Z3_global_param_set("verbose", underlyingString.c_str());
133 }
134}
135
136Z3SolverImpl::~Z3SolverImpl() {
137 Z3_params_dec_ref(builder->ctx, solverParameters);
138 delete builder;
139}
140
141Z3Solver::Z3Solver() : Solver(new Z3SolverImpl()) {}
142
143char *Z3Solver::getConstraintLog(const Query &query) {
144 return impl->getConstraintLog(query);
145}
146
147void Z3Solver::setCoreSolverTimeout(time::Span timeout) {
148 impl->setCoreSolverTimeout(timeout);
149}
150
151char *Z3SolverImpl::getConstraintLog(const Query &query) {
152 std::vector<Z3ASTHandle> assumptions;
153 // We use a different builder here because we don't want to interfere
154 // with the solver's builder because it may change the solver builder's
155 // cache.
156 // NOTE: The builder does not set `z3LogInteractionFile` to avoid conflicting
157 // with whatever the solver's builder is set to do.
158 Z3Builder temp_builder(/*autoClearConstructCache=*/false,
159 /*z3LogInteractionFile=*/NULL);
160 ConstantArrayFinder constant_arrays_in_query;
161 for (auto const &constraint : query.constraints) {
162 assumptions.push_back(temp_builder.construct(constraint));
163 constant_arrays_in_query.visit(constraint);
164 }
165
166 // KLEE Queries are validity queries i.e.
167 // ∀ X Constraints(X) → query(X)
168 // but Z3 works in terms of satisfiability so instead we ask the
169 // the negation of the equivalent i.e.
170 // ∃ X Constraints(X) ∧ ¬ query(X)
171 Z3ASTHandle formula = Z3ASTHandle(
172 Z3_mk_not(temp_builder.ctx, temp_builder.construct(query.expr)),
173 temp_builder.ctx);
174 constant_arrays_in_query.visit(query.expr);
175
176 for (auto const &constant_array : constant_arrays_in_query.results) {
177 assert(temp_builder.constant_array_assertions.count(constant_array) == 1 &&
178 "Constant array found in query, but not handled by Z3Builder");
179 for (auto const &arrayIndexValueExpr :
180 temp_builder.constant_array_assertions[constant_array]) {
181 assumptions.push_back(arrayIndexValueExpr);
182 }
183 }
184
185 ::Z3_ast *assumptionsArray = NULL;
186 int numAssumptions = assumptions.size();
187 if (numAssumptions) {
188 assumptionsArray = (::Z3_ast *)malloc(sizeof(::Z3_ast) * numAssumptions);
189 for (int index = 0; index < numAssumptions; ++index) {
190 assumptionsArray[index] = (::Z3_ast)assumptions[index];
191 }
192 }
193
194 ::Z3_string result = Z3_benchmark_to_smtlib_string(
195 temp_builder.ctx,
196 /*name=*/"Emited by klee::Z3SolverImpl::getConstraintLog()",
197 /*logic=*/"",
198 /*status=*/"unknown",
199 /*attributes=*/"",
200 /*num_assumptions=*/numAssumptions,
201 /*assumptions=*/assumptionsArray,
202 /*formula=*/formula);
203
204 if (numAssumptions)
205 free(assumptionsArray);
206
207 // We need to trigger a dereference before the `temp_builder` gets destroyed.
208 // We do this indirectly by emptying `assumptions` and assigning to
209 // `formula`.
210 assumptions.clear();
211 formula = Z3ASTHandle(NULL, temp_builder.ctx);
212 // Client is responsible for freeing the returned C-string
213 return strdup(result);
214}
215
216bool Z3SolverImpl::computeTruth(const Query &query, bool &isValid) {
217 bool hasSolution = false; // to remove compiler warning
218 bool status =
219 internalRunSolver(query, /*objects=*/NULL, /*values=*/NULL, hasSolution);
220 isValid = !hasSolution;
221 return status;
222}
223
224bool Z3SolverImpl::computeValue(const Query &query, ref<Expr> &result) {
225 std::vector<const Array *> objects;
226 std::vector<std::vector<unsigned char> > values;
227 bool hasSolution;
228
229 // Find the object used in the expression, and compute an assignment
230 // for them.
231 findSymbolicObjects(query.expr, objects);
232 if (!computeInitialValues(query.withFalse(), objects, values, hasSolution))
233 return false;
234 assert(hasSolution && "state has invalid constraint set");
235
236 // Evaluate the expression with the computed assignment.
237 Assignment a(objects, values);
238 result = a.evaluate(query.expr);
239
240 return true;
241}
242
243bool Z3SolverImpl::computeInitialValues(
244 const Query &query, const std::vector<const Array *> &objects,
245 std::vector<std::vector<unsigned char> > &values, bool &hasSolution) {
246 return internalRunSolver(query, &objects, &values, hasSolution);
247}
248
249bool Z3SolverImpl::internalRunSolver(
250 const Query &query, const std::vector<const Array *> *objects,
251 std::vector<std::vector<unsigned char> > *values, bool &hasSolution) {
252
253 TimerStatIncrementer t(stats::queryTime);
254 // NOTE: Z3 will switch to using a slower solver internally if push/pop are
255 // used so for now it is likely that creating a new solver each time is the
256 // right way to go until Z3 changes its behaviour.
257 //
258 // TODO: Investigate using a custom tactic as described in
259 // https://github.com/klee/klee/issues/653
260 Z3_solver theSolver = Z3_mk_solver(builder->ctx);
261 Z3_solver_inc_ref(builder->ctx, theSolver);
262 Z3_solver_set_params(builder->ctx, theSolver, solverParameters);
263
264 runStatusCode = SOLVER_RUN_STATUS_FAILURE;
265
266 ConstantArrayFinder constant_arrays_in_query;
267 for (auto const &constraint : query.constraints) {
268 Z3_solver_assert(builder->ctx, theSolver, builder->construct(constraint));
269 constant_arrays_in_query.visit(constraint);
270 }
272 if (objects)
274
275 Z3ASTHandle z3QueryExpr =
276 Z3ASTHandle(builder->construct(query.expr), builder->ctx);
277 constant_arrays_in_query.visit(query.expr);
278
279 for (auto const &constant_array : constant_arrays_in_query.results) {
280 assert(builder->constant_array_assertions.count(constant_array) == 1 &&
281 "Constant array found in query, but not handled by Z3Builder");
282 for (auto const &arrayIndexValueExpr :
283 builder->constant_array_assertions[constant_array]) {
284 Z3_solver_assert(builder->ctx, theSolver, arrayIndexValueExpr);
285 }
286 }
287
288 // KLEE Queries are validity queries i.e.
289 // ∀ X Constraints(X) → query(X)
290 // but Z3 works in terms of satisfiability so instead we ask the
291 // negation of the equivalent i.e.
292 // ∃ X Constraints(X) ∧ ¬ query(X)
293 Z3_solver_assert(
294 builder->ctx, theSolver,
295 Z3ASTHandle(Z3_mk_not(builder->ctx, z3QueryExpr), builder->ctx));
296
297 if (dumpedQueriesFile) {
298 *dumpedQueriesFile << "; start Z3 query\n";
299 *dumpedQueriesFile << Z3_solver_to_string(builder->ctx, theSolver);
300 *dumpedQueriesFile << "(check-sat)\n";
301 *dumpedQueriesFile << "(reset)\n";
302 *dumpedQueriesFile << "; end Z3 query\n\n";
303 dumpedQueriesFile->flush();
304 }
305
306 ::Z3_lbool satisfiable = Z3_solver_check(builder->ctx, theSolver);
307 runStatusCode = handleSolverResponse(theSolver, satisfiable, objects, values,
308 hasSolution);
309
310 Z3_solver_dec_ref(builder->ctx, theSolver);
311 // Clear the builder's cache to prevent memory usage exploding.
312 // By using ``autoClearConstructCache=false`` and clearning now
313 // we allow Z3_ast expressions to be shared from an entire
314 // ``Query`` rather than only sharing within a single call to
315 // ``builder->construct()``.
316 builder->clearConstructCache();
317
318 if (runStatusCode == SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE ||
319 runStatusCode == SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE) {
320 if (hasSolution) {
322 } else {
324 }
325 return true; // success
326 }
327 if (runStatusCode == SolverImpl::SOLVER_RUN_STATUS_INTERRUPTED) {
328 raise(SIGINT);
329 }
330 return false; // failed
331}
332
333SolverImpl::SolverRunStatus Z3SolverImpl::handleSolverResponse(
334 ::Z3_solver theSolver, ::Z3_lbool satisfiable,
335 const std::vector<const Array *> *objects,
336 std::vector<std::vector<unsigned char> > *values, bool &hasSolution) {
337 switch (satisfiable) {
338 case Z3_L_TRUE: {
339 hasSolution = true;
340 if (!objects) {
341 // No assignment is needed
342 assert(values == NULL);
343 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE;
344 }
345 assert(values && "values cannot be nullptr");
346 ::Z3_model theModel = Z3_solver_get_model(builder->ctx, theSolver);
347 assert(theModel && "Failed to retrieve model");
348 Z3_model_inc_ref(builder->ctx, theModel);
349 values->reserve(objects->size());
350 for (std::vector<const Array *>::const_iterator it = objects->begin(),
351 ie = objects->end();
352 it != ie; ++it) {
353 const Array *array = *it;
354 std::vector<unsigned char> data;
355
356 data.reserve(array->size);
357 for (unsigned offset = 0; offset < array->size; offset++) {
358 // We can't use Z3ASTHandle here so have to do ref counting manually
359 ::Z3_ast arrayElementExpr;
360 Z3ASTHandle initial_read = builder->getInitialRead(array, offset);
361
362 __attribute__((unused))
363 bool successfulEval =
364 Z3_model_eval(builder->ctx, theModel, initial_read,
365 /*model_completion=*/Z3_TRUE, &arrayElementExpr);
366 assert(successfulEval && "Failed to evaluate model");
367 Z3_inc_ref(builder->ctx, arrayElementExpr);
368 assert(Z3_get_ast_kind(builder->ctx, arrayElementExpr) ==
369 Z3_NUMERAL_AST &&
370 "Evaluated expression has wrong sort");
371
372 int arrayElementValue = 0;
373 __attribute__((unused))
374 bool successGet = Z3_get_numeral_int(builder->ctx, arrayElementExpr,
375 &arrayElementValue);
376 assert(successGet && "failed to get value back");
377 assert(arrayElementValue >= 0 && arrayElementValue <= 255 &&
378 "Integer from model is out of range");
379 data.push_back(arrayElementValue);
380 Z3_dec_ref(builder->ctx, arrayElementExpr);
381 }
382 values->push_back(data);
383 }
384
385 // Validate the model if requested
386 if (Z3ValidateModels) {
387 bool success = validateZ3Model(theSolver, theModel);
388 if (!success)
389 abort();
390 }
391
392 Z3_model_dec_ref(builder->ctx, theModel);
393 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE;
394 }
395 case Z3_L_FALSE:
396 hasSolution = false;
397 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE;
398 case Z3_L_UNDEF: {
399 ::Z3_string reason =
400 ::Z3_solver_get_reason_unknown(builder->ctx, theSolver);
401 if (strcmp(reason, "timeout") == 0 || strcmp(reason, "canceled") == 0 ||
402 strcmp(reason, "(resource limits reached)") == 0) {
403 return SolverImpl::SOLVER_RUN_STATUS_TIMEOUT;
404 }
405 if (strcmp(reason, "unknown") == 0) {
406 return SolverImpl::SOLVER_RUN_STATUS_FAILURE;
407 }
408 if (strcmp(reason, "interrupted from keyboard") == 0) {
409 return SolverImpl::SOLVER_RUN_STATUS_INTERRUPTED;
410 }
411 klee_warning("Unexpected solver failure. Reason is \"%s,\"\n", reason);
412 abort();
413 }
414 default:
415 llvm_unreachable("unhandled Z3 result");
416 }
417}
418
419bool Z3SolverImpl::validateZ3Model(::Z3_solver &theSolver, ::Z3_model &theModel) {
420 bool success = true;
421 ::Z3_ast_vector constraints =
422 Z3_solver_get_assertions(builder->ctx, theSolver);
423 Z3_ast_vector_inc_ref(builder->ctx, constraints);
424
425 unsigned size = Z3_ast_vector_size(builder->ctx, constraints);
426
427 for (unsigned index = 0; index < size; ++index) {
428 Z3ASTHandle constraint = Z3ASTHandle(
429 Z3_ast_vector_get(builder->ctx, constraints, index), builder->ctx);
430
431 ::Z3_ast rawEvaluatedExpr;
432 __attribute__((unused))
433 bool successfulEval =
434 Z3_model_eval(builder->ctx, theModel, constraint,
435 /*model_completion=*/Z3_TRUE, &rawEvaluatedExpr);
436 assert(successfulEval && "Failed to evaluate model");
437
438 // Use handle to do ref-counting.
439 Z3ASTHandle evaluatedExpr(rawEvaluatedExpr, builder->ctx);
440
441 Z3SortHandle sort =
442 Z3SortHandle(Z3_get_sort(builder->ctx, evaluatedExpr), builder->ctx);
443 assert(Z3_get_sort_kind(builder->ctx, sort) == Z3_BOOL_SORT &&
444 "Evaluated expression has wrong sort");
445
446 Z3_lbool evaluatedValue =
447 Z3_get_bool_value(builder->ctx, evaluatedExpr);
448 if (evaluatedValue != Z3_L_TRUE) {
449 llvm::errs() << "Validating model failed:\n"
450 << "The expression:\n";
451 constraint.dump();
452 llvm::errs() << "evaluated to \n";
453 evaluatedExpr.dump();
454 llvm::errs() << "But should be true\n";
455 success = false;
456 }
457 }
458
459 if (!success) {
460 llvm::errs() << "Solver state:\n" << Z3_solver_to_string(builder->ctx, theSolver) << "\n";
461 llvm::errs() << "Model:\n" << Z3_model_to_string(builder->ctx, theModel) << "\n";
462 }
463
464 Z3_ast_vector_dec_ref(builder->ctx, constraints);
465 return success;
466}
467
468SolverImpl::SolverRunStatus Z3SolverImpl::getOperationStatusCode() {
469 return runStatusCode;
470}
471}
472#endif // ENABLE_Z3
void *__dso_handle __attribute__((__weak__))
void klee_warning(char *name)
Definition: klee-replay.c:432
Statistic queryTime
Statistic queriesInvalid
Statistic queriesValid
Statistic queryCounterexamples
Statistic queries
Definition: main.cpp:291
void findSymbolicObjects(ref< Expr > e, std::vector< const Array * > &results)
Definition: ExprUtil.cpp:132
std::unique_ptr< llvm::raw_fd_ostream > klee_open_output_file(const std::string &path, std::string &error)
void klee_message(const char *msg,...) __attribute__((format(printf
Z3NodeHandle< Z3_ast > Z3ASTHandle
Definition: Z3Builder.h:91
void klee_error(const char *msg,...) __attribute__((format(printf
llvm::cl::OptionCategory SolvingCat
Z3NodeHandle< Z3_sort > Z3SortHandle
Definition: Z3Builder.h:86