//===-- STPSolver.cpp -----------------------------------------------------===// // // 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_STP #include "STPSolver.h" #include "STPBuilder.h" #include "klee/Solver.h" #include "klee/SolverImpl.h" #include "klee/Constraints.h" #include "klee/Internal/Support/ErrorHandling.h" #include "klee/OptionCategories.h" #include "klee/util/Assignment.h" #include "klee/util/ExprUtil.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Errno.h" #include "llvm/Support/ErrorHandling.h" #include #include #include #include #include #include namespace { llvm::cl::opt DebugDumpSTPQueries( "debug-dump-stp-queries", llvm::cl::init(false), llvm::cl::desc("Dump every STP query to stderr (default=false)"), llvm::cl::cat(klee::SolvingCat)); llvm::cl::opt IgnoreSolverFailures( "ignore-solver-failures", llvm::cl::init(false), llvm::cl::desc("Ignore any STP solver failures (default=false)"), llvm::cl::cat(klee::SolvingCat)); } #define vc_bvBoolExtract IAMTHESPAWNOFSATAN static unsigned char *shared_memory_ptr; static int shared_memory_id = 0; // Darwin by default has a very small limit on the maximum amount of shared // memory, which will quickly be exhausted by KLEE running its tests in // parallel. For now, we work around this by just requesting a smaller size -- // in practice users hitting this limit on counterexample sizes probably already // are hitting more serious scalability issues. #ifdef __APPLE__ static const unsigned shared_memory_size = 1 << 16; #else static const unsigned shared_memory_size = 1 << 20; #endif static void stp_error_handler(const char *err_msg) { fprintf(stderr, "error: STP Error: %s\n", err_msg); abort(); } namespace klee { class STPSolverImpl : public SolverImpl { private: VC vc; STPBuilder *builder; time::Span timeout; bool useForkedSTP; SolverRunStatus runStatusCode; public: STPSolverImpl(bool _useForkedSTP, bool _optimizeDivides = true); ~STPSolverImpl(); char *getConstraintLog(const Query &); void setCoreSolverTimeout(time::Span timeout) { this->timeout = timeout; } 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 getOperationStatusCode(); }; STPSolverImpl::STPSolverImpl(bool _useForkedSTP, bool _optimizeDivides) : vc(vc_createValidityChecker()), builder(new STPBuilder(vc, _optimizeDivides)), useForkedSTP(_useForkedSTP), runStatusCode(SOLVER_RUN_STATUS_FAILURE) { assert(vc && "unable to create validity checker"); assert(builder && "unable to create STPBuilder"); // In newer versions of STP, a memory management mechanism has been // introduced that automatically invalidates certain C interface // pointers at vc_Destroy time. This caused double-free errors // due to the ExprHandle destructor also attempting to invalidate // the pointers using vc_DeleteExpr. By setting EXPRDELETE to 0 // we restore the old behaviour. vc_setInterfaceFlags(vc, EXPRDELETE, 0); make_division_total(vc); vc_registerErrorHandler(::stp_error_handler); if (useForkedSTP) { assert(shared_memory_id == 0 && "shared memory id already allocated"); shared_memory_id = shmget(IPC_PRIVATE, shared_memory_size, IPC_CREAT | 0700); if (shared_memory_id < 0) llvm::report_fatal_error("unable to allocate shared memory region"); shared_memory_ptr = (unsigned char *)shmat(shared_memory_id, NULL, 0); if (shared_memory_ptr == (void *)-1) llvm::report_fatal_error("unable to attach shared memory region"); shmctl(shared_memory_id, IPC_RMID, NULL); } } STPSolverImpl::~STPSolverImpl() { // Detach the memory region. shmdt(shared_memory_ptr); shared_memory_ptr = 0; shared_memory_id = 0; delete builder; vc_Destroy(vc); } /***/ char *STPSolverImpl::getConstraintLog(const Query &query) { vc_push(vc); for (std::vector >::const_iterator it = query.constraints.begin(), ie = query.constraints.end(); it != ie; ++it) vc_assertFormula(vc, builder->construct(*it)); assert(query.expr == ConstantExpr::alloc(0, Expr::Bool) && "Unexpected expression in query!"); char *buffer; unsigned long length; vc_printQueryStateToBuffer(vc, builder->getFalse(), &buffer, &length, false); vc_pop(vc); return buffer; } bool STPSolverImpl::computeTruth(const Query &query, bool &isValid) { std::vector objects; std::vector > values; bool hasSolution; if (!computeInitialValues(query, objects, values, hasSolution)) return false; isValid = !hasSolution; return true; } bool STPSolverImpl::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; } static SolverImpl::SolverRunStatus runAndGetCex(::VC vc, STPBuilder *builder, ::VCExpr q, const std::vector &objects, std::vector > &values, bool &hasSolution) { // XXX I want to be able to timeout here, safely hasSolution = !vc_query(vc, q); if (hasSolution) { 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++) { ExprHandle counter = vc_getCounterExample(vc, builder->getInitialRead(array, offset)); unsigned char val = getBVUnsigned(counter); data.push_back(val); } values.push_back(data); } } if (true == hasSolution) { return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE; } else { return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE; } } static void stpTimeoutHandler(int x) { _exit(52); } static SolverImpl::SolverRunStatus runAndGetCexForked(::VC vc, STPBuilder *builder, ::VCExpr q, const std::vector &objects, std::vector > &values, bool &hasSolution, time::Span timeout) { unsigned char *pos = shared_memory_ptr; unsigned sum = 0; for (std::vector::const_iterator it = objects.begin(), ie = objects.end(); it != ie; ++it) sum += (*it)->size; if (sum >= shared_memory_size) llvm::report_fatal_error("not enough shared memory for counterexample"); fflush(stdout); fflush(stderr); int pid = fork(); if (pid == -1) { klee_warning("fork failed (for STP) - %s", llvm::sys::StrError(errno).c_str()); if (!IgnoreSolverFailures) exit(1); return SolverImpl::SOLVER_RUN_STATUS_FORK_FAILED; } if (pid == 0) { if (timeout) { ::alarm(0); /* Turn off alarm so we can safely set signal handler */ ::signal(SIGALRM, stpTimeoutHandler); ::alarm(std::max(1u, static_cast(timeout.toSeconds()))); } unsigned res = vc_query(vc, q); if (!res) { for (std::vector::const_iterator it = objects.begin(), ie = objects.end(); it != ie; ++it) { const Array *array = *it; for (unsigned offset = 0; offset < array->size; offset++) { ExprHandle counter = vc_getCounterExample(vc, builder->getInitialRead(array, offset)); *pos++ = getBVUnsigned(counter); } } } _exit(res); } else { int status; pid_t res; do { res = waitpid(pid, &status, 0); } while (res < 0 && errno == EINTR); if (res < 0) { klee_warning("waitpid() for STP failed"); if (!IgnoreSolverFailures) exit(1); return SolverImpl::SOLVER_RUN_STATUS_WAITPID_FAILED; } // From timed_run.py: It appears that linux at least will on // "occasion" return a status when the process was terminated by a // signal, so test signal first. if (WIFSIGNALED(status) || !WIFEXITED(status)) { klee_warning("STP did not return successfully. Most likely you forgot " "to run 'ulimit -s unlimited'"); if (!IgnoreSolverFailures) { exit(1); } return SolverImpl::SOLVER_RUN_STATUS_INTERRUPTED; } int exitcode = WEXITSTATUS(status); if (exitcode == 0) { hasSolution = true; } else if (exitcode == 1) { hasSolution = false; } else if (exitcode == 52) { klee_warning("STP timed out"); // mark that a timeout occurred return SolverImpl::SOLVER_RUN_STATUS_TIMEOUT; } else { klee_warning("STP did not return a recognized code"); if (!IgnoreSolverFailures) exit(1); return SolverImpl::SOLVER_RUN_STATUS_UNEXPECTED_EXIT_CODE; } if (hasSolution) { values = std::vector >(objects.size()); unsigned i = 0; for (std::vector::const_iterator it = objects.begin(), ie = objects.end(); it != ie; ++it) { const Array *array = *it; std::vector &data = values[i++]; data.insert(data.begin(), pos, pos + array->size); pos += array->size; } } if (true == hasSolution) { return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE; } else { return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE; } } } bool STPSolverImpl::computeInitialValues( const Query &query, const std::vector &objects, std::vector > &values, bool &hasSolution) { runStatusCode = SOLVER_RUN_STATUS_FAILURE; TimerStatIncrementer t(stats::queryTime); vc_push(vc); for (ConstraintManager::const_iterator it = query.constraints.begin(), ie = query.constraints.end(); it != ie; ++it) vc_assertFormula(vc, builder->construct(*it)); ++stats::queries; ++stats::queryCounterexamples; ExprHandle stp_e = builder->construct(query.expr); if (DebugDumpSTPQueries) { char *buf; unsigned long len; vc_printQueryStateToBuffer(vc, stp_e, &buf, &len, false); klee_warning("STP query:\n%.*s\n", (unsigned)len, buf); } bool success; if (useForkedSTP) { runStatusCode = runAndGetCexForked(vc, builder, stp_e, objects, values, hasSolution, timeout); success = ((SOLVER_RUN_STATUS_SUCCESS_SOLVABLE == runStatusCode) || (SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE == runStatusCode)); } else { runStatusCode = runAndGetCex(vc, builder, stp_e, objects, values, hasSolution); success = true; } if (success) { if (hasSolution) ++stats::queriesInvalid; else ++stats::queriesValid; } vc_pop(vc); return success; } SolverImpl::SolverRunStatus STPSolverImpl::getOperationStatusCode() { return runStatusCode; } STPSolver::STPSolver(bool useForkedSTP, bool optimizeDivides) : Solver(new STPSolverImpl(useForkedSTP, optimizeDivides)) {} char *STPSolver::getConstraintLog(const Query &query) { return impl->getConstraintLog(query); } void STPSolver::setCoreSolverTimeout(time::Span timeout) { impl->setCoreSolverTimeout(timeout); } } #endif // ENABLE_STP