1use crate::expr::{Context, ExprRef};
6use crate::smt::parser::{SmtParserError, parse_get_value_response};
7use crate::smt::serialize::serialize_cmd;
8use std::io::{BufRead, BufReader, BufWriter};
9use std::io::{Read, Write};
10use std::process::{Command, Stdio};
11use thiserror::Error;
12
13#[derive(Error, Debug)]
15pub enum Error {
16 #[error("[smt] I/O operation failed")]
17 Io(#[from] std::io::Error),
18 #[error("[smt] cannot pop because the stack is already empty")]
19 StackUnderflow,
20 #[error("[smt] {0} reported an error:\n{1}")]
21 FromSolver(String, String),
22 #[error("[smt]{0} is unreachable, the process might have died")]
23 SolverDead(String),
24 #[error("[smt] {0} returned an unexpected response:\n{1}")]
25 UnexpectedResponse(String, String),
26 #[error("[smt] failed to parse a response")]
27 Parser(#[from] SmtParserError),
28}
29
30pub type Result<T> = std::result::Result<T, Error>;
31
32#[derive(Debug, Clone, Eq, PartialEq)]
34pub enum Logic {
35 All,
36 QfAufbv,
37 QfAbv,
38}
39
40impl Logic {
41 pub(crate) fn to_smt_str(&self) -> &'static str {
42 match self {
43 Logic::All => "ALL",
44 Logic::QfAufbv => "QF_AUFBV",
45 Logic::QfAbv => "QF_ABV",
46 }
47 }
48}
49
50#[derive(Debug, Clone, Eq, PartialEq)]
52pub enum SmtCommand {
53 Exit,
54 CheckSat,
55 SetLogic(Logic),
56 SetOption(String, String),
57 SetInfo(String, String),
58 Assert(ExprRef),
59 DeclareConst(ExprRef),
60 DefineConst(ExprRef, ExprRef),
61 CheckSatAssuming(Vec<ExprRef>),
62 Push(u64),
63 Pop(u64),
64 GetValue(ExprRef),
65}
66
67#[derive(Debug, Clone, Eq, PartialEq)]
69pub enum CheckSatResponse {
70 Sat,
71 Unsat,
72 Unknown,
73}
74
75pub trait SolverMetaData {
77 fn name(&self) -> &str;
79 fn supports_check_assuming(&self) -> bool;
80 fn supports_uf(&self) -> bool;
81 fn supports_const_array(&self) -> bool;
83}
84
85pub trait Solver<R: Write + Send>: SolverMetaData {
87 type Context: SolverContext;
88 fn start(&self, replay_file: Option<R>) -> Result<Self::Context>;
90}
91
92pub trait SolverContext: SolverMetaData {
94 fn restart(&mut self) -> Result<()>;
96 fn set_logic(&mut self, option: Logic) -> Result<()>;
97 fn assert(&mut self, ctx: &Context, e: ExprRef) -> Result<()>;
98 fn declare_const(&mut self, ctx: &Context, symbol: ExprRef) -> Result<()>;
99 fn define_const(&mut self, ctx: &Context, symbol: ExprRef, expr: ExprRef) -> Result<()>;
100 fn check_sat_assuming(
101 &mut self,
102 ctx: &Context,
103 props: impl IntoIterator<Item = ExprRef>,
104 ) -> Result<CheckSatResponse>;
105 fn check_sat(&mut self) -> Result<CheckSatResponse>;
106 fn push(&mut self) -> Result<()>;
107 fn pop(&mut self) -> Result<()>;
108 fn get_value(&mut self, ctx: &mut Context, e: ExprRef) -> Result<ExprRef>;
109}
110
111#[derive(Debug, Clone, Eq, PartialEq)]
112pub struct SmtLibSolver {
113 name: &'static str,
114 args: &'static [&'static str],
115 options: &'static [&'static str],
116 supports_uf: bool,
117 supports_check_assuming: bool,
118 supports_const_array: bool,
119}
120
121impl SolverMetaData for SmtLibSolver {
122 fn name(&self) -> &str {
123 self.name
124 }
125
126 fn supports_check_assuming(&self) -> bool {
127 self.supports_check_assuming
128 }
129
130 fn supports_uf(&self) -> bool {
131 self.supports_uf
132 }
133
134 fn supports_const_array(&self) -> bool {
135 self.supports_const_array
136 }
137}
138
139impl<R: Write + Send> Solver<R> for SmtLibSolver {
140 type Context = SmtLibSolverCtx<R>;
141 fn start(&self, replay_file: Option<R>) -> Result<Self::Context> {
142 let mut proc = Command::new(self.name)
143 .args(self.args)
144 .stdin(Stdio::piped())
145 .stdout(Stdio::piped())
146 .stderr(Stdio::piped())
147 .spawn()?;
148 let stdin = BufWriter::new(proc.stdin.take().unwrap());
149 let stdout = BufReader::new(proc.stdout.take().unwrap());
150 let stderr = proc.stderr.take().unwrap();
151 let mut solver = SmtLibSolverCtx {
152 name: self.name.to_string(),
153 proc,
154 stdin,
155 stdout,
156 stderr,
157 stack_depth: 0,
158 response: String::new(),
159 replay_file,
160 has_error: false,
161 solver_args: self.args.iter().map(|a| a.to_string()).collect(),
162 solver_options: self.options.iter().map(|a| a.to_string()).collect(),
163 supports_uf: self.supports_uf,
164 supports_check_assuming: self.supports_check_assuming,
165 supports_const_array: self.supports_const_array,
166 };
167 for option in self.options.iter() {
168 solver.write_cmd(
169 None,
170 &SmtCommand::SetOption(option.to_string(), "true".to_string()),
171 )?
172 }
173 Ok(solver)
174 }
175}
176
177pub struct SmtLibSolverCtx<R: Write + Send> {
179 name: String,
180 proc: std::process::Child,
181 stdin: BufWriter<std::process::ChildStdin>,
182 stdout: BufReader<std::process::ChildStdout>,
183 stderr: std::process::ChildStderr,
184 stack_depth: usize,
185 response: String,
186 replay_file: Option<R>,
187 has_error: bool,
190 solver_args: Vec<String>,
192 solver_options: Vec<String>,
193 supports_uf: bool,
195 supports_check_assuming: bool,
196 supports_const_array: bool,
197}
198
199impl<R: Write + Send> SmtLibSolverCtx<R> {
200 #[inline]
201 fn write_cmd(&mut self, ctx: Option<&Context>, cmd: &SmtCommand) -> Result<()> {
202 if let Some(rf) = self.replay_file.as_mut() {
203 serialize_cmd(rf, ctx, cmd)?;
204 }
205 serialize_cmd(&mut self.stdin, ctx, cmd)?;
206 if let Some(rf) = self.replay_file.as_mut() {
207 rf.flush()?;
208 }
209 match self.stdin.flush() {
210 Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
211 let _ = self.replay_file.take();
213 match self.read_response() {
215 Err(e @ Error::FromSolver(_, _)) => Err(e),
216 _ => Err(Error::SolverDead(self.name.clone())),
217 }
218 }
219 Err(other) => Err(other.into()),
220 Ok(_) => Ok(()),
221 }
222 }
223
224 fn read_response(&mut self) -> Result<()> {
226 self.response.clear();
227 self.stdout.read_line(&mut self.response)?;
231 while count_parens(&self.response) > 0 {
232 self.response.push(' ');
233 self.stdout.read_line(&mut self.response)?;
234 }
235
236 if self.response.trim_start().starts_with("(error") {
238 let trimmed = self.response.trim();
239 let start = "(error ".len();
240 let msg = &trimmed[start..(trimmed.len() - start - 1)];
241 self.has_error = true;
242 Err(Error::FromSolver(self.name.clone(), msg.to_string()))
243 } else {
244 match self.proc.try_wait() {
246 Ok(Some(status)) if !status.success() => {
247 let mut err = vec![];
250 self.stderr.read_to_end(&mut err)?;
251 self.has_error = true;
252 Err(Error::FromSolver(
253 self.name.clone(),
254 String::from_utf8_lossy(&err).to_string(),
255 ))
256 }
257 _ => Ok(()),
258 }
259 }
260 }
261
262 fn read_sat_response(&mut self) -> Result<CheckSatResponse> {
263 self.stdin.flush()?; self.read_response()?;
265 let response = self.response.trim();
266 match response {
267 "sat" => Ok(CheckSatResponse::Sat),
268 "unsat" => Ok(CheckSatResponse::Unsat),
269 other => Err(Error::UnexpectedResponse(
270 self.name.clone(),
271 other.to_string(),
272 )),
273 }
274 }
275}
276
277fn count_parens(s: &str) -> i64 {
278 s.chars().fold(0, |count, cc| match cc {
279 '(' => count + 1,
280 ')' => count - 1,
281 _ => count,
282 })
283}
284
285impl<R: Write + Send> Drop for SmtLibSolverCtx<R> {
286 fn drop(&mut self) {
287 shut_down_solver(self);
288 }
289}
290
291fn shut_down_solver<R: Write + Send>(solver: &mut SmtLibSolverCtx<R>) {
293 if solver.write_cmd(None, &SmtCommand::Exit).is_ok() {
295 let _status = solver
296 .proc
297 .wait()
298 .expect("failed to wait for SMT solver to exit");
299 }
300 }
302
303impl<R: Write + Send> SolverMetaData for SmtLibSolverCtx<R> {
304 fn name(&self) -> &str {
305 &self.name
306 }
307
308 fn supports_uf(&self) -> bool {
309 self.supports_uf
310 }
311 fn supports_check_assuming(&self) -> bool {
312 self.supports_check_assuming
313 }
314
315 fn supports_const_array(&self) -> bool {
316 self.supports_const_array
317 }
318}
319
320impl<R: Write + Send> SolverContext for SmtLibSolverCtx<R> {
321 fn restart(&mut self) -> Result<()> {
322 shut_down_solver(self);
323
324 let mut proc = Command::new(&self.name)
325 .args(&self.solver_args)
326 .stdin(Stdio::piped())
327 .stdout(Stdio::piped())
328 .stderr(Stdio::piped())
329 .spawn()?;
330 let stdin = BufWriter::new(proc.stdin.take().unwrap());
331 let stdout = BufReader::new(proc.stdout.take().unwrap());
332 let stderr = proc.stderr.take().unwrap();
333 self.proc = proc;
334 self.stdin = stdin;
335 self.stdout = stdout;
336 self.stderr = stderr;
337 for option in self.solver_options.clone() {
338 self.write_cmd(None, &SmtCommand::SetOption(option, "true".to_string()))?;
339 }
340 Ok(())
341 }
342
343 fn set_logic(&mut self, logic: Logic) -> Result<()> {
344 self.write_cmd(None, &SmtCommand::SetLogic(logic))
345 }
346
347 fn assert(&mut self, ctx: &Context, e: ExprRef) -> Result<()> {
348 self.write_cmd(Some(ctx), &SmtCommand::Assert(e))
349 }
350
351 fn declare_const(&mut self, ctx: &Context, symbol: ExprRef) -> Result<()> {
352 self.write_cmd(Some(ctx), &SmtCommand::DeclareConst(symbol))
353 }
354
355 fn define_const(&mut self, ctx: &Context, symbol: ExprRef, expr: ExprRef) -> Result<()> {
356 self.write_cmd(Some(ctx), &SmtCommand::DefineConst(symbol, expr))
357 }
358
359 fn check_sat_assuming(
360 &mut self,
361 ctx: &Context,
362 props: impl IntoIterator<Item = ExprRef>,
363 ) -> Result<CheckSatResponse> {
364 let props: Vec<ExprRef> = props.into_iter().collect();
365 self.write_cmd(Some(ctx), &SmtCommand::CheckSatAssuming(props))?;
366 self.read_sat_response()
367 }
368
369 fn check_sat(&mut self) -> Result<CheckSatResponse> {
370 self.write_cmd(None, &SmtCommand::CheckSat)?;
371 self.read_sat_response()
372 }
373
374 fn push(&mut self) -> Result<()> {
375 self.write_cmd(None, &SmtCommand::Push(1))?;
376 self.stack_depth += 1;
377 Ok(())
378 }
379
380 fn pop(&mut self) -> Result<()> {
381 if self.stack_depth > 0 {
382 self.write_cmd(None, &SmtCommand::Pop(1))?;
383 self.stack_depth -= 1;
384 Ok(())
385 } else {
386 Err(Error::StackUnderflow)
387 }
388 }
389
390 fn get_value(&mut self, ctx: &mut Context, e: ExprRef) -> Result<ExprRef> {
391 self.write_cmd(Some(ctx), &SmtCommand::GetValue(e))?;
392 self.stdin.flush()?; self.read_response()?;
394 let response = self.response.trim();
395 let expr = parse_get_value_response(ctx, response.as_bytes())?;
396 Ok(expr)
397 }
398}
399
400pub const BITWUZLA: SmtLibSolver = SmtLibSolver {
401 name: "bitwuzla",
402 args: &[],
403 options: &["incremental", "produce-models"],
404 supports_uf: false,
405 supports_check_assuming: true,
406 supports_const_array: true,
407};
408
409pub const YICES2: SmtLibSolver = SmtLibSolver {
410 name: "yices-smt2",
411 args: &["--incremental"],
412 options: &[],
413 supports_uf: false, supports_check_assuming: false,
415 supports_const_array: false,
417};
418
419pub const Z3: SmtLibSolver = SmtLibSolver {
420 name: "z3",
421 args: &["-in"],
422 options: &[],
423 supports_uf: true,
424 supports_check_assuming: true,
425 supports_const_array: true,
426};
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_bitwuzla_error() {
434 let mut ctx = Context::default();
435 let replay = Some(std::fs::File::create("replay.smt").unwrap());
436 let mut solver = BITWUZLA.start(replay).unwrap();
437 let a = ctx.bv_symbol("a", 3);
438 let e = ctx.build(|c| c.equal(a, c.bit_vec_val(3, 3)));
439 solver.assert(&ctx, e).unwrap();
440 let res = solver.check_sat();
441 assert!(res.is_err(), "a was not declared!");
442 let _res = solver.declare_const(&ctx, a);
444 }
447
448 #[test]
449 fn test_bitwuzla() {
450 let mut ctx = Context::default();
451 let a = ctx.bv_symbol("a", 3);
452 let e = ctx.build(|c| c.equal(a, c.bit_vec_val(3, 3)));
453 let replay = Some(std::fs::File::create("replay.smt").unwrap());
454 let mut solver = BITWUZLA.start(replay).unwrap();
455 solver.declare_const(&ctx, a).unwrap();
456 let res = solver.check_sat_assuming(&ctx, [e]);
457 assert_eq!(res.unwrap(), CheckSatResponse::Sat);
458 let value_of_a = solver.get_value(&mut ctx, a).unwrap();
459 assert_eq!(value_of_a, ctx.bit_vec_val(3, 3));
460 }
461
462 #[test]
463 fn test_bitwuzla_restart() {
464 let mut ctx = Context::default();
465 let a = ctx.bv_symbol("a", 3);
466 let replay = Some(std::fs::File::create("replay.smt").unwrap());
467 let mut solver = BITWUZLA.start(replay).unwrap();
468 let three = ctx.bit_vec_val(3, 3);
469 let four = ctx.bit_vec_val(3, 3);
470 solver.define_const(&ctx, a, three).unwrap();
471 let _res = solver.check_sat().unwrap();
472 let value_of_a = solver.get_value(&mut ctx, a).unwrap();
473 assert_eq!(value_of_a, three);
474
475 solver.restart().unwrap();
477 solver.define_const(&ctx, a, four).unwrap();
478 let _res = solver.check_sat().unwrap();
479 let value_of_a = solver.get_value(&mut ctx, a).unwrap();
480 assert_eq!(value_of_a, four);
481 }
482}