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 : std::uint8_t { 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 std::error_code ec = sys::fs::remove(path);
66 if (ec && ec != std::errc::no_such_file_or_directory) {
67 errs() <<
"llzk-smt-check: failed to remove temporary file '" << path
68 <<
"': " << ec.message() <<
'\n';
74static cl::opt<std::string> InputFilename(cl::Positional, cl::Required);
75static cl::opt<std::string>
76 SolverBinary(
"solver-binary", cl::desc(
"SMT solver executable"), cl::init(
"z3"));
77static cl::opt<bool> Quiet(
"quiet", cl::desc(
"Suppress per-stage summaries"));
79 DumpRawOutput(
"dump-raw-output", cl::desc(
"Print raw solver stdout after the stage summaries"));
81StringRef stringify(SatResult result) {
85 case SatResult::Unsat:
87 case SatResult::Unknown:
90 llvm_unreachable(
"unknown sat result");
93std::optional<SatResult> parseSatResult(StringRef text) {
95 return SatResult::Sat;
97 if (text ==
"unsat") {
98 return SatResult::Unsat;
100 if (text ==
"unknown") {
101 return SatResult::Unknown;
106Expected<std::string> readInput(StringRef inputFilename) {
107 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = MemoryBuffer::getFileOrSTDIN(inputFilename);
109 return createStringError(buffer.getError(),
"failed to read input '%s'", inputFilename.data());
111 return std::string(buffer.get()->getBuffer());
114Expected<ScriptMetadata> scanScript(StringRef script) {
115 ScriptMetadata metadata;
116 std::optional<std::string> currentRoot;
117 std::optional<std::string> pendingInfoStage;
118 std::optional<SatResult> pendingInfoExpected;
120 auto parseQuotedString = [&](StringRef value, StringRef fullLine) -> Expected<std::string> {
121 StringRef trimmed = value.trim();
122 if (trimmed.size() < 2 || !trimmed.starts_with(
"\"") || !trimmed.ends_with(
"\"")) {
123 return createStringError(
124 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", fullLine.str().c_str()
127 return trimmed.drop_front().drop_back().str();
130 SmallVector<StringRef> lines;
131 script.split(lines,
'\n');
132 for (StringRef line : lines) {
133 StringRef trimmed = line.ltrim();
134 if (trimmed.starts_with(
"(set-info")) {
135 StringRef body = trimmed.drop_front(StringRef(
"(set-info").size()).trim();
136 if (!body.ends_with(
")")) {
137 return createStringError(
138 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", trimmed.str().c_str()
141 body = body.drop_back().trim();
142 size_t splitPos = body.find_first_of(
" \t");
143 if (splitPos == StringRef::npos) {
144 return createStringError(
145 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", trimmed.str().c_str()
148 StringRef key = body.take_front(splitPos).trim();
149 StringRef value = body.drop_front(splitPos).trim();
150 if (key ==
":status") {
151 pendingInfoExpected = parseSatResult(value);
152 if (!pendingInfoExpected) {
153 return createStringError(
154 inconvertibleErrorCode(),
"invalid set-info annotation: '%s'", trimmed.str().c_str()
157 }
else if (key ==
":llzk-stage") {
158 auto parsed = parseQuotedString(value, trimmed);
160 return parsed.takeError();
162 pendingInfoStage = std::move(*parsed);
163 }
else if (key ==
":llzk-root") {
164 auto parsed = parseQuotedString(value, trimmed);
166 return parsed.takeError();
168 currentRoot = std::move(*parsed);
172 if (trimmed ==
"(check-sat)") {
173 std::optional<std::string> stageName = pendingInfoStage;
174 std::optional<SatResult> expected = pendingInfoExpected;
176 ++metadata.checkSatCount;
178 if (stageName && !expected) {
179 return createStringError(
180 inconvertibleErrorCode(),
"missing expected result before check-sat for stage '%s'",
185 if (stageName || expected) {
186 metadata.stages.push_back(
188 currentRoot.value_or(
""), stageName.value_or(
""),
189 expected.value_or(SatResult::Unknown), expected.has_value()
194 pendingInfoStage.reset();
195 pendingInfoExpected.reset();
200 if (!metadata.stages.empty() && metadata.stages.size() != metadata.checkSatCount) {
201 return createStringError(
202 inconvertibleErrorCode(),
"check metadata count (%zu) does not match check-sat count (%zu)",
203 metadata.stages.size(), metadata.checkSatCount
210std::string stripMetadataForSolver(StringRef script) {
211 std::string sanitized;
212 raw_string_ostream os(sanitized);
214 SmallVector<StringRef> lines;
215 script.split(lines,
'\n');
216 for (StringRef line : lines) {
217 StringRef trimmed = line.ltrim();
218 bool shouldStrip =
false;
219 if (trimmed.starts_with(
"(set-info")) {
220 StringRef body = trimmed.drop_front(StringRef(
"(set-info").size()).trim();
221 if (body.ends_with(
")")) {
222 body = body.drop_back().trim();
223 size_t splitPos = body.find_first_of(
" \t");
224 if (splitPos != StringRef::npos) {
225 StringRef key = body.take_front(splitPos).trim();
226 shouldStrip = key ==
":status" || key ==
":llzk-stage" || key ==
":llzk-root";
239std::string formatStageLabel(
const StageExpectation &stage,
size_t index) {
240 if (!stage.stageName.empty() && !stage.rootName.empty()) {
241 return (Twine(stage.rootName) +
"/" + stage.stageName).str();
243 if (!stage.stageName.empty()) {
244 return stage.stageName;
246 return (
"check[" + std::to_string(index) +
"]");
249Expected<std::string> readWholeFile(StringRef path) {
250 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = MemoryBuffer::getFile(path);
252 return createStringError(buffer.getError(),
"failed to read '%s'", path.data());
254 return std::string(buffer.get()->getBuffer());
257Expected<std::string> resolveSolverPath(StringRef solverBinary) {
258 if (sys::path::has_parent_path(solverBinary)) {
259 return solverBinary.str();
261 ErrorOr<std::string> found = sys::findProgramByName(solverBinary);
263 return createStringError(
264 found.getError(),
"failed to find solver binary '%s'", solverBinary.data()
270Expected<SmallString<128>> createTempFile(StringRef prefix, StringRef suffix, StringRef contents) {
271 SmallString<128> path;
273 std::error_code ec = sys::fs::createTemporaryFile(prefix, suffix, fd, path);
275 return createStringError(ec,
"failed to create temporary file");
278 raw_fd_ostream os(fd,
true);
279 if (!contents.empty()) {
283 if (os.has_error()) {
284 return createStringError(inconvertibleErrorCode(),
"failed to write temporary file");
290Expected<SolverInvocationResult> runSolver(StringRef solverPath, StringRef script) {
291 SolverInvocationResult result;
292 TempFileCleanup cleanup;
294 auto stdoutFile = createTempFile(
"llzk-smt-check-stdout",
"txt",
"");
296 return stdoutFile.takeError();
298 auto stderrFile = createTempFile(
"llzk-smt-check-stderr",
"txt",
"");
300 return stderrFile.takeError();
302 cleanup.paths.push_back(*stdoutFile);
303 cleanup.paths.push_back(*stderrFile);
305 std::array<std::optional<StringRef>, 3> redirects = {
306 std::nullopt, StringRef(stdoutFile->data(), stdoutFile->size()),
307 StringRef(stderrFile->data(), stderrFile->size())
310 SmallVector<StringRef> args;
311 args.push_back(solverPath);
312 auto tempInput = createTempFile(
"llzk-smt-check-input",
"smt2", script);
314 return tempInput.takeError();
316 cleanup.paths.push_back(*tempInput);
317 args.push_back(
"-smt2");
318 args.push_back(StringRef(tempInput->data(), tempInput->size()));
320 std::string errorMessage;
321 bool executionFailed =
false;
322 result.exitCode = sys::ExecuteAndWait(
323 solverPath, args, std::nullopt, redirects, 0, 0, &errorMessage, &executionFailed
325 result.executionFailed = executionFailed;
326 result.errorMessage = std::move(errorMessage);
328 auto stdoutText = readWholeFile(*stdoutFile);
330 return stdoutText.takeError();
332 result.stdoutText = std::move(*stdoutText);
334 auto stderrText = readWholeFile(*stderrFile);
336 return stderrText.takeError();
338 result.stderrText = std::move(*stderrText);
343Expected<std::pair<SmallVector<SatResult>, SmallVector<std::string>>>
344parseSolverStdout(StringRef text) {
345 SmallVector<SatResult> results;
346 SmallVector<std::string> extraLines;
347 SmallVector<StringRef> lines;
348 text.split(lines,
'\n');
349 for (StringRef line : lines) {
350 StringRef trimmed = line.trim();
351 if (trimmed.empty()) {
354 if (std::optional<SatResult> result = parseSatResult(trimmed)) {
355 results.push_back(*result);
359 std::string lowered = trimmed.lower();
360 if (StringRef(lowered).starts_with(
"z3 ")) {
363 extraLines.push_back(trimmed.str());
365 return std::make_pair(std::move(results), std::move(extraLines));
368void printSolverFailure(
const SolverInvocationResult &invocation) {
369 if (!invocation.errorMessage.empty()) {
370 errs() <<
"llzk-smt-check: " << invocation.errorMessage <<
'\n';
372 if (!invocation.stderrText.empty()) {
373 errs() << invocation.stderrText;
374 if (!invocation.stderrText.ends_with(
'\n')) {
382static int runMain(
int argc,
char **argv) {
383 sys::PrintStackTraceOnErrorSignal(StringRef());
386 " and include the crash backtrace, relevant SMT-LIB inputs, and associated run script(s).\n"
389 cl::ParseCommandLineOptions(
391 "llzk-smt-check: run an SMT solver on staged SMT-LIB and validate per-stage results.\n"
394 auto input = readInput(InputFilename);
396 errs() << toString(input.takeError()) <<
'\n';
400 auto metadata = scanScript(*input);
402 errs() <<
"llzk-smt-check: " << toString(metadata.takeError()) <<
'\n';
406 auto solverPath = resolveSolverPath(SolverBinary);
408 errs() <<
"llzk-smt-check: " << toString(solverPath.takeError()) <<
'\n';
412 std::string solverScript = stripMetadataForSolver(*input);
413 auto invocation = runSolver(*solverPath, solverScript);
415 errs() <<
"llzk-smt-check: " << toString(invocation.takeError()) <<
'\n';
418 if (invocation->executionFailed || invocation->exitCode != 0) {
419 errs() <<
"llzk-smt-check: solver exited with code " << invocation->exitCode <<
'\n';
420 printSolverFailure(*invocation);
424 auto parsedStdout = parseSolverStdout(invocation->stdoutText);
426 errs() <<
"llzk-smt-check: " << toString(parsedStdout.takeError()) <<
'\n';
430 SmallVector<SatResult> solverResults = std::move(parsedStdout->first);
431 SmallVector<std::string> extraLines = std::move(parsedStdout->second);
432 if (!DumpRawOutput && !extraLines.empty()) {
433 errs() <<
"llzk-smt-check: unexpected solver stdout:\n";
434 for (
const std::string &line : extraLines) {
435 errs() << line <<
'\n';
439 if (solverResults.size() != metadata->checkSatCount) {
440 errs() <<
"llzk-smt-check: solver returned " << solverResults.size() <<
" result(s) for "
441 << metadata->checkSatCount <<
" check-sat command(s)\n";
445 SmallVector<std::string> mismatches;
446 SmallVector<std::string> summaries;
447 for (
size_t i = 0; i < solverResults.size(); ++i) {
449 if (metadata->stages.empty() || metadata->stages[i].stageName.empty()) {
450 label =
"check[" + std::to_string(i) +
"]";
452 label = formatStageLabel(metadata->stages[i], i);
455 std::string summary = (Twine(label) +
": " + stringify(solverResults[i])).str();
456 if (!metadata->stages.empty() && metadata->stages[i].hasExpected) {
458 (Twine(summary) +
" (expected " + stringify(metadata->stages[i].expected) +
")").str();
460 summaries.push_back(std::move(summary));
462 if (!metadata->stages.empty() && metadata->stages[i].hasExpected &&
463 solverResults[i] != metadata->stages[i].expected) {
464 mismatches.push_back((Twine(label) +
": got " + stringify(solverResults[i]) +
", expected " +
465 stringify(metadata->stages[i].expected))
470 if (DumpRawOutput && !invocation->stdoutText.empty()) {
471 outs() <<
"--- raw solver stdout ---\n" << invocation->stdoutText;
472 if (!invocation->stdoutText.ends_with(
'\n')) {
477 if (!mismatches.empty()) {
478 for (
const std::string &summary : summaries) {
479 errs() << summary <<
'\n';
481 errs() <<
"llzk-smt-check: stage result mismatch:\n";
482 for (
const std::string &mismatch : mismatches) {
483 errs() << mismatch <<
'\n';
488 for (
const std::string &summary : summaries) {
489 outs() << summary <<
'\n';
495int main(
int argc,
char **argv)
noexcept {
497 return runMain(argc, argv);
498 }
catch (
const std::exception &ex) {
499 errs() <<
"llzk-smt-check: unhandled exception: " << ex.what() <<
'\n';
501 errs() <<
"llzk-smt-check: unhandled non-standard exception\n";
int main(int argc, char **argv) noexcept