klee
STPSolver.cpp
Go to the documentation of this file.
1//===-- STPSolver.cpp -----------------------------------------------------===//
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#include "klee/Config/config.h"
10
11#ifdef ENABLE_STP
12
13#include "STPBuilder.h"
14#include "STPSolver.h"
15
18#include "klee/Expr/ExprUtil.h"
22
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Support/Errno.h"
25
26#include <csignal>
27#include <sys/ipc.h>
28#include <sys/shm.h>
29#include <sys/wait.h>
30#include <unistd.h>
31
32namespace {
33
34llvm::cl::opt<bool> DebugDumpSTPQueries(
35 "debug-dump-stp-queries", llvm::cl::init(false),
36 llvm::cl::desc("Dump every STP query to stderr (default=false)"),
37 llvm::cl::cat(klee::SolvingCat));
38
39llvm::cl::opt<bool> IgnoreSolverFailures(
40 "ignore-solver-failures", llvm::cl::init(false),
41 llvm::cl::desc("Ignore any STP solver failures (default=false)"),
42 llvm::cl::cat(klee::SolvingCat));
43}
44
45#define vc_bvBoolExtract IAMTHESPAWNOFSATAN
46
47static unsigned char *shared_memory_ptr = nullptr;
48static int shared_memory_id = 0;
49// Darwin by default has a very small limit on the maximum amount of shared
50// memory, which will quickly be exhausted by KLEE running its tests in
51// parallel. For now, we work around this by just requesting a smaller size --
52// in practice users hitting this limit on counterexample sizes probably already
53// are hitting more serious scalability issues.
54#ifdef __APPLE__
55static const unsigned shared_memory_size = 1 << 16;
56#else
57static const unsigned shared_memory_size = 1 << 20;
58#endif
59
60static void stp_error_handler(const char *err_msg) {
61 fprintf(stderr, "error: STP Error: %s\n", err_msg);
62 abort();
63}
64
65namespace klee {
66
67class STPSolverImpl : public SolverImpl {
68private:
69 VC vc;
70 STPBuilder *builder;
71 time::Span timeout;
72 bool useForkedSTP;
73 SolverRunStatus runStatusCode;
74
75public:
76 explicit STPSolverImpl(bool useForkedSTP, bool optimizeDivides = true);
77 ~STPSolverImpl() override;
78
79 char *getConstraintLog(const Query &) override;
80 void setCoreSolverTimeout(time::Span timeout) override { this->timeout = timeout; }
81
82 bool computeTruth(const Query &, bool &isValid) override;
83 bool computeValue(const Query &, ref<Expr> &result) override;
84 bool computeInitialValues(const Query &,
85 const std::vector<const Array *> &objects,
86 std::vector<std::vector<unsigned char>> &values,
87 bool &hasSolution) override;
88 SolverRunStatus getOperationStatusCode() override;
89};
90
91STPSolverImpl::STPSolverImpl(bool useForkedSTP, bool optimizeDivides)
92 : vc(vc_createValidityChecker()),
93 builder(new STPBuilder(vc, optimizeDivides)),
94 useForkedSTP(useForkedSTP), runStatusCode(SOLVER_RUN_STATUS_FAILURE) {
95 assert(vc && "unable to create validity checker");
96 assert(builder && "unable to create STPBuilder");
97
98 // In newer versions of STP, a memory management mechanism has been
99 // introduced that automatically invalidates certain C interface
100 // pointers at vc_Destroy time. This caused double-free errors
101 // due to the ExprHandle destructor also attempting to invalidate
102 // the pointers using vc_DeleteExpr. By setting EXPRDELETE to 0
103 // we restore the old behaviour.
104 vc_setInterfaceFlags(vc, EXPRDELETE, 0);
105
106 make_division_total(vc);
107
108 vc_registerErrorHandler(::stp_error_handler);
109
110 if (useForkedSTP) {
111 assert(shared_memory_id == 0 && "shared memory id already allocated");
112 shared_memory_id =
113 shmget(IPC_PRIVATE, shared_memory_size, IPC_CREAT | 0700);
114 if (shared_memory_id < 0)
115 llvm::report_fatal_error("unable to allocate shared memory region");
116 shared_memory_ptr = (unsigned char *)shmat(shared_memory_id, nullptr, 0);
117 if (shared_memory_ptr == (void *)-1)
118 llvm::report_fatal_error("unable to attach shared memory region");
119 shmctl(shared_memory_id, IPC_RMID, nullptr);
120 }
121}
122
123STPSolverImpl::~STPSolverImpl() {
124 // Detach the memory region.
125 shmdt(shared_memory_ptr);
126 shared_memory_ptr = nullptr;
127 shared_memory_id = 0;
128
129 delete builder;
130
131 vc_Destroy(vc);
132}
133
134/***/
135
136char *STPSolverImpl::getConstraintLog(const Query &query) {
137 vc_push(vc);
138
139 for (const auto &constraint : query.constraints)
140 vc_assertFormula(vc, builder->construct(constraint));
141 assert(query.expr == ConstantExpr::alloc(0, Expr::Bool) &&
142 "Unexpected expression in query!");
143
144 char *buffer;
145 unsigned long length;
146 vc_printQueryStateToBuffer(vc, builder->getFalse(), &buffer, &length, false);
147 vc_pop(vc);
148
149 return buffer;
150}
151
152bool STPSolverImpl::computeTruth(const Query &query, bool &isValid) {
153 std::vector<const Array *> objects;
154 std::vector<std::vector<unsigned char>> values;
155 bool hasSolution;
156
157 if (!computeInitialValues(query, objects, values, hasSolution))
158 return false;
159
160 isValid = !hasSolution;
161 return true;
162}
163
164bool STPSolverImpl::computeValue(const Query &query, ref<Expr> &result) {
165 std::vector<const Array *> objects;
166 std::vector<std::vector<unsigned char>> values;
167 bool hasSolution;
168
169 // Find the object used in the expression, and compute an assignment
170 // for them.
171 findSymbolicObjects(query.expr, objects);
172 if (!computeInitialValues(query.withFalse(), objects, values, hasSolution))
173 return false;
174 assert(hasSolution && "state has invalid constraint set");
175
176 // Evaluate the expression with the computed assignment.
177 Assignment a(objects, values);
178 result = a.evaluate(query.expr);
179
180 return true;
181}
182
183static SolverImpl::SolverRunStatus
184runAndGetCex(::VC vc, STPBuilder *builder, ::VCExpr q,
185 const std::vector<const Array *> &objects,
186 std::vector<std::vector<unsigned char>> &values,
187 bool &hasSolution) {
188 // XXX I want to be able to timeout here, safely
189 hasSolution = !vc_query(vc, q);
190
191 if (!hasSolution)
192 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE;
193
194 values.reserve(objects.size());
195 unsigned i = 0; // FIXME C++17: use reference from emplace_back()
196 for (const auto object : objects) {
197 values.emplace_back(object->size);
198
199 for (unsigned offset = 0; offset < object->size; offset++) {
200 ExprHandle counter =
201 vc_getCounterExample(vc, builder->getInitialRead(object, offset));
202 values[i][offset] = static_cast<unsigned char>(getBVUnsigned(counter));
203 }
204 ++i;
205 }
206
207 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE;
208}
209
210static void stpTimeoutHandler(int x) { _exit(52); }
211
212static SolverImpl::SolverRunStatus
213runAndGetCexForked(::VC vc, STPBuilder *builder, ::VCExpr q,
214 const std::vector<const Array *> &objects,
215 std::vector<std::vector<unsigned char>> &values,
216 bool &hasSolution, time::Span timeout) {
217 unsigned char *pos = shared_memory_ptr;
218 unsigned sum = 0;
219 for (const auto object : objects)
220 sum += object->size;
221 if (sum >= shared_memory_size)
222 llvm::report_fatal_error("not enough shared memory for counterexample");
223
224 fflush(stdout);
225 fflush(stderr);
226
227 // fork solver
228 int pid = fork();
229 // - error
230 if (pid == -1) {
231 klee_warning("fork failed (for STP) - %s", llvm::sys::StrError(errno).c_str());
232 if (!IgnoreSolverFailures)
233 exit(1);
234 return SolverImpl::SOLVER_RUN_STATUS_FORK_FAILED;
235 }
236 // - child (solver)
237 if (pid == 0) {
238 if (timeout) {
239 ::alarm(0); /* Turn off alarm so we can safely set signal handler */
240 ::signal(SIGALRM, stpTimeoutHandler);
241 ::alarm(std::max(1u, static_cast<unsigned>(timeout.toSeconds())));
242 }
243 int res = vc_query(vc, q);
244 if (!res) {
245 for (const auto object : objects) {
246 for (unsigned offset = 0; offset < object->size; offset++) {
247 ExprHandle counter =
248 vc_getCounterExample(vc, builder->getInitialRead(object, offset));
249 *pos++ = static_cast<unsigned char>(getBVUnsigned(counter));
250 }
251 }
252 }
253 _exit(res);
254 // - parent
255 } else {
256 int status;
257 pid_t res;
258
259 do {
260 res = waitpid(pid, &status, 0);
261 } while (res < 0 && errno == EINTR);
262
263 if (res < 0) {
264 klee_warning("waitpid() for STP failed");
265 if (!IgnoreSolverFailures)
266 exit(1);
267 return SolverImpl::SOLVER_RUN_STATUS_WAITPID_FAILED;
268 }
269
270 // From timed_run.py: It appears that linux at least will on
271 // "occasion" return a status when the process was terminated by a
272 // signal, so test signal first.
273 if (WIFSIGNALED(status) || !WIFEXITED(status)) {
274 klee_warning("STP did not return successfully. Most likely you forgot "
275 "to run 'ulimit -s unlimited'");
276 if (!IgnoreSolverFailures) {
277 exit(1);
278 }
279 return SolverImpl::SOLVER_RUN_STATUS_INTERRUPTED;
280 }
281
282 int exitcode = WEXITSTATUS(status);
283
284 // solvable
285 if (exitcode == 0) {
286 hasSolution = true;
287
288 values.reserve(objects.size());
289 for (const auto object : objects) {
290 values.emplace_back(pos, pos + object->size);
291 pos += object->size;
292 }
293
294 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_SOLVABLE;
295 }
296
297 // unsolvable
298 if (exitcode == 1) {
299 hasSolution = false;
300 return SolverImpl::SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE;
301 }
302
303 // timeout
304 if (exitcode == 52) {
305 klee_warning("STP timed out");
306 // mark that a timeout occurred
307 return SolverImpl::SOLVER_RUN_STATUS_TIMEOUT;
308 }
309
310 // unknown return code
311 klee_warning("STP did not return a recognized code");
312 if (!IgnoreSolverFailures)
313 exit(1);
314 return SolverImpl::SOLVER_RUN_STATUS_UNEXPECTED_EXIT_CODE;
315 }
316}
317
318bool STPSolverImpl::computeInitialValues(
319 const Query &query, const std::vector<const Array *> &objects,
320 std::vector<std::vector<unsigned char>> &values, bool &hasSolution) {
321 runStatusCode = SOLVER_RUN_STATUS_FAILURE;
322 TimerStatIncrementer t(stats::queryTime);
323
324 vc_push(vc);
325
326 for (const auto &constraint : query.constraints)
327 vc_assertFormula(vc, builder->construct(constraint));
328
331
332 ExprHandle stp_e = builder->construct(query.expr);
333
334 if (DebugDumpSTPQueries) {
335 char *buf;
336 unsigned long len;
337 vc_printQueryStateToBuffer(vc, stp_e, &buf, &len, false);
338 klee_warning("STP query:\n%.*s\n", (unsigned)len, buf);
339 free(buf);
340 }
341
342 bool success;
343 if (useForkedSTP) {
344 runStatusCode = runAndGetCexForked(vc, builder, stp_e, objects, values,
345 hasSolution, timeout);
346 success = ((SOLVER_RUN_STATUS_SUCCESS_SOLVABLE == runStatusCode) ||
347 (SOLVER_RUN_STATUS_SUCCESS_UNSOLVABLE == runStatusCode));
348 } else {
349 runStatusCode =
350 runAndGetCex(vc, builder, stp_e, objects, values, hasSolution);
351 success = true;
352 }
353
354 if (success) {
355 if (hasSolution)
357 else
359 }
360
361 vc_pop(vc);
362
363 return success;
364}
365
366SolverImpl::SolverRunStatus STPSolverImpl::getOperationStatusCode() {
367 return runStatusCode;
368}
369
370STPSolver::STPSolver(bool useForkedSTP, bool optimizeDivides)
371 : Solver(new STPSolverImpl(useForkedSTP, optimizeDivides)) {}
372
373char *STPSolver::getConstraintLog(const Query &query) {
374 return impl->getConstraintLog(query);
375}
376
377void STPSolver::setCoreSolverTimeout(time::Span timeout) {
378 impl->setCoreSolverTimeout(timeout);
379}
380
381} // klee
382#endif // ENABLE_STP
void klee_warning(char *name)
Definition: klee-replay.c:432
ref< Expr > ExprHandle
Definition: Parser.h:27
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
llvm::cl::OptionCategory SolvingCat