12#include <llvm/ADT/ArrayRef.h>
13#include <llvm/ADT/SmallString.h>
14#include <llvm/ADT/SmallVector.h>
15#include <llvm/ADT/StringRef.h>
16#include <llvm/Support/CommandLine.h>
17#include <llvm/Support/ErrorOr.h>
18#include <llvm/Support/FileSystem.h>
19#include <llvm/Support/MemoryBuffer.h>
20#include <llvm/Support/Path.h>
21#include <llvm/Support/PrettyStackTrace.h>
22#include <llvm/Support/Program.h>
23#include <llvm/Support/Signals.h>
24#include <llvm/Support/raw_ostream.h>
30#include <system_error>
38enum class SatResult { Sat, Unsat, Unknown };
40struct StageExpectation {
42 std::string stageName;
44 bool hasExpected =
false;
47struct ScriptMetadata {
48 SmallVector<StageExpectation> stages;
49 size_t checkSatCount = 0;
52struct SolverInvocationResult {
54 bool executionFailed =
false;
55 std::string errorMessage;
56 std::string stdoutText;
57 std::string stderrText;
60struct TempFileCleanup {
61 SmallVector<SmallString<128>> paths;
64 for (
const SmallString<128> &path : paths) {
65 (void)sys::fs::remove(path);
70static cl::opt<std::string> InputFilename(cl::Positional, cl::Required);
71static cl::opt<std::string>
72 SolverBinary(
"solver-binary", cl::desc(
"SMT solver executable"), cl::init(
"z3"));
73static cl::opt<bool> Quiet(
"quiet", cl::desc(
"Suppress per-stage summaries"));
75 DumpRawOutput(
"dump-raw-output", cl::desc(
"Print raw solver stdout after the stage summaries"));
77StringRef stringify(SatResult result) {
81 case SatResult::Unsat:
83 case SatResult::Unknown:
86 llvm_unreachable(
"unknown sat result");
89std::optional<SatResult> parseSatResult(StringRef text) {
91 return SatResult::Sat;
93 if (text ==
"unsat") {
94 return SatResult::Unsat;
96 if (text ==
"unknown") {
97 return SatResult::Unknown;
102Expected<std::string> readInput(StringRef inputFilename) {
103 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = MemoryBuffer::getFileOrSTDIN(inputFilename);
105 return createStringError(buffer.getError(),
"failed to read input '%s'", inputFilename.data());
107 return std::string(buffer.get()->getBuffer());
110Expected<ScriptMetadata> scanScript(StringRef script) {
111 ScriptMetadata metadata;
112 std::optional<std::string> currentRoot;
113 std::optional<std::string> pendingInfoStage;
114 std::optional<SatResult> pendingInfoExpected;
116 auto parseQuotedString = [&](StringRef value, StringRef fullLine) -> Expected<std::string> {
117 StringRef trimmed = value.trim();
118 if (trimmed.size() < 2 || !trimmed.starts_with(
"\"") || !trimmed.ends_with(
"\"")) {
119 return createStringError(
120 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", fullLine.str().c_str()
123 return trimmed.drop_front().drop_back().str();
126 SmallVector<StringRef> lines;
127 script.split(lines,
'\n');
128 for (StringRef line : lines) {
129 StringRef trimmed = line.ltrim();
130 if (trimmed.starts_with(
"(set-info")) {
131 StringRef body = trimmed.drop_front(StringRef(
"(set-info").size()).trim();
132 if (!body.ends_with(
")")) {
133 return createStringError(
134 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", trimmed.str().c_str()
137 body = body.drop_back().trim();
138 size_t splitPos = body.find_first_of(
" \t");
139 if (splitPos == StringRef::npos) {
140 return createStringError(
141 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", trimmed.str().c_str()
144 StringRef key = body.take_front(splitPos).trim();
145 StringRef value = body.drop_front(splitPos).trim();
146 if (key ==
":status") {
147 pendingInfoExpected = parseSatResult(value);
148 if (!pendingInfoExpected) {
149 return createStringError(
150 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", trimmed.str().c_str()
153 }
else if (key ==
":llzk-stage") {
154 auto parsed = parseQuotedString(value, trimmed);
156 return parsed.takeError();
158 pendingInfoStage = std::move(*parsed);
159 }
else if (key ==
":llzk-root") {
160 auto parsed = parseQuotedString(value, trimmed);
162 return parsed.takeError();
164 currentRoot = std::move(*parsed);
168 if (trimmed ==
"(check-sat)") {
169 std::optional<std::string> stageName = pendingInfoStage;
170 std::optional<SatResult> expected = pendingInfoExpected;
172 ++metadata.checkSatCount;
174 if (stageName && !expected) {
175 return createStringError(
176 inconvertibleErrorCode(),
"missing expected result before check-sat for stage '%s'",
181 if (stageName || expected) {
182 metadata.stages.push_back(
184 currentRoot.value_or(
""), stageName.value_or(
""),
185 expected.value_or(SatResult::Unknown), expected.has_value()
190 pendingInfoStage.reset();
191 pendingInfoExpected.reset();
196 if (!metadata.stages.empty() && metadata.stages.size() != metadata.checkSatCount) {
197 return createStringError(
198 inconvertibleErrorCode(),
"check metadata count (%zu) does not match check-sat count (%zu)",
199 metadata.stages.size(), metadata.checkSatCount
206std::string stripMetadataForSolver(StringRef script) {
207 std::string sanitized;
208 raw_string_ostream os(sanitized);
210 SmallVector<StringRef> lines;
211 script.split(lines,
'\n');
212 for (StringRef line : lines) {
213 StringRef trimmed = line.ltrim();
214 bool shouldStrip =
false;
215 if (trimmed.starts_with(
"(set-info")) {
216 StringRef body = trimmed.drop_front(StringRef(
"(set-info").size()).trim();
217 if (body.ends_with(
")")) {
218 body = body.drop_back().trim();
219 size_t splitPos = body.find_first_of(
" \t");
220 if (splitPos != StringRef::npos) {
221 StringRef key = body.take_front(splitPos).trim();
222 shouldStrip = key ==
":status" || key ==
":llzk-stage" || key ==
":llzk-root";
235std::string formatStageLabel(
const StageExpectation &stage,
size_t index) {
236 if (!stage.stageName.empty() && !stage.rootName.empty()) {
237 return (Twine(stage.rootName) +
"/" + stage.stageName).str();
239 if (!stage.stageName.empty()) {
240 return stage.stageName;
242 return (
"check[" + std::to_string(index) +
"]");
245Expected<std::string> readWholeFile(StringRef path) {
246 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = MemoryBuffer::getFile(path);
248 return createStringError(buffer.getError(),
"failed to read '%s'", path.data());
250 return std::string(buffer.get()->getBuffer());
253Expected<std::string> resolveSolverPath(StringRef solverBinary) {
254 if (sys::path::has_parent_path(solverBinary)) {
255 return solverBinary.str();
257 ErrorOr<std::string> found = sys::findProgramByName(solverBinary);
259 return createStringError(
260 found.getError(),
"failed to find solver binary '%s'", solverBinary.data()
266Expected<SmallString<128>> createTempFile(StringRef prefix, StringRef suffix, StringRef contents) {
267 SmallString<128> path;
269 std::error_code ec = sys::fs::createTemporaryFile(prefix, suffix, fd, path);
271 return createStringError(ec,
"failed to create temporary file");
274 raw_fd_ostream os(fd,
true);
275 if (!contents.empty()) {
279 if (os.has_error()) {
280 return createStringError(inconvertibleErrorCode(),
"failed to write temporary file");
286Expected<SolverInvocationResult> runSolver(StringRef solverPath, StringRef script) {
287 SolverInvocationResult result;
288 TempFileCleanup cleanup;
290 auto stdoutFile = createTempFile(
"llzk-smt-check-stdout",
"txt",
"");
292 return stdoutFile.takeError();
294 auto stderrFile = createTempFile(
"llzk-smt-check-stderr",
"txt",
"");
296 return stderrFile.takeError();
298 cleanup.paths.push_back(*stdoutFile);
299 cleanup.paths.push_back(*stderrFile);
301 std::array<std::optional<StringRef>, 3> redirects = {
302 std::nullopt, StringRef(stdoutFile->data(), stdoutFile->size()),
303 StringRef(stderrFile->data(), stderrFile->size())
306 SmallVector<StringRef> args;
307 args.push_back(solverPath);
308 auto tempInput = createTempFile(
"llzk-smt-check-input",
"smt2", script);
310 return tempInput.takeError();
312 cleanup.paths.push_back(*tempInput);
313 args.push_back(
"-smt2");
314 args.push_back(StringRef(tempInput->data(), tempInput->size()));
316 std::string errorMessage;
317 bool executionFailed =
false;
318 result.exitCode = sys::ExecuteAndWait(
319 solverPath, args, std::nullopt, redirects, 0, 0, &errorMessage, &executionFailed
321 result.executionFailed = executionFailed;
322 result.errorMessage = std::move(errorMessage);
324 auto stdoutText = readWholeFile(*stdoutFile);
326 return stdoutText.takeError();
328 result.stdoutText = std::move(*stdoutText);
330 auto stderrText = readWholeFile(*stderrFile);
332 return stderrText.takeError();
334 result.stderrText = std::move(*stderrText);
339Expected<std::pair<SmallVector<SatResult>, SmallVector<std::string>>>
340parseSolverStdout(StringRef text) {
341 SmallVector<SatResult> results;
342 SmallVector<std::string> extraLines;
343 SmallVector<StringRef> lines;
344 text.split(lines,
'\n');
345 for (StringRef line : lines) {
346 StringRef trimmed = line.trim();
347 if (trimmed.empty()) {
350 if (std::optional<SatResult> result = parseSatResult(trimmed)) {
351 results.push_back(*result);
355 std::string lowered = trimmed.lower();
356 if (StringRef(lowered).starts_with(
"z3 ")) {
359 extraLines.push_back(trimmed.str());
361 return std::make_pair(std::move(results), std::move(extraLines));
364void printSolverFailure(
const SolverInvocationResult &invocation) {
365 if (!invocation.errorMessage.empty()) {
366 errs() <<
"llzk-smt-check: " << invocation.errorMessage <<
'\n';
368 if (!invocation.stderrText.empty()) {
369 errs() << invocation.stderrText;
370 if (!invocation.stderrText.ends_with(
'\n')) {
378int main(
int argc,
char **argv) {
379 sys::PrintStackTraceOnErrorSignal(StringRef());
382 " and include the crash backtrace, relevant SMT-LIB inputs, and associated run script(s).\n"
385 cl::ParseCommandLineOptions(
387 "llzk-smt-check: run an SMT solver on staged SMT-LIB and validate per-stage results.\n"
390 auto input = readInput(InputFilename);
392 errs() << toString(input.takeError()) <<
'\n';
396 auto metadata = scanScript(*input);
398 errs() <<
"llzk-smt-check: " << toString(metadata.takeError()) <<
'\n';
402 auto solverPath = resolveSolverPath(SolverBinary);
404 errs() <<
"llzk-smt-check: " << toString(solverPath.takeError()) <<
'\n';
408 std::string solverScript = stripMetadataForSolver(*input);
409 auto invocation = runSolver(*solverPath, solverScript);
411 errs() <<
"llzk-smt-check: " << toString(invocation.takeError()) <<
'\n';
414 if (invocation->executionFailed || invocation->exitCode != 0) {
415 errs() <<
"llzk-smt-check: solver exited with code " << invocation->exitCode <<
'\n';
416 printSolverFailure(*invocation);
420 auto parsedStdout = parseSolverStdout(invocation->stdoutText);
422 errs() <<
"llzk-smt-check: " << toString(parsedStdout.takeError()) <<
'\n';
426 SmallVector<SatResult> solverResults = std::move(parsedStdout->first);
427 SmallVector<std::string> extraLines = std::move(parsedStdout->second);
428 if (!DumpRawOutput && !extraLines.empty()) {
429 errs() <<
"llzk-smt-check: unexpected solver stdout:\n";
430 for (
const std::string &line : extraLines) {
431 errs() << line <<
'\n';
435 if (solverResults.size() != metadata->checkSatCount) {
436 errs() <<
"llzk-smt-check: solver returned " << solverResults.size() <<
" result(s) for "
437 << metadata->checkSatCount <<
" check-sat command(s)\n";
441 SmallVector<std::string> mismatches;
442 SmallVector<std::string> summaries;
443 for (
size_t i = 0; i < solverResults.size(); ++i) {
445 if (metadata->stages.empty() || metadata->stages[i].stageName.empty()) {
446 label =
"check[" + std::to_string(i) +
"]";
448 label = formatStageLabel(metadata->stages[i], i);
451 std::string summary = (Twine(label) +
": " + stringify(solverResults[i])).str();
452 if (!metadata->stages.empty() && metadata->stages[i].hasExpected) {
454 (Twine(summary) +
" (expected " + stringify(metadata->stages[i].expected) +
")").str();
456 summaries.push_back(std::move(summary));
458 if (!metadata->stages.empty() && metadata->stages[i].hasExpected &&
459 solverResults[i] != metadata->stages[i].expected) {
460 mismatches.push_back((Twine(label) +
": got " + stringify(solverResults[i]) +
", expected " +
461 stringify(metadata->stages[i].expected))
466 if (DumpRawOutput && !invocation->stdoutText.empty()) {
467 outs() <<
"--- raw solver stdout ---\n" << invocation->stdoutText;
468 if (!invocation->stdoutText.ends_with(
'\n')) {
473 if (!mismatches.empty()) {
474 for (
const std::string &summary : summaries) {
475 errs() << summary <<
'\n';
477 errs() <<
"llzk-smt-check: stage result mismatch:\n";
478 for (
const std::string &mismatch : mismatches) {
479 errs() << mismatch <<
'\n';
484 for (
const std::string &summary : summaries) {
485 outs() << summary <<
'\n';
int main(int argc, char **argv)